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

Reply via email to