Daniel Kinzler has uploaded a new change for review.
https://gerrit.wikimedia.org/r/223192
Change subject: Add optional validation to wbparsevalue
......................................................................
Add optional validation to wbparsevalue
Bug: T104330
Change-Id: I315a55db8dff1225cc1077cb312b6d4c7a90d282
---
M repo/i18n/en.json
M repo/i18n/qqq.json
M repo/includes/BuilderBasedDataTypeValidatorFactory.php
M repo/includes/DataTypeValidatorFactory.php
M repo/includes/WikibaseRepo.php
M repo/includes/api/ParseValue.php
M repo/tests/phpunit/includes/ValidatorBuildersTest.php
M repo/tests/phpunit/includes/api/ParseValueTest.php
8 files changed, 191 insertions(+), 23 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase
refs/changes/92/223192/1
diff --git a/repo/i18n/en.json b/repo/i18n/en.json
index b83916d..015254f 100644
--- a/repo/i18n/en.json
+++ b/repo/i18n/en.json
@@ -554,7 +554,8 @@
"apihelp-wbparsevalue-param-values": "The values to parse",
"apihelp-wbparsevalue-param-options": "The options the parser should
use. Provided as a JSON object.",
"apihelp-wbparsevalue-example-1": "Parse a plain string into a
StringValue object.",
- "apihelp-wbparsevalue-example-2": "Parse 1994-02-08 to a TimeValue
object with a precision of 9 (the year).",
+ "apihelp-wbparsevalue-example-2": "Parse 1994-02-08 to a TimeValue
object with a precision of 9 (year).",
+ "apihelp-wbparsevalue-example-3": "Parse 1994-02-08 to a TimeValue
object with a precision of 14 (second) with validation enabled, resulting in a
validation failure.",
"apihelp-wbremoveclaims-description": "Removes Wikibase claims.",
"apihelp-wbremoveclaims-param-claim": "One GUID or several
(pipe-separated) GUIDs identifying the claims to be removed.\nAll claims must
belong to the same entity.",
"apihelp-wbremoveclaims-example-1": "Remove claim with GUID of
\"Q42$D8404CDA-25E4-4334-AF13-A3290BCD9C0N\"",
diff --git a/repo/i18n/qqq.json b/repo/i18n/qqq.json
index 04ed02d..341bd8b 100644
--- a/repo/i18n/qqq.json
+++ b/repo/i18n/qqq.json
@@ -583,6 +583,7 @@
"apihelp-wbparsevalue-param-options":
"{{doc-apihelp-param|wbparsevalue|options}}",
"apihelp-wbparsevalue-example-1":
"{{doc-apihelp-example|wbparsevalue}}",
"apihelp-wbparsevalue-example-2":
"{{doc-apihelp-example|wbparsevalue}}",
+ "apihelp-wbparsevalue-example-3":
"{{doc-apihelp-example|wbparsevalue}}",
"apihelp-wbremoveclaims-description":
"{{doc-apihelp-description|wbremoveclaims}}",
"apihelp-wbremoveclaims-param-claim":
"{{doc-apihelp-param|wbremoveclaims|claim}}",
"apihelp-wbremoveclaims-example-1":
"{{doc-apihelp-example|wbremoveclaim}}",
diff --git a/repo/includes/BuilderBasedDataTypeValidatorFactory.php
b/repo/includes/BuilderBasedDataTypeValidatorFactory.php
index 5780b04..02ec071 100644
--- a/repo/includes/BuilderBasedDataTypeValidatorFactory.php
+++ b/repo/includes/BuilderBasedDataTypeValidatorFactory.php
@@ -3,9 +3,12 @@
namespace Wikibase\Repo;
use ValueValidators\ValueValidator;
+use Wikimedia\Assert\Assert;
/**
* A factory providing ValueValidators based on DataType id that uses
ValidatorBuilders.
+ *
+ * @todo: unit tests!
*
* @author Adrian Heine < [email protected] >
*/
@@ -17,10 +20,12 @@
private $validatorBuilders;
/**
- * @param ValidatorBuilders $validatorBuilders
+ * @param callable[] $validatorBuilders
*/
- public function __construct( ValidatorBuilders $validatorBuilders ) {
- $this->validatorBuilders =
$validatorBuilders->getDataTypeValidators();
+ public function __construct( array $validatorBuilders ) {
+ Assert::parameterElementType( 'callable', $validatorBuilders,
'$validatorBuilders' );
+
+ $this->validatorBuilders = $validatorBuilders;
}
/**
@@ -30,9 +35,22 @@
* @return ValueValidator[]
*/
public function getValidators( $dataTypeId ) {
- return call_user_func(
+ if ( !isset( $this->validatorBuilders[ $dataTypeId ] ) ) {
+ //@todo: test me!
+ return array();
+ }
+
+ $validators = call_user_func(
$this->validatorBuilders[ $dataTypeId ]
);
+
+ Assert::postcondition( is_array( $validators ), "Factory
function for $dataTypeId did not return an array of ValueValidator objects!" );
+
+ foreach ( $validators as $v ) {
+ Assert::postcondition( $v instanceof ValueValidator,
"Factory function for $dataTypeId did not return an array of ValueValidator
objects!" );
+ }
+
+ return $validators;
}
}
diff --git a/repo/includes/DataTypeValidatorFactory.php
b/repo/includes/DataTypeValidatorFactory.php
index 69bb30e..4f9ee7b 100644
--- a/repo/includes/DataTypeValidatorFactory.php
+++ b/repo/includes/DataTypeValidatorFactory.php
@@ -12,6 +12,8 @@
interface DataTypeValidatorFactory {
/**
+ * Returns the validators associated with the given $dataTypeId.
+ * For unknown $dataTypeIds, an empty array is returned.
*
* @param string $dataTypeId
*
diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php
index 86af164..752ce1e 100644
--- a/repo/includes/WikibaseRepo.php
+++ b/repo/includes/WikibaseRepo.php
@@ -1144,16 +1144,18 @@
);
}
- private function getDataTypeValidatorFactory() {
+ public function getDataTypeValidatorFactory() {
$urlSchemes = $this->settings->getSetting( 'urlSchemes' );
+ $builders = new ValidatorBuilders(
+ $this->getEntityLookup(),
+ $this->getEntityIdParser(),
+ $urlSchemes,
+ $this->getMonolingualTextLanguages()
+ );
+
return new BuilderBasedDataTypeValidatorFactory(
- new ValidatorBuilders(
- $this->getEntityLookup(),
- $this->getEntityIdParser(),
- $urlSchemes,
- $this->getMonolingualTextLanguages()
- )
+ $builders->getDataTypeValidators()
);
}
diff --git a/repo/includes/api/ParseValue.php b/repo/includes/api/ParseValue.php
index aed3ff5..0b69752 100644
--- a/repo/includes/api/ParseValue.php
+++ b/repo/includes/api/ParseValue.php
@@ -14,9 +14,15 @@
use ValueParsers\ParseException;
use ValueParsers\ParserOptions;
use ValueParsers\ValueParser;
+use ValueValidators\Error;
+use ValueValidators\NullValidator;
+use ValueValidators\ValueValidator;
use Wikibase\Lib\Localizer\ExceptionLocalizer;
+use Wikibase\Repo\DataTypeValidatorFactory;
use Wikibase\Repo\ValueParserFactory;
use Wikibase\Repo\WikibaseRepo;
+use Wikibase\Validators\CompositeValidator;
+use Wikibase\Validators\ValidatorErrorLocalizer;
/**
* API module for using value parsers.
@@ -39,6 +45,16 @@
* @var ValueParserFactory
*/
private $valueParserFactory;
+
+ /**
+ * @var DataTypeValidatorFactory
+ */
+ private $dataTypeValidatorFactory;
+
+ /**
+ * @var ValidatorErrorLocalizer
+ */
+ private $validatorErrorLocalizer;
/**
* @var ExceptionLocalizer
@@ -66,7 +82,9 @@
$this->setServices(
$wikibaseRepo->getDataTypeFactory(),
new ValueParserFactory( $GLOBALS['wgValueParsers'] ),
+ $wikibaseRepo->getDataTypeValidatorFactory(),
$wikibaseRepo->getExceptionLocalizer(),
+ $wikibaseRepo->getValidatorErrorLocalizer(),
$apiHelperFactory->getErrorReporter( $this )
);
}
@@ -74,12 +92,16 @@
public function setServices(
DataTypeFactory $dataTypeFactory,
ValueParserFactory $valueParserFactory,
+ DataTypeValidatorFactory $dataTypeValidatorFactory,
ExceptionLocalizer $exceptionLocalizer,
+ ValidatorErrorLocalizer $validatorErrorLocalizer,
ApiErrorReporter $errorReporter
) {
$this->dataTypeFactory = $dataTypeFactory;
$this->valueParserFactory = $valueParserFactory;
+ $this->dataTypeValidatorFactory = $dataTypeValidatorFactory;
$this->exceptionLocalizer = $exceptionLocalizer;
+ $this->validatorErrorLocalizer = $validatorErrorLocalizer;
$this->errorReporter = $errorReporter;
}
@@ -94,9 +116,10 @@
$results = array();
$params = $this->extractRequestParams();
+ $validator = $params['validate'] ? $this->getValidator() : null;
foreach ( $params['values'] as $value ) {
- $results[] = $this->parseStringValue( $parser, $value );
+ $results[] = $this->parseStringValue( $parser, $value,
$validator );
}
$this->outputResults( $results );
@@ -119,25 +142,59 @@
if ( empty( $name ) ) {
// If neither 'datatype' not 'parser' is given, tell
the client to use 'datatype'.
- $this->dieUsageMsg( array( 'missingparam', 'datatype' )
);
+ $this->errorReporter->dieError( 'No datatype given',
'param-illegal' );
}
try {
$parser = $this->valueParserFactory->newParser( $name,
$options );
+ return $parser;
} catch ( OutOfBoundsException $ex ) {
- throw new LogicException( "No parser registered for
`$name`" );
+ $this->errorReporter->dieError( "Unknown datatype
`$name`", 'unknown-datatype' );
+ throw new LogicException( 'dieError() did not throw an
exception' );
+ }
+ }
+
+ /**
+ * @return ValueValidator
+ */
+ private function getValidator() {
+ $params = $this->extractRequestParams();
+
+ $name = $params['datatype'];
+
+ if ( empty( $name ) ) {
+ // 'datatype' parameter is required for validation.
+ $this->errorReporter->dieError( 'No datatype given',
'param-illegal' );
}
- return $parser;
+ // Note: For unknown datatype, we'll get an empty list.
+ $validators = $this->dataTypeValidatorFactory->getValidators(
$name );
+ return $this->wrapValidators( $validators );
+ }
+
+ /**
+ * @param ValueValidator[] $validators
+ *
+ * @return ValueValidator
+ */
+ private function wrapValidators( array $validators ) {
+ if ( count( $validators ) === 0 ) {
+ return new NullValidator();
+ } elseif ( count( $validators ) === 1 ) {
+ return reset( $validators );
+ } else {
+ return new CompositeValidator( $validators, true );
+ }
}
/**
* @param ValueParser $parser
* @param string $value
+ * @param ValueValidator $validator
*
* @return array
*/
- private function parseStringValue( ValueParser $parser, $value ) {
+ private function parseStringValue( ValueParser $parser, $value,
ValueValidator $validator = null ) {
$result = array(
'raw' => $value
);
@@ -158,7 +215,34 @@
$result['value'] = $parseResult;
}
+ if ( $validator ) {
+ $validatorResult = $validator->validate( $value );
+ $validationStatus =
$this->validatorErrorLocalizer->getResultStatus( $validatorResult );
+
+ $result['valid'] = $validationStatus->isOK();
+
+ if ( !$validationStatus->isOK() ) {
+ $result['error'] = 'ValidationError';
+ $this->errorReporter->addStatusToResult(
$validationStatus, $result );
+ $result['validation-errors'] =
$this->getValidatorErrorCodes( $validatorResult->getErrors() );
+ }
+ }
+
return $result;
+ }
+
+ /**
+ * @param Error[] $errors
+ *
+ * @return string[]
+ */
+ private function getValidatorErrorCodes( array $errors ) {
+ return array_map(
+ function ( Error $error ) {
+ return $error->getCode();
+ },
+ $errors
+ );
}
private function addParseErrorToResult( array &$result, ParseException
$parseError ) {
@@ -254,6 +338,9 @@
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => false,
),
+ 'validate' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ),
);
}
@@ -266,6 +353,8 @@
'apihelp-wbparsevalue-example-1',
'action=wbparsevalue&datatype=time&values=1994-02-08&options={"precision":9}' =>
'apihelp-wbparsevalue-example-2',
+
'action=wbparsevalue&datatype=time&validate&values=1994-02-08&options={"precision":14}'
=>
+ 'apihelp-wbparsevalue-example-3',
);
}
diff --git a/repo/tests/phpunit/includes/ValidatorBuildersTest.php
b/repo/tests/phpunit/includes/ValidatorBuildersTest.php
index ec8c30c..a4228dc 100644
--- a/repo/tests/phpunit/includes/ValidatorBuildersTest.php
+++ b/repo/tests/phpunit/includes/ValidatorBuildersTest.php
@@ -56,7 +56,7 @@
$urlSchemes,
$contentLanguages
);
- $validatorFactory = new BuilderBasedDataTypeValidatorFactory(
$builders );
+ $validatorFactory = new BuilderBasedDataTypeValidatorFactory(
$builders->getDataTypeValidators() );
return $validatorFactory;
}
diff --git a/repo/tests/phpunit/includes/api/ParseValueTest.php
b/repo/tests/phpunit/includes/api/ParseValueTest.php
index 28199bc..115be46 100644
--- a/repo/tests/phpunit/includes/api/ParseValueTest.php
+++ b/repo/tests/phpunit/includes/api/ParseValueTest.php
@@ -10,8 +10,10 @@
use ValueParsers\NullParser;
use Wikibase\Api\ApiErrorReporter;
use Wikibase\Api\ParseValue;
+use Wikibase\Repo\BuilderBasedDataTypeValidatorFactory;
use Wikibase\Repo\ValueParserFactory;
use Wikibase\Repo\WikibaseRepo;
+use Wikibase\Validators\RegexValidator;
/**
* @covers Wikibase\Api\ParseValue
@@ -40,6 +42,8 @@
$module = new ParseValue( $main, 'wbparsevalue' );
$exceptionLocalizer =
WikibaseRepo::getDefaultInstance()->getExceptionLocalizer();
+ $validatorErrorLocalizer =
WikibaseRepo::getDefaultInstance()->getValidatorErrorLocalizer();
+
$errorReporter = new ApiErrorReporter(
$module,
$exceptionLocalizer,
@@ -59,21 +63,32 @@
'globe-coordinate' =>
'DataValues\Geo\Parsers\GlobeCoordinateParser',
) );
+ $validatorFactory = new BuilderBasedDataTypeValidatorFactory(
array(
+ 'string' => array( $this, 'newArrayWithStringValidator'
),
+ 'url' => array( $this, 'newArrayWithStringValidator' ),
+ ) );
+
$module->setServices(
$dataTypeFactory,
$valueParserFactory,
+ $validatorFactory,
$exceptionLocalizer,
+ $validatorErrorLocalizer,
$errorReporter
);
return $module;
}
- public function newStringDataType( $spec, $name ) {
+ public function newArrayWithStringValidator() {
+ return array( new RegexValidator( '/INVALID/', true,
'no-kittens' ) );
+ }
+
+ public function newStringDataType( $name ) {
return new DataType( $name, 'string', array() );
}
- public function newCoordinateDataType( $spec, $name ) {
+ public function newCoordinateDataType( $name ) {
return new DataType( $name, 'globecoordinate', array() );
}
@@ -88,7 +103,7 @@
$result = $module->getResult();
$data = $result->getResultData( null, array(
- 'BC' => array(),
+ 'BC' => array( 'nobool' ),
'Types' => array(),
'Strip' => 'all',
) );
@@ -121,6 +136,45 @@
'0/raw' => 'foo',
'0/type' => 'unknown',
'0/value' => 'foo',
+ ),
+ ),
+
+ 'validation' => array(
+ array(
+ 'values' => 'VALID',
+ 'datatype' => 'string',
+ 'validate' => ''
+ ),
+ array(
+ '0/raw' => 'VALID',
+ '0/valid' => true,
+ ),
+ ),
+
+ 'bad value, validation failure' => array(
+ array(
+ 'values' => 'INVALID',
+ 'datatype' => 'string',
+ 'validate' => ''
+ ),
+ array(
+ '0/raw' => 'INVALID',
+ '0/valid' => false,
+ '0/error' => 'ValidationError',
+ '0/messages/0/name' =>
'wikibase-validator-no-kittens',
+ '0/messages/0/html/*' => '/.+/',
+ '0/validation-errors/0' => 'no-kittens',
+ ),
+ ),
+
+ 'bad value, no validation' => array(
+ array(
+ 'values' => 'INVALID',
+ 'datatype' => 'string',
+ ),
+ array(
+ '0/raw' => 'INVALID',
+ '0/type' => 'unknown',
),
),
@@ -193,9 +247,10 @@
protected function assertValueAtPath( $expected, $path, $data ) {
$name = '';
foreach ( $path as $step ) {
- $this->assertArrayHasKey( $step, $data );
- $data = $data[$step];
$name .= '/' . $step;
+ $this->assertInternalType( 'array', $data, $name );
+ $this->assertArrayHasKey( $step, $data, $name );
+ $data = $data[$step];
}
if ( is_string( $expected ) && preg_match(
'/^([^\s\w\d]).*\1[a-zA-Z]*$/', $expected ) ) {
--
To view, visit https://gerrit.wikimedia.org/r/223192
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I315a55db8dff1225cc1077cb312b6d4c7a90d282
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits