Daniel Kinzler has uploaded a new change for review.
https://gerrit.wikimedia.org/r/68650
Change subject: (bug 49263) Introducing SnakValidator.
......................................................................
(bug 49263) Introducing SnakValidator.
This is to be used for validating snaks in the API.
Change-Id: I7378838f9ab22a52261d79af6690f531f0f1ee2b
---
M repo/Wikibase.classes.php
M repo/Wikibase.hooks.php
A repo/includes/Validators/SnakValidator.php
A repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
4 files changed, 561 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase
refs/changes/50/68650/1
diff --git a/repo/Wikibase.classes.php b/repo/Wikibase.classes.php
index b7e9fad..57694e9 100644
--- a/repo/Wikibase.classes.php
+++ b/repo/Wikibase.classes.php
@@ -148,6 +148,9 @@
'Wikibase\ItemDeletionUpdate' =>
'includes/updates/ItemDeletionUpdate.php',
'Wikibase\ItemModificationUpdate' =>
'includes/updates/ItemModificationUpdate.php',
+ // includes/Validators
+ 'Wikibase\Validators\SnakValidator' =>
'includes/Validators/SnakValidator.php',
+
// maintenance
'Wikibase\RebuildTermsSearchKey' =>
'maintenance/rebuildTermsSearchKey.php',
'Wikibase\RebuildEntityPerPage' =>
'maintenance/rebuildEntityPerPage.php',
diff --git a/repo/Wikibase.hooks.php b/repo/Wikibase.hooks.php
index 1193737..c97f9a3 100644
--- a/repo/Wikibase.hooks.php
+++ b/repo/Wikibase.hooks.php
@@ -227,6 +227,8 @@
'updates/ItemDeletionUpdate',
'updates/ItemModificationUpdate',
+
+ 'Validators/SnakValidator',
);
foreach ( $testFiles as $file ) {
diff --git a/repo/includes/Validators/SnakValidator.php
b/repo/includes/Validators/SnakValidator.php
new file mode 100644
index 0000000..21a7fb1
--- /dev/null
+++ b/repo/includes/Validators/SnakValidator.php
@@ -0,0 +1,229 @@
+<?php
+ /**
+ *
+ * Copyright © 10.06.13 by the authors listed below.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @license GPL 2+
+ * @file
+ *
+ * @author Daniel Kinzler
+ */
+
+
+namespace Wikibase\Validators;
+
+
+use DataTypes\DataTypeFactory;
+use DataValues\DataValue;
+use ValueValidators\Result;
+use ValueValidators\ValueValidator;
+use Wikibase\Claim;
+use Wikibase\Lib\PropertyDataTypeLookup;
+use Wikibase\PropertyNoValueSnak;
+use Wikibase\PropertyValueSnak;
+use Wikibase\Reference;
+use Wikibase\References;
+use Wikibase\Snak;
+use Wikibase\SnakObject;
+use Wikibase\Statement;
+
+/**
+ * Class SnakValidator for validating Snaks.
+ *
+ * @package Wikibase\Validators
+ */
+class SnakValidator implements ValueValidator {
+
+ /**
+ * @var DataTypeFactory
+ */
+ protected $dataTypeFactory;
+
+ /**
+ * @var PropertyDataTypeLookup
+ */
+ protected $propertyDataTypeLookup;
+
+ public function __construct(
+ PropertyDataTypeLookup $propertyDataTypeLookup,
+ DataTypeFactory $dataTypeFactory ) {
+
+ $this->propertyDataTypeLookup = $propertyDataTypeLookup;
+ $this->dataTypeFactory = $dataTypeFactory;
+ }
+
+ /**
+ * Applies validation to the given Claim.
+ * This is done by validating all snaks contained in the claim, notably:
+ * the main snak, the qualifiers, and all snaks of all references,
+ * in case the claim is a Statement.
+ *
+ * @param \Wikibase\Claim $claim The value to validate
+ *
+ * @return \ValueValidators\Result
+ */
+ public function validateClaimSnaks( Claim $claim ) {
+ $snak = $claim->getMainSnak();
+ $result = $this->validate( $snak );
+
+ if ( !$result->isValid() ) {
+ return $result;
+ }
+
+ foreach ( $claim->getQualifiers() as $snak ) {
+ $result = $this->validate( $snak );
+
+ if ( !$result->isValid() ) {
+ return $result;
+ }
+ }
+
+ if ( $claim instanceof Statement ) {
+ $result = $this->validateReferences(
$claim->getReferences() );
+
+ if ( !$result->isValid() ) {
+ return $result;
+ }
+ }
+
+ return Result::newSuccess();
+ }
+
+ /**
+ * Validate a list of references.
+ * This is done by validating all snaks in all of the references.
+ *
+ * @param References $references
+ * @return \ValueValidators\Result
+ */
+ public function validateReferences( References $references ) {
+ /* @var Reference $ref */
+ foreach ( $references as $ref ) {
+ $result = $this->validateReference( $ref );
+
+ if ( !$result->isValid() ) {
+ return $result;
+ }
+ }
+
+ return Result::newSuccess();
+ }
+
+ /**
+ * Validate a list of references.
+ * This is done by validating all snaks in all of the references.
+ *
+ * @param Reference $reference
+ * @return \ValueValidators\Result
+ */
+ public function validateReference( Reference $reference ) {
+ foreach ( $reference->getSnaks() as $snak ) {
+ $result = $this->validate( $snak );
+
+ if ( !$result->isValid() ) {
+ return $result;
+ }
+ }
+
+ return Result::newSuccess();
+ }
+
+ /**
+ * Validates a Snak.
+ * For a PropertyValueSnak, this is done using the validators from the
DataType
+ * that is associated with the Snak's property.
+ * Other Snak types are currently not validated.
+ *
+ * @see ValueValidator::validate()
+ *
+ * @param Snak $snak The value to validate
+ *
+ * @return \ValueValidators\Result
+ * @throws \InvalidArgumentException
+ */
+ public function validate( $snak ) {
+ // XXX: instead of an instanceof check, we could have multiple
validators
+ // with a canValidate() method, to determine which
validator to use
+ // for a given snak.
+
+ if ( $snak instanceof SnakObject ) {
+
+ //NOTE: We only really need the DataType in case of
PropertyValueSnak,
+ // but getDataTypeIdForProperty() is a quick way
to check if the property exists.
+
+ //XXX: getDataTypeIdForProperty may throw a
PropertyNotFoundException.
+ // Shall we catch that and report it using the
Result object?
+
+ $propertyId = $snak->getPropertyId();
+ $typeId =
$this->propertyDataTypeLookup->getDataTypeIdForProperty( $propertyId );
+
+ if ( $snak instanceof PropertyValueSnak ) {
+ $dataValue = $snak->getDataValue();
+ $result = $this->validateDataValue( $dataValue,
$typeId );
+
+ //TODO: include property ID in any error report
+ } else {
+ $result = Result::newSuccess();
+ }
+ } else {
+ $result = Result::newSuccess();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validates the given data value using the given data type.
+ *
+ * @param DataValue $dataValue
+ * @param string $dataTypeId
+ *
+ * @return Result
+ */
+ public function validateDataValue( DataValue $dataValue, $dataTypeId ) {
+ $dataType = $this->dataTypeFactory->getType( $dataTypeId );
+
+ $result = Result::newSuccess();
+
+ //XXX: Perhaps DataType should have a validate() method (even
implement ValueValidator)
+ // At least, DataType should expose only one validator,
which would be a CompositeValidator
+ foreach ( $dataType->getValidators() as $validator ) {
+ $subResult = $validator->validate( $dataValue );
+
+ //XXX: Some validators should be fatal and cause us to
abort the loop.
+ // Others shouldn't.
+
+ if ( !$subResult->isValid() ) {
+ //TODO: Don't bail out immediately. Accumulate
errors from all validators.
+ // We need Result::merge() for this.
+ return $subResult;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see ValueValidator::setOptions()
+ *
+ * @param array $options
+ */
+ public function setOptions( array $options ) {
+ // Do nothing. This method shouldn't even be in the interface.
+ }
+}
\ No newline at end of file
diff --git a/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
b/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
new file mode 100644
index 0000000..fe3e95c
--- /dev/null
+++ b/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
@@ -0,0 +1,327 @@
+<?php
+
+namespace Wikibase\Test;
+
+use DataTypes\DataType;
+use DataTypes\DataTypeFactory;
+use DataValues\DataValue;
+use DataValues\StringValue;
+use ValueValidators\Error;
+use ValueValidators\Result;
+use ValueValidators\StringValidator;
+use ValueValidators\ValueValidator;
+use Wikibase\Claim;
+use Wikibase\EntityId;
+use Wikibase\Lib\InMemoryDataTypeLookup;
+use Wikibase\Lib\PropertyDataTypeLookup;
+use Wikibase\Property;
+use Wikibase\PropertyNoValueSnak;
+use Wikibase\PropertySomeValueSnak;
+use Wikibase\PropertyValueSnak;
+use Wikibase\Reference;
+use Wikibase\ReferenceList;
+use Wikibase\References;
+use Wikibase\Snak;
+use Wikibase\SnakList;
+use Wikibase\Statement;
+use Wikibase\Validators\DataValueValidator;
+use Wikibase\Validators\SnakValidator;
+
+/**
+ * @covers Wikibase\Validators\SnakValidator
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 0.4
+ *
+ * @ingroup WikibaseRepoTest
+ * @ingroup Test
+ *
+ * @group Wikibase
+ * @group WikibaseValidators
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class SnakValidatorTest extends \MediaWikiTestCase {
+
+ /**
+ * @var DataTypeFactory
+ */
+ protected $dataTypeFactory;
+
+ /**
+ * @var PropertyDataTypeLookup
+ */
+ protected $propertyDataTypeLookup;
+
+ public function setUp() {
+ parent::setUp();
+
+ $numericValidator = new SnakValidatorTestValidator(
'/^[0-9]+$/' );
+ $alphabeticValidator = new SnakValidatorTestValidator(
'/^[a-zA-Z]+$/' );
+ $lengthValidator = new SnakValidatorTestValidator(
'/^.{1,10}$/' );
+
+ $this->dataTypeFactory = new DataTypeFactory();
+ $this->dataTypeFactory->registerDataType( new DataType(
'numeric', 'string', array(), array(), array( $numericValidator,
$lengthValidator ) ) );
+ $this->dataTypeFactory->registerDataType( new DataType(
'alphabetic', 'string', array(), array(), array( $alphabeticValidator,
$lengthValidator ) ) );
+
+ $p1 = new EntityId( Property::ENTITY_TYPE, 1 );
+ $p2 = new EntityId( Property::ENTITY_TYPE, 2 );
+
+ $this->propertyDataTypeLookup = new InMemoryDataTypeLookup();
+ $this->propertyDataTypeLookup->setDataTypeForProperty( $p1,
'numeric' );
+ $this->propertyDataTypeLookup->setDataTypeForProperty( $p2,
'alphabetic' );
+ }
+
+ public static function provideValidateClaimSnaks() {
+ $p1 = new EntityId( Property::ENTITY_TYPE, 1 ); // numeric
+ $p2 = new EntityId( Property::ENTITY_TYPE, 2 ); // alphabetic
+
+ $cases = array();
+
+ $claim = new Statement( new PropertyNoValueSnak( $p1 ) );
+ $cases[] = array( $claim, 'empty claim', true );
+
+ $claim = new Statement(
+ new PropertyValueSnak( $p1, new StringValue( '12' ) )
+ );
+ $claim->setQualifiers( new SnakList( array(
+ new PropertyValueSnak( $p2, new StringValue( 'abc' ) )
+ ) ) );
+ $claim->setReferences( new ReferenceList( array (
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p2, new StringValue(
'xyz' ) )
+ ) ) )
+ ) ) );
+ $cases[] = array( $claim, 'conforming claim', true );
+
+ $brokenClaim = clone $claim;
+ $brokenClaim->setMainSnak(
+ new PropertyValueSnak( $p1, new StringValue( 'kittens'
) )
+ );
+ $cases[] = array( $brokenClaim, 'error in main snak', false );
+
+ $brokenClaim = clone $claim;
+ $brokenClaim->setQualifiers( new SnakList( array(
+ new PropertyValueSnak( $p2, new StringValue( '333' ) )
+ ) ) );
+ $cases[] = array( $brokenClaim, 'error in qualifier', false );
+
+ $brokenClaim = clone $claim;
+ $brokenClaim->setReferences( new ReferenceList( array (
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p1, new StringValue(
'xyz' ) )
+ ) ) )
+ ) ) );
+ $cases[] = array( $brokenClaim, 'error in reference', false );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideValidateClaimSnaks
+ */
+ public function testValidateClaimSnaks( Claim $claim, $description,
$expectedValid = true ) {
+ $validator = new SnakValidator( $this->propertyDataTypeLookup,
$this->dataTypeFactory );
+
+ $result = $validator->validateClaimSnaks( $claim );
+
+ $this->assertEquals( $expectedValid, $result->isValid(),
$description );
+ }
+
+ public static function provideValidateReferences() {
+ $p1 = new EntityId( Property::ENTITY_TYPE, 1 ); // numeric
+ $p2 = new EntityId( Property::ENTITY_TYPE, 2 ); // alphabetic
+
+ $cases = array();
+
+ $references = new ReferenceList();
+ $cases[] = array( $references, 'empty reference list', true );
+
+ $references = new ReferenceList( array (
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p1, new StringValue(
'123' ) )
+ ) ) ),
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p2, new StringValue(
'abc' ) )
+ ) ) )
+ ) );
+ $cases[] = array( $references, 'conforming reference list',
true );
+
+ $references = new ReferenceList( array (
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p1, new StringValue(
'123' ) )
+ ) ) ),
+ new Reference( new SnakList( array(
+ new PropertyValueSnak( $p2, new StringValue(
'456' ) )
+ ) ) )
+ ) );
+ $cases[] = array( $references, 'invalid reference list', false
);
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideValidateReferences
+ */
+ public function testValidateReferences( References $references,
$description, $expectedValid = true ) {
+ $validator = new SnakValidator( $this->propertyDataTypeLookup,
$this->dataTypeFactory );
+
+ $result = $validator->validateReferences( $references );
+
+ $this->assertEquals( $expectedValid, $result->isValid(),
$description );
+ }
+
+
+ public static function provideValidateReference() {
+ $p1 = new EntityId( Property::ENTITY_TYPE, 1 ); // numeric
+ $p2 = new EntityId( Property::ENTITY_TYPE, 2 ); // alphabetic
+
+ $cases = array();
+
+ $reference = new Reference( new SnakList() );
+ $cases[] = array( $reference, 'empty reference', true );
+
+ $reference = new Reference( new SnakList( array(
+ new PropertyValueSnak( $p1, new StringValue(
'123' ) ),
+ new PropertyValueSnak( $p2, new StringValue(
'abc' ) )
+ ) )
+ );
+ $cases[] = array( $reference, 'conforming reference', true );
+
+ $reference = new Reference( new SnakList( array(
+ new PropertyValueSnak( $p1, new StringValue(
'123' ) ),
+ new PropertyValueSnak( $p2, new StringValue(
'456' ) )
+ ) )
+ );
+ $cases[] = array( $reference, 'invalid reference', false );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideValidateReference
+ */
+ public function testValidateReference( Reference $reference,
$description, $expectedValid = true ) {
+ $validator = new SnakValidator( $this->propertyDataTypeLookup,
$this->dataTypeFactory );
+
+ $result = $validator->validateReference( $reference );
+
+ $this->assertEquals( $expectedValid, $result->isValid(),
$description );
+ }
+
+ public static function provideValidate() {
+ $p1 = new EntityId( Property::ENTITY_TYPE, 1 ); // numeric
+ $p2 = new EntityId( Property::ENTITY_TYPE, 2 ); // alphabetic
+
+ $cases = array();
+
+ $snak = new PropertyNoValueSnak( $p1 );
+ $cases[] = array( $snak, 'PropertyNoValueSnak', true );
+
+ $snak = new PropertySomeValueSnak( $p2 );
+ $cases[] = array( $snak, 'PropertySomeValueSnak', true );
+
+ $snak = new PropertyValueSnak( $p1, new StringValue( '123' ) );
+ $cases[] = array( $snak, 'valid numeric value', true );
+
+ $snak = new PropertyValueSnak( $p2, new StringValue( '123' ) );
+ $cases[] = array( $snak, 'invalid alphabetic value', false );
+
+ $snak = new PropertyValueSnak( $p2, new StringValue( 'abc' ) );
+ $cases[] = array( $snak, 'valid alphabetic value', true );
+
+ $snak = new PropertyValueSnak( $p1, new StringValue( 'abc' ) );
+ $cases[] = array( $snak, 'invalid numeric value', false );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideValidate
+ */
+ public function testValidate( Snak $snak, $description, $expectedValid
= true ) {
+ $validator = new SnakValidator( $this->propertyDataTypeLookup,
$this->dataTypeFactory );
+
+ $result = $validator->validate( $snak );
+
+ $this->assertEquals( $expectedValid, $result->isValid(),
$description );
+ }
+
+ public static function provideValidateDataValue() {
+ return array(
+ array( new StringValue( '123' ), 'numeric', 'p1',
'valid numeric value', true ),
+ array( new StringValue( '123' ), 'alphabetic', 'p2',
'invalid alphabetic value', false ),
+ array( new StringValue( 'abc' ), 'alphabetic', 'p2',
'valid alphabetic value', true ),
+ array( new StringValue( 'abc' ), 'numeric', 'p1',
'invalid numeric value', false ),
+
+ //XXX: string length check is currently broken
+ //array( new StringValue( '01234567890123456789' ),
'numeric', 'p1', 'overly long numeric value', false ),
+ );
+ }
+
+ /**
+ * @dataProvider provideValidateDataValue
+ */
+ public function testValidateDataValue( DataValue $dataValue,
$dataTypeId, $propertyName, $description, $expectedValid = true ) {
+ $validator = new SnakValidator( $this->propertyDataTypeLookup,
$this->dataTypeFactory );
+
+ $result = $validator->validateDataValue( $dataValue,
$dataTypeId, $propertyName );
+
+ $this->assertEquals( $expectedValid, $result->isValid(),
$description );
+ }
+
+}
+
+/**
+ * Simple validator for testing.
+ * Checks the string representation of a DataValue against a regular
expression.
+ *
+ * @package Wikibase\Test
+ */
+class SnakValidatorTestValidator implements ValueValidator {
+ protected $regex;
+
+ public function __construct( $regex ) {
+ $this->regex = $regex;
+ }
+
+ /**
+ * @return Result
+ */
+ public function validate( $value ) {
+ /* @var DataValue $value */
+
+ $value = $value->getArrayValue();
+
+ if ( preg_match( $this->regex, $value ) ) {
+ return Result::newSuccess();
+ } else {
+ return Result::newError( array(
+ Error::newError( "doesn't match " .
$this->regex )
+ ) );
+ }
+ }
+
+ /**
+ * @param array $options
+ */
+ public function setOptions( array $options ) {
+ // noop
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/68650
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I7378838f9ab22a52261d79af6690f531f0f1ee2b
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