jenkins-bot has submitted this change and it was merged.

Change subject: Move Validator builders out of WikibaseDataTypeBuilders
......................................................................


Move Validator builders out of WikibaseDataTypeBuilders

This is part of bug T87762.

Change-Id: I6fb14c135315c7c064ccf49c019670dfab68c857
---
M lib/includes/WikibaseDataTypeBuilders.php
M lib/tests/phpunit/WikibaseDataTypeBuildersTest.php
A repo/includes/BuilderBasedDataTypeValidatorFactory.php
A repo/includes/DataTypeValidatorFactory.php
A repo/includes/ValidatorBuilders.php
M repo/includes/Validators/SnakValidator.php
M repo/includes/WikibaseRepo.php
M repo/tests/phpunit/includes/ChangeOp/ChangeOpTestMockProvider.php
A repo/tests/phpunit/includes/ValidatorBuildersTest.php
M repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
10 files changed, 727 insertions(+), 450 deletions(-)

Approvals:
  Jeroen De Dauw: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/lib/includes/WikibaseDataTypeBuilders.php 
b/lib/includes/WikibaseDataTypeBuilders.php
index 90f7ee4..c6e09c4 100644
--- a/lib/includes/WikibaseDataTypeBuilders.php
+++ b/lib/includes/WikibaseDataTypeBuilders.php
@@ -3,26 +3,6 @@
 namespace Wikibase\Lib;
 
 use DataTypes\DataType;
-use DataValues\TimeValue;
-use ValueValidators\ValueValidator;
-use Wikibase\DataModel\Entity\EntityIdParser;
-use Wikibase\DataModel\Entity\Item;
-use Wikibase\DataModel\Entity\Property;
-use Wikibase\Lib\Store\EntityLookup;
-use Wikibase\Utils;
-use Wikibase\Validators\AlternativeValidator;
-use Wikibase\Validators\CompositeValidator;
-use Wikibase\Validators\DataFieldValidator;
-use Wikibase\Validators\DataValueValidator;
-use Wikibase\Validators\EntityExistsValidator;
-use Wikibase\Validators\MembershipValidator;
-use Wikibase\Validators\NumberRangeValidator;
-use Wikibase\Validators\NumberValidator;
-use Wikibase\Validators\RegexValidator;
-use Wikibase\Validators\StringLengthValidator;
-use Wikibase\Validators\TypeValidator;
-use Wikibase\Validators\UrlSchemeValidators;
-use Wikibase\Validators\UrlValidator;
 
 /**
  * Defines the data types supported by Wikibase.
@@ -33,36 +13,6 @@
  * @author Daniel Kinzler
  */
 class WikibaseDataTypeBuilders {
-
-       /**
-        * @var EntityIdParser
-        */
-       private $entityIdParser;
-
-       /**
-        * @var EntityLookup
-        */
-       private $entityLookup;
-
-       /**
-        * @var string[]
-        */
-       private $urlSchemes;
-
-       /**
-        * @param EntityLookup $lookup
-        * @param EntityIdParser $idParser
-        * @param string[] $urlSchemes
-        */
-       public function __construct(
-               EntityLookup $lookup,
-               EntityIdParser $idParser,
-               array $urlSchemes
-       ) {
-               $this->entityIdParser = $idParser;
-               $this->entityLookup = $lookup;
-               $this->urlSchemes = $urlSchemes;
-       }
 
        /**
         * @return array DataType builder specs
@@ -88,6 +38,7 @@
                 * wikibase-property => wikibase-entityid
                 */
 
+               // Update ValidatorBuilders in repo if you change this
                $types = array(
                        'commonsMedia'      => array( $this, 'buildMediaType' ),
                        'globe-coordinate'  => array( $this, 
'buildCoordinateType' ),
@@ -117,13 +68,7 @@
         * @return DataType
         */
        public function buildItemType( $id ) {
-               $validators = array();
-
-               //NOTE: The DataValue in question is going to be an instance of 
EntityId!
-               $validators[] = new TypeValidator( 
'Wikibase\DataModel\Entity\EntityIdValue' );
-               $validators[] = new EntityExistsValidator( $this->entityLookup, 
Item::ENTITY_TYPE );
-
-               return new DataType( $id, 'wikibase-entityid', $validators );
+               return new DataType( $id, 'wikibase-entityid', array() );
        }
 
        /**
@@ -132,30 +77,7 @@
         * @return DataType
         */
        public function buildPropertyType( $id ) {
-               $validators = array();
-
-               //NOTE: The DataValue in question is going to be an instance of 
EntityId!
-               $validators[] = new TypeValidator( 
'Wikibase\DataModel\Entity\EntityIdValue' );
-               $validators[] = new EntityExistsValidator( $this->entityLookup, 
Property::ENTITY_TYPE );
-
-               return new DataType( $id, 'wikibase-entityid', $validators );
-       }
-
-       /**
-        * @param int $maxLength Defaults to 400 characters. This was an 
arbitrary decision when it
-        * turned out that 255 was to short for descriptions.
-        *
-        * @return ValueValidator[]
-        */
-       private function getCommonStringValidators( $maxLength = 400  ) {
-               $validators = array();
-
-               $validators[] = new TypeValidator( 'string' );
-               //TODO: validate UTF8 (here and elsewhere)
-               $validators[] = new StringLengthValidator( 1, $maxLength, 
'mb_strlen' );
-               $validators[] = new RegexValidator( '/^\s|[\r\n\t]|\s$/', true 
); // no leading/trailing whitespace, no line breaks.
-
-               return $validators;
+               return new DataType( $id, 'wikibase-entityid', array() );
        }
 
        /**
@@ -164,22 +86,8 @@
         * @return DataType
         */
        public function buildMediaType( $id ) {
-               // oi_archive_name is max 255 bytes, which include a timestamp 
and an exclamation mark,
-               // so restrict file name to 240 bytes (see 
UploadBase::getTitle).
-               $validators = $this->getCommonStringValidators( 240 );
 
-               $validators[] = new RegexValidator( '@[#/:\\\\]@u', true ); // 
no nasty chars
-               // Must contain a non-empty file name and a non-empty, 
character-only file extension.
-               $validators[] = new RegexValidator( '/.\.\w+$/u' );
-               //TODO: add a validator that checks the rules that MediaWiki 
imposes on filenames for uploads.
-               //      $wgLegalTitleChars and $wgIllegalFileChars define this, 
but we need these for the *target* wiki.
-               //TODO: add a validator that uses a foreign DB query to check 
whether the file actually exists on commons.
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'string', array( new TypeValidator( 
'DataValues\DataValue' ), $topValidator ) );
+               return new DataType( $id, 'string', array() );
        }
 
        /**
@@ -188,13 +96,7 @@
         * @return DataType
         */
        public function buildStringType( $id ) {
-               $validators = $this->getCommonStringValidators();
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'string', array( new TypeValidator( 
'DataValues\DataValue' ), $topValidator ) );
+               return new DataType( $id, 'string', array() );
        }
 
        /**
@@ -203,23 +105,7 @@
         * @return DataType
         */
        public function buildMonolingualTextType( $id ) {
-               $validators = array();
-
-               $validators[] = new DataFieldValidator(
-                       'text',
-                       new CompositeValidator( 
$this->getCommonStringValidators() ) //Note: each validator is fatal
-               );
-
-               $validators[] = new DataFieldValidator(
-                       'language',
-                       new MembershipValidator( Utils::getLanguageCodes() )
-               );
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'monolingualtext', array( new 
TypeValidator( 'DataValues\DataValue' ), $topValidator ) );
+               return new DataType( $id, 'monolingualtext', array() );
        }
 
        /**
@@ -228,48 +114,7 @@
         * @return DataType
         */
        public function buildTimeType( $id ) {
-               $validators = array();
-               $validators[] = new TypeValidator( 'array' );
-
-               // Expected to be a short IRI, see TimeFormatter and TimeParser.
-               $urlValidator = $this->buildUrlValidator( array( 'http', 
'https' ), 255 );
-               //TODO: enforce well known calendar models from config
-
-               $validators[] = new DataFieldValidator( 'calendarmodel', 
$urlValidator );
-
-               // time string field
-               $timeStringValidators = array();
-               $timeStringValidators[] = new TypeValidator( 'string' );
-
-               // down to the second
-               //$maxPrecision = TimeValue::PRECISION_SECOND;
-               //$isoDataPattern = 
'!^[-+]\d{1,16}-(0\d|1[012])-([012]\d|3[01])T([01]\d|2[0123]):[0-5]\d:([0-5]\d|6[012])Z$!';
-
-               // down to the day
-               $maxPrecision = TimeValue::PRECISION_DAY;
-               $isoDataPattern = 
'!^[-+]\d{1,16}-(0\d|1[012])-([012]\d|3[01])T00:00:00Z$!';
-
-               $timeStringValidators[] = new RegexValidator( $isoDataPattern );
-
-               $validators[] = new DataFieldValidator(
-                       'time',
-                       new CompositeValidator( $timeStringValidators ) //Note: 
each validator is fatal
-               );
-
-               $precisionValidators = array();
-               $precisionValidators[] = new TypeValidator( 'integer' );
-               $precisionValidators[] = new NumberRangeValidator( 
TimeValue::PRECISION_Ga, $maxPrecision );
-
-               $validators[] = new DataFieldValidator(
-                       'precision',
-                       new CompositeValidator( $precisionValidators ) //Note: 
each validator is fatal
-               );
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'time', array( new TypeValidator( 
'DataValues\DataValue' ), $topValidator ) );
+               return new DataType( $id, 'time', array() );
        }
 
        /**
@@ -278,42 +123,7 @@
         * @return DataType
         */
        public function buildCoordinateType( $id ) {
-               $validators = array();
-               $validators[] = new TypeValidator( 'array' );
-
-               // Expected to be a short IRI, see GlobeCoordinateValue and 
GlobeCoordinateParser.
-               $urlValidator = $this->buildUrlValidator( array( 'http', 
'https' ), 255 );
-               //TODO: enforce well known reference globes from config
-
-               $validators[] = new DataFieldValidator( 'precision', new 
NumberValidator() );
-
-               $validators[] = new DataFieldValidator( 'globe', $urlValidator 
);
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'globecoordinate', array( new 
TypeValidator( 'DataValues\DataValue' ), $topValidator ) );
-       }
-
-       /**
-        * @param string[] $urlSchemes List of URL schemes, e.g. 'http'
-        * @param int $maxLength Defaults to 500 characters. Even if URLs are 
unlimited in theory they
-        * should be limited to about 2000. About 500 is a reasonable 
compromise.
-        * @see http://stackoverflow.com/a/417184
-        *
-        * @return CompositeValidator
-        */
-       public function buildUrlValidator( $urlSchemes, $maxLength = 500 ) {
-               $validators = array();
-               $validators[] = new TypeValidator( 'string' );
-               $validators[] = new StringLengthValidator( 2, $maxLength );
-
-               $urlValidators = new UrlSchemeValidators();
-               $urlSchemeValidators = $urlValidators->getValidators( 
$urlSchemes );
-               $validators[] = new UrlValidator( $urlSchemeValidators );
-
-               return new CompositeValidator( $validators ); //Note: each 
validator is fatal
+               return new DataType( $id, 'globecoordinate', array() );
        }
 
        /**
@@ -322,13 +132,7 @@
         * @return DataType
         */
        public function buildUrlType( $id ) {
-               $urlValidator = $this->buildUrlValidator( $this->urlSchemes );
-
-               $topValidator = new DataValueValidator(
-                       $urlValidator
-               );
-
-               return new DataType( $id, 'string', array( new TypeValidator( 
'DataValues\DataValue' ), $topValidator ) );
+               return new DataType( $id, 'string', array() );
        }
 
        /**
@@ -337,26 +141,7 @@
         * @return DataType
         */
        public function buildQuantityType( $id ) {
-               $validators = array();
-               $validators[] = new TypeValidator( 'array' );
-
-               // the 'amount' field is already validated by QuantityValue's 
constructor
-               // the 'digits' field is already validated by QuantityValue's 
constructor
-
-               $unitValidators = new AlternativeValidator( array(
-                       // NOTE: "1" is always considered legal for historical 
reasons,
-                       // since we use it to represent "unitless" quantities. 
We could also use
-                       // http://qudt.org/vocab/unit#Unitless or 
https://www.wikidata.org/entity/Q199
-                       new MembershipValidator( array( '1' ) ),
-                       $this->buildUrlValidator( array( 'http', 'https' ), 255 
),
-               ) );
-               $validators[] = new DataFieldValidator( 'unit', $unitValidators 
);
-
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
-               );
-
-               return new DataType( $id, 'quantity', array( new TypeValidator( 
'DataValues\QuantityValue' ), $topValidator ) );
+               return new DataType( $id, 'quantity', array() );
        }
 
 }
diff --git a/lib/tests/phpunit/WikibaseDataTypeBuildersTest.php 
b/lib/tests/phpunit/WikibaseDataTypeBuildersTest.php
index c6a2201..69353e5 100644
--- a/lib/tests/phpunit/WikibaseDataTypeBuildersTest.php
+++ b/lib/tests/phpunit/WikibaseDataTypeBuildersTest.php
@@ -2,22 +2,8 @@
 
 namespace Wikibase\Test;
 
-use DataTypes\DataType;
 use DataTypes\DataTypeFactory;
-use DataValues\Geo\Values\GlobeCoordinateValue;
-use DataValues\Geo\Values\LatLongValue;
-use DataValues\MonolingualTextValue;
-use DataValues\NumberValue;
-use DataValues\QuantityValue;
-use DataValues\StringValue;
-use DataValues\TimeValue;
-use ValueValidators\Result;
-use Wikibase\DataModel\Entity\BasicEntityIdParser;
-use Wikibase\DataModel\Entity\EntityIdValue;
-use Wikibase\DataModel\Entity\Item;
-use Wikibase\DataModel\Entity\ItemId;
-use Wikibase\DataModel\Entity\Property;
-use Wikibase\DataModel\Entity\PropertyId;
+use PHPUnit_Framework_TestCase;
 use Wikibase\Lib\WikibaseDataTypeBuilders;
 
 /**
@@ -25,161 +11,30 @@
  *
  * @group WikibaseLib
  * @group Wikibase
- * @group WikibaseValidators
  *
  * @license GPL 2+
  * @author Daniel Kinzler
  */
-class WikibaseDataTypeBuildersTest extends \PHPUnit_Framework_TestCase {
+class WikibaseDataTypeBuildersTest extends PHPUnit_Framework_TestCase {
 
-       protected function newTypeFactory() {
-               $entityIdParser = new BasicEntityIdParser();
-
-               $q8 = new Item( new ItemId( 'Q8' ) );
-
-               $p8 = Property::newFromType( 'string' );
-               $p8->setId( new PropertyId( 'P8' ) );
-
-               $entityLookup = new MockRepository();
-               $entityLookup->putEntity( $q8 );
-               $entityLookup->putEntity( $p8 );
-
-               $urlSchemes = array( 'http', 'https', 'ftp', 'mailto' );
-
-               $builders = new WikibaseDataTypeBuilders( $entityLookup, 
$entityIdParser, $urlSchemes );
+       private function newTypeFactory() {
+               $builders = new WikibaseDataTypeBuilders();
                $dataTypeFactory = new DataTypeFactory( 
$builders->getDataTypeBuilders() );
 
                return $dataTypeFactory;
        }
 
-       public function provideDataTypeValidation() {
-               $latLonValue = new LatLongValue( 0, 0 );
-
+       public function provideDataTypes() {
                $cases = array(
-                       //wikibase-item
-                       array( 'wikibase-item', 'q8', false, 'Expected 
EntityId, string supplied' ),
-                       array( 'wikibase-item', new StringValue( 'q8' ), false, 
'Expected EntityId, StringValue supplied' ),
-                       array( 'wikibase-item', new EntityIdValue( new ItemId( 
'q8' ) ), true, 'existing entity' ),
-                       array( 'wikibase-item', new EntityIdValue( new ItemId( 
'q3' ) ), false, 'missing entity' ),
-                       array( 'wikibase-item', new EntityIdValue( new 
PropertyId( 'p8' ) ), false, 'not an item' ),
-
-                       array( 'wikibase-property', new EntityIdValue( new 
PropertyId( 'p8' ) ), true, 'existing entity' ),
-                       array( 'wikibase-property', new EntityIdValue( new 
ItemId( 'q8' ) ), false, 'not a property' ),
-
-                       //commonsMedia
-                       array( 'commonsMedia', 'Foo.jpg', false, 'StringValue 
expected, string supplied' ),
-                       array( 'commonsMedia', new NumberValue( 7 ), false, 
'StringValue expected' ),
-                       array( 'commonsMedia', new StringValue( '' ), false, 
'empty string should be invalid' ),
-                       array( 'commonsMedia', new StringValue( str_repeat('x', 
250) . '.jpg' ), false, 'name too long' ),
-                       array( 'commonsMedia', new StringValue( 'Foo' ), false, 
'no file extension' ),
-                       array( 'commonsMedia', new StringValue( 'Foo.jpg' ), 
true, 'this should be good' ),
-                       array( 'commonsMedia', new StringValue( 'Foo#bar.jpg' 
), false, 'illegal character: hash' ),
-                       array( 'commonsMedia', new StringValue( 'Foo:bar.jpg' 
), false, 'illegal character: colon' ),
-                       array( 'commonsMedia', new StringValue( 'Foo/bar.jpg' 
), false, 'illegal character: slash' ),
-                       array( 'commonsMedia', new StringValue( 'Foo\bar.jpg' 
), false, 'illegal character: backslash' ),
-                       array( 'commonsMedia', new StringValue( 'Äöü.jpg' ), 
true, 'Unicode support' ),
-                       array( 'commonsMedia', new StringValue( ' Foo.jpg' ), 
false, 'media name with leading space' ),
-                       array( 'commonsMedia', new StringValue( 'Foo.jpg ' ), 
false, 'media name with trailing space' ),
-
-                       //string
-                       array( 'string', 'Foo', false, 'StringValue expected, 
string supplied' ),
-                       array( 'string', new NumberValue( 7 ), false, 
'StringValue expected' ),
-                       array( 'string', new StringValue( '' ), false, 'empty 
string should be invalid' ),
-                       array( 'string', new StringValue( 'Foo' ), true, 
'simple string' ),
-                       array( 'string', new StringValue( 'Äöü' ), true, 
'Unicode support' ),
-                       array( 'string', new StringValue( str_repeat('x', 390) 
), true, 'long, but not too long' ),
-                       array( 'string', new StringValue( str_repeat('x', 401) 
), false, 'too long' ),
-                       array( 'string', new StringValue( ' Foo' ), false, 
'string with leading space' ),
-                       array( 'string', new StringValue( 'Foo ' ), false, 
'string with trailing space' ),
-
-                       //time
-                       array( 'time', 'Foo', false, 'TimeValue expected, 
string supplied' ),
-                       array( 'time', new NumberValue( 7 ), false, 'TimeValue 
expected' ),
-
-                       //time['calendar-model']
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, '' ), 
false, 'calendar: empty string should be invalid' ),
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://' . str_repeat('x', 256) ), false, 'calendar: too long' ),
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://acme.com/calendar' ), true, 'calendar: URL' ),
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, ' 
http://acme.com/calendar ' ), false, 'calendar: untrimmed' ),
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, ' 
javascript:alert(1)' ), false, 'calendar: bad URL' ),
-
-                       //precision to the second (currently not allowed)
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T11:22:33Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://acme.com/calendar' ), false, 'time given to the second' ),
-                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_SECOND, 
'http://acme.com/calendar' ), false, 'precision: second' ),
-
-                       //time['time']
-                       //NOTE: The below will fail with a 
IllegalValueExcpetion once the TimeValue constructor enforces the time format.
-                       //      Once that is done, this test and the respective 
validator can and should both be removed.
-                       //array( 'string', new TimeValue( '2013-06-06 
11:22:33', 0, 0, 0, 0, 'http://acme.com/calendar' ), false, 'time: not ISO 
8601' ),
-
-                       //TODO: calendar must be an item reference
-                       //TODO: calendar must be from a list of configured 
values
-
-                       //globe-coordinate
-                       array( 'globe-coordinate', 'Foo', false, 
'GlobeCoordinateValue expected, string supplied' ),
-                       array( 'globe-coordinate', new NumberValue( 7 ), false, 
'GlobeCoordinateValue expected' ),
-
-                       //globe-coordinate[precision]
-                       array(
-                               'globe-coordinate',
-                               new GlobeCoordinateValue( $latLonValue, 1, 
'http://www.wikidata.org/entity/Q2' ),
-                               true,
-                               'integer precision is valid'
-                       ),
-                       array(
-                               'globe-coordinate',
-                               new GlobeCoordinateValue( $latLonValue, 0.2, 
'http://www.wikidata.org/entity/Q2' ),
-                               true,
-                               'float precision is valid'
-                       ),
-                       array(
-                               'globe-coordinate',
-                               new GlobeCoordinateValue( $latLonValue, null, 
'http://www.wikdiata.org/entity/Q2' ),
-                               false,
-                               'null precision is invalid'
-                       ),
-
-                       //globe-coordinate[globe]
-                       // FIXME: this is testing unimplemented behaviour? 
Probably broken...
-                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, '' ), false, 'globe: empty string should be invalid' ),
-                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, 'http://' . str_repeat('x', 256) ), false, 'globe: too long' ),
-                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, 'http://acme.com/globe' ), true, 'globe: URL' ),
-                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, ' http://acme.com/globe ' ), false, 'globe: untrimmed' ),
-                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, ' javascript:alert(1) ' ), false, 'globe: bad URL scheme' ),
-                       //TODO: globe must be an item reference
-                       //TODO: globe must be from a list of configured values
-
-                       // url
-                       array( 'url', 'Foo', false, 'StringValue expected, 
string supplied' ),
-                       array( 'url', new NumberValue( 7 ), false, 'StringValue 
expected' ),
-
-                       array( 'url', new StringValue( 'http://acme.com' ), 
true, 'Simple HTTP URL' ),
-                       array( 'url', new StringValue( 'https://acme.com' ), 
true, 'Simple HTTPS URL' ),
-                       array( 'url', new StringValue( 'ftp://acme.com' ), 
true, 'Simple FTP URL' ),
-                       array( 'url', new StringValue( 
'http://acme.com/foo/bar?some=stuff#fragment' ), true, 'Complex HTTP URL' ),
-
-                       // evil url
-                       array( 'url', new StringValue( '//bla' ), false, 
'Protocol-relative' ),
-                       array( 'url', new StringValue( '/bla/bla' ), false, 
'relative path' ),
-                       array( 'url', new StringValue( 'just stuff' ), false, 
'just words' ),
-                       array( 'url', new StringValue( 
'javascript:alert("evil")' ), false, 'JavaScript URL' ),
-                       array( 'url', new StringValue( 'http://' ), false, 'bad 
http URL' ),
-                       array( 'url', new StringValue( 'http://' . 
str_repeat('x', 505) ), false, 'URL too long' ),
-
-                       array( 'url', new StringValue( ' http://acme.com' ), 
false, 'URL with leading space' ),
-                       array( 'url', new StringValue( 'http://acme.com ' ), 
false, 'URL with trailing space' ),
-
-                       //quantity
-                       array( 'quantity', QuantityValue::newFromNumber( 5 ), 
true, 'Simple integer' ),
-                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'http://qudt.org/vocab/unit#Meter' ), true, 'Vocabulary URI' ),
-                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'https://www.wikidata.org/entity/Q11573' ), true, 'Wikidata URI' ),
-                       array( 'quantity', QuantityValue::newFromNumber( 5, '1' 
), true, '1 means unitless' ),
-                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'kittens' ), false, 'Bad unit URI' ),
-                       array( 'quantity', QuantityValue::newFromNumber( 
'-11.234', '1', '-10', '-12' ), true, 'decimal strings' ),
-
-                       //monolingual text
-                       array( 'monolingualtext', new MonolingualTextValue( 
'en', 'text' ), true, 'Simple value' ),
-                       array( 'monolingualtext', new MonolingualTextValue( 
'grrr', 'text' ), false, 'Not a valid language' ),
+                       array( 'wikibase-item' ),
+                       array( 'wikibase-property' ),
+                       array( 'commonsMedia' ),
+                       array( 'string' ),
+                       array( 'time' ),
+                       array( 'globe-coordinate' ),
+                       array( 'url' ),
+                       array( 'quantity' ),
+                       array( 'monolingualtext' ),
                );
 
                if ( defined( 'WB_EXPERIMENTAL_FEATURES' ) && 
WB_EXPERIMENTAL_FEATURES ) {
@@ -193,37 +48,13 @@
        }
 
        /**
-        * @dataProvider provideDataTypeValidation
+        * @dataProvider provideDataTypes
         */
-       public function testDataTypeValidation( $typeId, $value, $expected, 
$message ) {
+       public function testDataTypes( $typeId ) {
                $typeFactory = $this->newTypeFactory();
                $type = $typeFactory->getType( $typeId );
 
-               $this->assertValidation( $expected, $type, $value, $message );
-       }
-
-       protected function assertValidation( $expected, DataType $type, $value, 
$message ) {
-               $validators = $type->getValidators(); //TODO: there should 
probably be a DataType::validate() method.
-
-               $result = Result::newSuccess();
-               foreach ( $validators as $validator ) {
-                       $result = $validator->validate( $value );
-
-                       if ( !$result->isValid() ) {
-                               break;
-                       }
-               }
-
-               if ( $expected ) {
-                       $errors = $result->getErrors();
-                       if ( !empty( $errors ) ) {
-                               $this->fail( $message . "\n" . 
$errors[0]->getText() );
-                       }
-
-                       $this->assertEquals( $expected, $result->isValid(), 
$message );
-               } else {
-                       $this->assertEquals( $expected, $result->isValid(), 
$message );
-               }
+               $this->assertInstanceOf( 'DataTypes\DataType', $type );
        }
 
 }
diff --git a/repo/includes/BuilderBasedDataTypeValidatorFactory.php 
b/repo/includes/BuilderBasedDataTypeValidatorFactory.php
new file mode 100644
index 0000000..9a628a3
--- /dev/null
+++ b/repo/includes/BuilderBasedDataTypeValidatorFactory.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Wikibase\Repo;
+
+/**
+ * A factory providing ValueValidators based on DataType id that uses 
ValidatorBuilders.
+ *
+ * @author Adrian Heine < [email protected] >
+ */
+class BuilderBasedDataTypeValidatorFactory implements DataTypeValidatorFactory 
{
+
+       /**
+        * @var callable[]
+        */
+       private $validatorBuilders;
+
+       /**
+        * @param ValidatorBuilders $validatorBuilders
+        */
+       public function __construct( ValidatorBuilders $validatorBuilders ) {
+               $this->validatorBuilders = 
$validatorBuilders->getDataTypeValidators();
+       }
+
+       /**
+        *
+        * @param string $dataTypeId
+        *
+        * @return ValueValidator[]
+        */
+       public function getValidators( $dataTypeId ) {
+               return call_user_func(
+                       $this->validatorBuilders[ $dataTypeId ]
+               );
+       }
+
+}
diff --git a/repo/includes/DataTypeValidatorFactory.php 
b/repo/includes/DataTypeValidatorFactory.php
new file mode 100644
index 0000000..0bc68f4
--- /dev/null
+++ b/repo/includes/DataTypeValidatorFactory.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Wikibase\Repo;
+
+/**
+ * A factory providing ValueValidators based on DataType id.
+ *
+ * @author Adrian Heine < [email protected] >
+ */
+interface DataTypeValidatorFactory {
+
+       /**
+        *
+        * @param string $dataTypeId
+        *
+        * @return ValueValidator[]
+        */
+       public function getValidators( $dataTypeId );
+
+}
diff --git a/repo/includes/ValidatorBuilders.php 
b/repo/includes/ValidatorBuilders.php
new file mode 100644
index 0000000..821f102
--- /dev/null
+++ b/repo/includes/ValidatorBuilders.php
@@ -0,0 +1,324 @@
+<?php
+
+namespace Wikibase\Repo;
+
+use DataValues\TimeValue;
+use ValueValidators\ValueValidator;
+use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\Lib\Store\EntityLookup;
+use Wikibase\Utils;
+use Wikibase\Validators\AlternativeValidator;
+use Wikibase\Validators\CompositeValidator;
+use Wikibase\Validators\DataFieldValidator;
+use Wikibase\Validators\DataValueValidator;
+use Wikibase\Validators\EntityExistsValidator;
+use Wikibase\Validators\MembershipValidator;
+use Wikibase\Validators\NumberRangeValidator;
+use Wikibase\Validators\NumberValidator;
+use Wikibase\Validators\RegexValidator;
+use Wikibase\Validators\StringLengthValidator;
+use Wikibase\Validators\TypeValidator;
+use Wikibase\Validators\UrlSchemeValidators;
+use Wikibase\Validators\UrlValidator;
+
+/**
+ * Defines validators for the data types supported by Wikibase.
+ *
+ * @since 0.4
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class ValidatorBuilders {
+
+       /**
+        * @var EntityIdParser
+        */
+       private $entityIdParser;
+
+       /**
+        * @var EntityLookup
+        */
+       private $entityLookup;
+
+       /**
+        * @var string[]
+        */
+       private $urlSchemes;
+
+       /**
+        * @param EntityLookup $lookup
+        * @param EntityIdParser $idParser
+        * @param string[] $urlSchemes
+        */
+       public function __construct(
+               EntityLookup $lookup,
+               EntityIdParser $idParser,
+               array $urlSchemes
+       ) {
+               $this->entityIdParser = $idParser;
+               $this->entityLookup = $lookup;
+               $this->urlSchemes = $urlSchemes;
+       }
+
+       /**
+        * @return callable[] ValueValidator for DataTypes build spec
+        */
+       public function getDataTypeValidators() {
+
+               $types = array(
+                       'commonsMedia'      => array( $this, 
'buildMediaValidators' ),
+                       'globe-coordinate'  => array( $this, 
'buildCoordinateValidators' ),
+                       'quantity'          => array( $this, 
'buildQuantityValidators' ),
+                       'string'            => array( $this, 
'buildStringValidators' ),
+                       'time'              => array( $this, 
'buildTimeValidators' ),
+                       'url'               => array( $this, 
'buildUrlValidators' ),
+                       'wikibase-item'     => array( $this, 
'buildItemValidators' ),
+                       'wikibase-property' => array( $this, 
'buildPropertyValidators' ),
+                       'monolingualtext'   => array( $this, 
'buildMonolingualTextValidators' ),
+               );
+
+               $experimental = array(
+                       // 'multilingualtext' => array( $this, 
'buildMultilingualTextValidators' ),
+               );
+
+               if ( defined( 'WB_EXPERIMENTAL_FEATURES' ) && 
WB_EXPERIMENTAL_FEATURES ) {
+                       $types = array_merge( $types, $experimental );
+               }
+
+               return $types;
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildItemValidators() {
+               $validators = array();
+
+               //NOTE: The DataValue in question is going to be an instance of 
EntityId!
+               $validators[] = new TypeValidator( 
'Wikibase\DataModel\Entity\EntityIdValue' );
+               $validators[] = new EntityExistsValidator( $this->entityLookup, 
Item::ENTITY_TYPE );
+
+               return $validators;
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildPropertyValidators( ) {
+               $validators = array();
+
+               //NOTE: The DataValue in question is going to be an instance of 
EntityId!
+               $validators[] = new TypeValidator( 
'Wikibase\DataModel\Entity\EntityIdValue' );
+               $validators[] = new EntityExistsValidator( $this->entityLookup, 
Property::ENTITY_TYPE );
+
+               return $validators;
+       }
+
+       /**
+        * @param int $maxLength Defaults to 400 characters. This was an 
arbitrary decision when it
+        * turned out that 255 was to short for descriptions.
+        *
+        * @return ValueValidator[]
+        */
+       private function getCommonStringValidators( $maxLength = 400  ) {
+               $validators = array();
+
+               $validators[] = new TypeValidator( 'string' );
+               //TODO: validate UTF8 (here and elsewhere)
+               $validators[] = new StringLengthValidator( 1, $maxLength, 
'mb_strlen' );
+               $validators[] = new RegexValidator( '/^\s|[\r\n\t]|\s$/', true 
); // no leading/trailing whitespace, no line breaks.
+
+               return $validators;
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildMediaValidators() {
+               // oi_archive_name is max 255 bytes, which include a timestamp 
and an exclamation mark,
+               // so restrict file name to 240 bytes (see 
UploadBase::getTitle).
+               $validators = $this->getCommonStringValidators( 240 );
+
+               $validators[] = new RegexValidator( '@[#/:\\\\]@u', true ); // 
no nasty chars
+               // Must contain a non-empty file name and a non-empty, 
character-only file extension.
+               $validators[] = new RegexValidator( '/.\.\w+$/u' );
+               //TODO: add a validator that checks the rules that MediaWiki 
imposes on filenames for uploads.
+               //      $wgLegalTitleChars and $wgIllegalFileChars define this, 
but we need these for the *target* wiki.
+               //TODO: add a validator that uses a foreign DB query to check 
whether the file actually exists on commons.
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildStringValidators() {
+               $validators = $this->getCommonStringValidators();
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildMonolingualTextValidators() {
+               $validators = array();
+
+               $validators[] = new DataFieldValidator(
+                       'text',
+                       new CompositeValidator( 
$this->getCommonStringValidators() ) //Note: each validator is fatal
+               );
+
+               $validators[] = new DataFieldValidator(
+                       'language',
+                       new MembershipValidator( Utils::getLanguageCodes() )
+               );
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildTimeValidators() {
+               $validators = array();
+               $validators[] = new TypeValidator( 'array' );
+
+               // Expected to be a short IRI, see TimeFormatter and TimeParser.
+               $urlValidator = $this->getUrlValidator( array( 'http', 'https' 
), 255 );
+               //TODO: enforce well known calendar models from config
+
+               $validators[] = new DataFieldValidator( 'calendarmodel', 
$urlValidator );
+
+               // time string field
+               $timeStringValidators = array();
+               $timeStringValidators[] = new TypeValidator( 'string' );
+
+               // down to the second
+               //$maxPrecision = TimeValue::PRECISION_SECOND;
+               //$isoDataPattern = 
'!^[-+]\d{1,16}-(0\d|1[012])-([012]\d|3[01])T([01]\d|2[0123]):[0-5]\d:([0-5]\d|6[012])Z$!';
+
+               // down to the day
+               $maxPrecision = TimeValue::PRECISION_DAY;
+               $isoDataPattern = 
'!^[-+]\d{1,16}-(0\d|1[012])-([012]\d|3[01])T00:00:00Z$!';
+
+               $timeStringValidators[] = new RegexValidator( $isoDataPattern );
+
+               $validators[] = new DataFieldValidator(
+                       'time',
+                       new CompositeValidator( $timeStringValidators ) //Note: 
each validator is fatal
+               );
+
+               $precisionValidators = array();
+               $precisionValidators[] = new TypeValidator( 'integer' );
+               $precisionValidators[] = new NumberRangeValidator( 
TimeValue::PRECISION_Ga, $maxPrecision );
+
+               $validators[] = new DataFieldValidator(
+                       'precision',
+                       new CompositeValidator( $precisionValidators ) //Note: 
each validator is fatal
+               );
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildCoordinateValidators() {
+               $validators = array();
+               $validators[] = new TypeValidator( 'array' );
+
+               // Expected to be a short IRI, see GlobeCoordinateValue and 
GlobeCoordinateParser.
+               $urlValidator = $this->getUrlValidator( array( 'http', 'https' 
), 255 );
+               //TODO: enforce well known reference globes from config
+
+               $validators[] = new DataFieldValidator( 'precision', new 
NumberValidator() );
+
+               $validators[] = new DataFieldValidator( 'globe', $urlValidator 
);
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @param string[] $urlSchemes List of URL schemes, e.g. 'http'
+        * @param int $maxLength Defaults to 500 characters. Even if URLs are 
unlimited in theory they
+        * should be limited to about 2000. About 500 is a reasonable 
compromise.
+        * @see http://stackoverflow.com/a/417184
+        *
+        * @return CompositeValidator
+        */
+       private function getUrlValidator( $urlSchemes, $maxLength = 500 ) {
+               $validators = array();
+               $validators[] = new TypeValidator( 'string' );
+               $validators[] = new StringLengthValidator( 2, $maxLength );
+
+               $urlValidators = new UrlSchemeValidators();
+               $urlSchemeValidators = $urlValidators->getValidators( 
$urlSchemes );
+               $validators[] = new UrlValidator( $urlSchemeValidators );
+
+               return new CompositeValidator( $validators ); //Note: each 
validator is fatal
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildUrlValidators( ) {
+               $urlValidator = $this->getUrlValidator( $this->urlSchemes );
+
+               $topValidator = new DataValueValidator(
+                       $urlValidator
+               );
+
+               return array( new TypeValidator( 'DataValues\DataValue' ), 
$topValidator );
+       }
+
+       /**
+        * @return ValueValidator[]
+        */
+       public function buildQuantityValidators( ) {
+               $validators = array();
+               $validators[] = new TypeValidator( 'array' );
+
+               // the 'amount' field is already validated by QuantityValue's 
constructor
+               // the 'digits' field is already validated by QuantityValue's 
constructor
+
+               $unitValidators = new AlternativeValidator( array(
+                       // NOTE: "1" is always considered legal for historical 
reasons,
+                       // since we use it to represent "unitless" quantities. 
We could also use
+                       // http://qudt.org/vocab/unit#Unitless or 
https://www.wikidata.org/entity/Q199
+                       new MembershipValidator( array( '1' ) ),
+                       $this->getUrlValidator( array( 'http', 'https' ), 255 ),
+               ) );
+               $validators[] = new DataFieldValidator( 'unit', $unitValidators 
);
+
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( $validators ) //Note: each 
validator is fatal
+               );
+
+               return array( new TypeValidator( 'DataValues\QuantityValue' ), 
$topValidator );
+       }
+
+}
diff --git a/repo/includes/Validators/SnakValidator.php 
b/repo/includes/Validators/SnakValidator.php
index 14aa045..49db831 100644
--- a/repo/includes/Validators/SnakValidator.php
+++ b/repo/includes/Validators/SnakValidator.php
@@ -17,6 +17,7 @@
 use Wikibase\DataModel\Snak\PropertyValueSnak;
 use Wikibase\DataModel\Snak\Snak;
 use Wikibase\DataModel\Statement\Statement;
+use Wikibase\Repo\DataTypeValidatorFactory;
 
 /**
  * Class SnakValidator for validating Snaks.
@@ -29,21 +30,28 @@
 class SnakValidator implements ValueValidator {
 
        /**
+        * @var DataTypeFactory
+        */
+       private $dataTypeFactory;
+
+       /**
         * @var PropertyDataTypeLookup
         */
        private $propertyDataTypeLookup;
 
        /**
-        * @var DataTypeFactory
+        * @var DataTypeValidatorFactory
         */
-       private $dataTypeFactory;
+       private $validatorFactory;
 
        public function __construct(
                PropertyDataTypeLookup $propertyDataTypeLookup,
-               DataTypeFactory $dataTypeFactory
+               DataTypeFactory $dataTypeFactory,
+               DataTypeValidatorFactory $validatorFactory
        ) {
-               $this->propertyDataTypeLookup = $propertyDataTypeLookup;
                $this->dataTypeFactory = $dataTypeFactory;
+               $this->propertyDataTypeLookup = $propertyDataTypeLookup;
+               $this->validatorFactory = $validatorFactory;
        }
 
        /**
@@ -174,7 +182,7 @@
         * @return Result
         */
        public function validateDataValue( DataValue $dataValue, $dataTypeId ) {
-               $dataType = $this->dataTypeFactory->getType( $dataTypeId );
+               $dataValueType = $this->dataTypeFactory->getType( $dataTypeId 
)->getDataValueType();
 
                if ( $dataValue instanceof UnDeserializableValue ) {
                        $result = Result::newError( array(
@@ -185,22 +193,21 @@
                                        array( $dataValue->getReason() )
                                ),
                        ) );
-               } elseif ( $dataType->getDataValueType() != 
$dataValue->getType() ) {
+               } elseif ( $dataValueType != $dataValue->getType() ) {
                        $result = Result::newError( array(
                                Error::newError(
-                                       'Bad value type: ' . 
$dataValue->getType() . ', expected ' . $dataType->getDataValueType(),
+                                       'Bad value type: ' . 
$dataValue->getType() . ', expected ' . $dataValueType,
                                        null,
                                        'bad-value-type',
-                                       array( $dataValue->getType(), 
$dataType->getDataValueType() )
+                                       array( $dataValue->getType(), 
$dataValueType )
                                ),
                        ) );
                } else {
                        $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 ) {
+               //XXX: DataTypeValidatorFactory should expose only one 
validator, which would be a CompositeValidator
+               foreach ( $this->validatorFactory->getValidators( $dataTypeId ) 
as $validator ) {
                        $subResult = $validator->validate( $dataValue );
 
                        //XXX: Some validators should be fatal and cause us to 
abort the loop.
diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php
index fbfbe04..af4f18f 100644
--- a/repo/includes/WikibaseRepo.php
+++ b/repo/includes/WikibaseRepo.php
@@ -211,12 +211,7 @@
         */
        public function getDataTypeFactory() {
                if ( $this->dataTypeFactory === null ) {
-                       $urlSchemes = $this->settings->getSetting( 'urlSchemes' 
);
-                       $builders = new WikibaseDataTypeBuilders(
-                               $this->getEntityLookup(),
-                               $this->getEntityIdParser(),
-                               $urlSchemes
-                       );
+                       $builders = new WikibaseDataTypeBuilders();
 
                        $typeBuilderSpecs = array_intersect_key(
                                $builders->getDataTypeBuilders(),
@@ -424,7 +419,8 @@
        public function getSnakValidator() {
                return new SnakValidator(
                        $this->getPropertyDataTypeLookup(),
-                       $this->getDataTypeFactory()
+                       $this->getDataTypeFactory(),
+                       $this->getDataTypeValidatorFactory()
                );
        }
 
@@ -1030,4 +1026,16 @@
                );
        }
 
+       private function getDataTypeValidatorFactory() {
+               $urlSchemes = $this->settings->getSetting( 'urlSchemes' );
+
+               return new BuilderBasedDataTypeValidatorFactory(
+                       new ValidatorBuilders(
+                               $this->getEntityLookup(),
+                               $this->getEntityIdParser(),
+                               $urlSchemes
+                       )
+               );
+       }
+
 }
diff --git a/repo/tests/phpunit/includes/ChangeOp/ChangeOpTestMockProvider.php 
b/repo/tests/phpunit/includes/ChangeOp/ChangeOpTestMockProvider.php
index aaba7e1..b5f5eaf 100644
--- a/repo/tests/phpunit/includes/ChangeOp/ChangeOpTestMockProvider.php
+++ b/repo/tests/phpunit/includes/ChangeOp/ChangeOpTestMockProvider.php
@@ -146,7 +146,8 @@
        public function getMockSnakValidator() {
                return new SnakValidator(
                        $this->getMockPropertyDataTypeLookup(),
-                       $this->getMockDataTypeFactory()
+                       $this->getMockDataTypeFactory(),
+                       $this->getMockDataTypeValidatorFactory()
                );
        }
 
@@ -167,22 +168,12 @@
 
        /**
         * Returns a mock MockDataTypeFactory that will return the same 
DataType for
-        * any type id; The ValueValidators of that DataType will accept any
-        * StringValue, unless the string is "INVALID".
+        * any type id.
         *
         * @return DataTypeFactory
         */
        public function getMockDataTypeFactory() {
-               // consider "INVALID" to be invalid
-               $topValidator = new DataValueValidator(
-                       new CompositeValidator( array(
-                               new TypeValidator( 'string' ),
-                               new RegexValidator( '/INVALID/', true ),
-                       ), true )
-               );
-
-               $validators = array( new TypeValidator( 'DataValues\DataValue' 
), $topValidator );
-               $stringType = new DataType( 'string', 'string', $validators );
+               $stringType = new DataType( 'string', 'string', array() );
 
                $types = array(
                        'string' => $stringType
@@ -205,6 +196,33 @@
        }
 
        /**
+        * Returns a mock DataTypeValidatorFactory that returns validators 
which will accept any
+        * StringValue, unless the string is "INVALID".
+        *
+        * @return DataTypeValidatorFactory
+        */
+       public function getMockDataTypeValidatorFactory() {
+               // consider "INVALID" to be invalid
+               $topValidator = new DataValueValidator(
+                       new CompositeValidator( array(
+                               new TypeValidator( 'string' ),
+                               new RegexValidator( '/INVALID/', true ),
+                       ), true )
+               );
+
+               $validators = array( new TypeValidator( 'DataValues\DataValue' 
), $topValidator );
+
+               $mock = $this->getMock( 
'Wikibase\Repo\DataTypeValidatorFactory' );
+               $mock->expects( PHPUnit_Framework_TestCase::any() )
+                       ->method( 'getValidators' )
+                       ->will( PHPUnit_Framework_TestCase::returnCallback( 
function( $id ) use ( $validators ) {
+                               return $validators;
+                       } ) );
+
+               return $mock;
+       }
+
+       /**
         * Returns a mock validator. The term and the language "INVALID" is 
considered to be
         * invalid.
         *
diff --git a/repo/tests/phpunit/includes/ValidatorBuildersTest.php 
b/repo/tests/phpunit/includes/ValidatorBuildersTest.php
new file mode 100644
index 0000000..c5c98b8
--- /dev/null
+++ b/repo/tests/phpunit/includes/ValidatorBuildersTest.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Wikibase\Test;
+
+use DataValues\Geo\Values\GlobeCoordinateValue;
+use DataValues\Geo\Values\LatLongValue;
+use DataValues\MonolingualTextValue;
+use DataValues\NumberValue;
+use DataValues\QuantityValue;
+use DataValues\StringValue;
+use DataValues\TimeValue;
+use PHPUnit_Framework_TestCase;
+use ValueValidators\Result;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Repo\BuilderBasedDataTypeValidatorFactory;
+use Wikibase\Repo\ValidatorBuilders;
+
+/**
+ * @covers Wikibase\Lib\ValidatorBuilders
+ *
+ * @group WikibaseLib
+ * @group Wikibase
+ * @group WikibaseValidators
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class ValidatorBuildersTest extends PHPUnit_Framework_TestCase {
+
+       protected function newValidatorFactory() {
+               $entityIdParser = new BasicEntityIdParser();
+
+               $q8 = new Item( new ItemId( 'Q8' ) );
+
+               $p8 = Property::newFromType( 'string' );
+               $p8->setId( new PropertyId( 'P8' ) );
+
+               $entityLookup = new MockRepository();
+               $entityLookup->putEntity( $q8 );
+               $entityLookup->putEntity( $p8 );
+
+               $urlSchemes = array( 'http', 'https', 'ftp', 'mailto' );
+
+               $builders = new ValidatorBuilders( $entityLookup, 
$entityIdParser, $urlSchemes );
+               $validatorFactory = new BuilderBasedDataTypeValidatorFactory( 
$builders );
+
+               return $validatorFactory;
+       }
+
+       public function provideDataTypeValidation() {
+               $latLonValue = new LatLongValue( 0, 0 );
+
+               $cases = array(
+                       //wikibase-item
+                       array( 'wikibase-item', 'q8', false, 'Expected 
EntityId, string supplied' ),
+                       array( 'wikibase-item', new StringValue( 'q8' ), false, 
'Expected EntityId, StringValue supplied' ),
+                       array( 'wikibase-item', new EntityIdValue( new ItemId( 
'q8' ) ), true, 'existing entity' ),
+                       array( 'wikibase-item', new EntityIdValue( new ItemId( 
'q3' ) ), false, 'missing entity' ),
+                       array( 'wikibase-item', new EntityIdValue( new 
PropertyId( 'p8' ) ), false, 'not an item' ),
+
+                       array( 'wikibase-property', new EntityIdValue( new 
PropertyId( 'p8' ) ), true, 'existing entity' ),
+                       array( 'wikibase-property', new EntityIdValue( new 
ItemId( 'q8' ) ), false, 'not a property' ),
+
+                       //commonsMedia
+                       array( 'commonsMedia', 'Foo.jpg', false, 'StringValue 
expected, string supplied' ),
+                       array( 'commonsMedia', new NumberValue( 7 ), false, 
'StringValue expected' ),
+                       array( 'commonsMedia', new StringValue( '' ), false, 
'empty string should be invalid' ),
+                       array( 'commonsMedia', new StringValue( str_repeat('x', 
250) . '.jpg' ), false, 'name too long' ),
+                       array( 'commonsMedia', new StringValue( 'Foo' ), false, 
'no file extension' ),
+                       array( 'commonsMedia', new StringValue( 'Foo.jpg' ), 
true, 'this should be good' ),
+                       array( 'commonsMedia', new StringValue( 'Foo#bar.jpg' 
), false, 'illegal character: hash' ),
+                       array( 'commonsMedia', new StringValue( 'Foo:bar.jpg' 
), false, 'illegal character: colon' ),
+                       array( 'commonsMedia', new StringValue( 'Foo/bar.jpg' 
), false, 'illegal character: slash' ),
+                       array( 'commonsMedia', new StringValue( 'Foo\bar.jpg' 
), false, 'illegal character: backslash' ),
+                       array( 'commonsMedia', new StringValue( 'Äöü.jpg' ), 
true, 'Unicode support' ),
+                       array( 'commonsMedia', new StringValue( ' Foo.jpg' ), 
false, 'media name with leading space' ),
+                       array( 'commonsMedia', new StringValue( 'Foo.jpg ' ), 
false, 'media name with trailing space' ),
+
+                       //string
+                       array( 'string', 'Foo', false, 'StringValue expected, 
string supplied' ),
+                       array( 'string', new NumberValue( 7 ), false, 
'StringValue expected' ),
+                       array( 'string', new StringValue( '' ), false, 'empty 
string should be invalid' ),
+                       array( 'string', new StringValue( 'Foo' ), true, 
'simple string' ),
+                       array( 'string', new StringValue( 'Äöü' ), true, 
'Unicode support' ),
+                       array( 'string', new StringValue( str_repeat('x', 390) 
), true, 'long, but not too long' ),
+                       array( 'string', new StringValue( str_repeat('x', 401) 
), false, 'too long' ),
+                       array( 'string', new StringValue( ' Foo' ), false, 
'string with leading space' ),
+                       array( 'string', new StringValue( 'Foo ' ), false, 
'string with trailing space' ),
+
+                       //time
+                       array( 'time', 'Foo', false, 'TimeValue expected, 
string supplied' ),
+                       array( 'time', new NumberValue( 7 ), false, 'TimeValue 
expected' ),
+
+                       //time['calendar-model']
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, '' ), 
false, 'calendar: empty string should be invalid' ),
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://' . str_repeat('x', 256) ), false, 'calendar: too long' ),
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://acme.com/calendar' ), true, 'calendar: URL' ),
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, ' 
http://acme.com/calendar ' ), false, 'calendar: untrimmed' ),
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_DAY, ' 
javascript:alert(1)' ), false, 'calendar: bad URL' ),
+
+                       //precision to the second (currently not allowed)
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T11:22:33Z', 0, 0, 0, TimeValue::PRECISION_DAY, 
'http://acme.com/calendar' ), false, 'time given to the second' ),
+                       array( 'time', new TimeValue( 
'+0000000000002013-06-06T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_SECOND, 
'http://acme.com/calendar' ), false, 'precision: second' ),
+
+                       //time['time']
+                       //NOTE: The below will fail with a 
IllegalValueExcpetion once the TimeValue constructor enforces the time format.
+                       //      Once that is done, this test and the respective 
validator can and should both be removed.
+                       //array( 'string', new TimeValue( '2013-06-06 
11:22:33', 0, 0, 0, 0, 'http://acme.com/calendar' ), false, 'time: not ISO 
8601' ),
+
+                       //TODO: calendar must be an item reference
+                       //TODO: calendar must be from a list of configured 
values
+
+                       //globe-coordinate
+                       array( 'globe-coordinate', 'Foo', false, 
'GlobeCoordinateValue expected, string supplied' ),
+                       array( 'globe-coordinate', new NumberValue( 7 ), false, 
'GlobeCoordinateValue expected' ),
+
+                       //globe-coordinate[precision]
+                       array(
+                               'globe-coordinate',
+                               new GlobeCoordinateValue( $latLonValue, 1, 
'http://www.wikidata.org/entity/Q2' ),
+                               true,
+                               'integer precision is valid'
+                       ),
+                       array(
+                               'globe-coordinate',
+                               new GlobeCoordinateValue( $latLonValue, 0.2, 
'http://www.wikidata.org/entity/Q2' ),
+                               true,
+                               'float precision is valid'
+                       ),
+                       array(
+                               'globe-coordinate',
+                               new GlobeCoordinateValue( $latLonValue, null, 
'http://www.wikdiata.org/entity/Q2' ),
+                               false,
+                               'null precision is invalid'
+                       ),
+
+                       //globe-coordinate[globe]
+                       // FIXME: this is testing unimplemented behaviour? 
Probably broken...
+                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, '' ), false, 'globe: empty string should be invalid' ),
+                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, 'http://' . str_repeat('x', 256) ), false, 'globe: too long' ),
+                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, 'http://acme.com/globe' ), true, 'globe: URL' ),
+                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, ' http://acme.com/globe ' ), false, 'globe: untrimmed' ),
+                       array( 'globe-coordinate', new GlobeCoordinateValue( 
$latLonValue, 1, ' javascript:alert(1) ' ), false, 'globe: bad URL scheme' ),
+                       //TODO: globe must be an item reference
+                       //TODO: globe must be from a list of configured values
+
+                       // url
+                       array( 'url', 'Foo', false, 'StringValue expected, 
string supplied' ),
+                       array( 'url', new NumberValue( 7 ), false, 'StringValue 
expected' ),
+
+                       array( 'url', new StringValue( 'http://acme.com' ), 
true, 'Simple HTTP URL' ),
+                       array( 'url', new StringValue( 'https://acme.com' ), 
true, 'Simple HTTPS URL' ),
+                       array( 'url', new StringValue( 'ftp://acme.com' ), 
true, 'Simple FTP URL' ),
+                       array( 'url', new StringValue( 
'http://acme.com/foo/bar?some=stuff#fragment' ), true, 'Complex HTTP URL' ),
+
+                       // evil url
+                       array( 'url', new StringValue( '//bla' ), false, 
'Protocol-relative' ),
+                       array( 'url', new StringValue( '/bla/bla' ), false, 
'relative path' ),
+                       array( 'url', new StringValue( 'just stuff' ), false, 
'just words' ),
+                       array( 'url', new StringValue( 
'javascript:alert("evil")' ), false, 'JavaScript URL' ),
+                       array( 'url', new StringValue( 'http://' ), false, 'bad 
http URL' ),
+                       array( 'url', new StringValue( 'http://' . 
str_repeat('x', 505) ), false, 'URL too long' ),
+
+                       array( 'url', new StringValue( ' http://acme.com' ), 
false, 'URL with leading space' ),
+                       array( 'url', new StringValue( 'http://acme.com ' ), 
false, 'URL with trailing space' ),
+
+                       //quantity
+                       array( 'quantity', QuantityValue::newFromNumber( 5 ), 
true, 'Simple integer' ),
+                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'http://qudt.org/vocab/unit#Meter' ), true, 'Vocabulary URI' ),
+                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'https://www.wikidata.org/entity/Q11573' ), true, 'Wikidata URI' ),
+                       array( 'quantity', QuantityValue::newFromNumber( 5, '1' 
), true, '1 means unitless' ),
+                       array( 'quantity', QuantityValue::newFromNumber( 5, 
'kittens' ), false, 'Bad unit URI' ),
+                       array( 'quantity', QuantityValue::newFromNumber( 
'-11.234', '1', '-10', '-12' ), true, 'decimal strings' ),
+
+                       //monolingual text
+                       array( 'monolingualtext', new MonolingualTextValue( 
'en', 'text' ), true, 'Simple value' ),
+                       array( 'monolingualtext', new MonolingualTextValue( 
'grrr', 'text' ), false, 'Not a valid language' ),
+               );
+
+               if ( defined( 'WB_EXPERIMENTAL_FEATURES' ) && 
WB_EXPERIMENTAL_FEATURES ) {
+                       $cases = array_merge( $cases, array(
+
+                               // ....
+                       ) );
+               }
+
+               return $cases;
+       }
+
+       /**
+        * @dataProvider provideDataTypeValidation
+        */
+       public function testDataTypeValidation( $typeId, $value, $expected, 
$message ) {
+               $validatorsFactory = $this->newValidatorFactory();
+               $validators = $validatorsFactory->getValidators( $typeId );
+
+               $this->assertValidation( $expected, $validators, $value, 
$message );
+       }
+
+       protected function assertValidation( $expected, array $validators, 
$value, $message ) {
+
+               $result = Result::newSuccess();
+               foreach ( $validators as $validator ) {
+                       $result = $validator->validate( $value );
+
+                       if ( !$result->isValid() ) {
+                               break;
+                       }
+               }
+
+               if ( $expected ) {
+                       $errors = $result->getErrors();
+                       if ( !empty( $errors ) ) {
+                               $this->fail( $message . "\n" . 
$errors[0]->getText() );
+                       }
+
+                       $this->assertEquals( $expected, $result->isValid(), 
$message );
+               } else {
+                       $this->assertEquals( $expected, $result->isValid(), 
$message );
+               }
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php 
b/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
index 0dca3f2..d8fe04b 100644
--- a/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
+++ b/repo/tests/phpunit/includes/Validators/SnakValidatorTest.php
@@ -8,6 +8,7 @@
 use DataValues\StringValue;
 use DataValues\UnDeserializableValue;
 use DataValues\UnknownValue;
+use PHPUnit_Framework_TestCase;
 use Wikibase\DataModel\Claim\Claim;
 use Wikibase\DataModel\Entity\InMemoryDataTypeLookup;
 use Wikibase\DataModel\Entity\PropertyDataTypeLookup;
@@ -20,6 +21,7 @@
 use Wikibase\DataModel\Snak\Snak;
 use Wikibase\DataModel\Snak\SnakList;
 use Wikibase\DataModel\Statement\Statement;
+use Wikibase\Repo\DataTypeValidatorFactory;
 use Wikibase\Validators\SnakValidator;
 
 /**
@@ -32,7 +34,7 @@
  * @licence GNU GPL v2+
  * @author Daniel Kinzler
  */
-class SnakValidatorTest extends \PHPUnit_Framework_TestCase {
+class SnakValidatorTest extends PHPUnit_Framework_TestCase {
 
        /**
         * @var DataTypeFactory
@@ -44,6 +46,11 @@
         */
        protected $propertyDataTypeLookup;
 
+       /**
+        * @var DataTypeValidatorFactory
+        */
+       private $validatorFactory;
+
        protected function setUp() {
                parent::setUp();
 
@@ -52,8 +59,8 @@
                $lengthValidator = new TestValidator( '/^.{1,10}$/' );
 
                $this->dataTypeFactory = new DataTypeFactory();
-               $this->dataTypeFactory->registerDataType( new DataType( 
'numeric', 'string', array( $numericValidator, $lengthValidator ) ) );
-               $this->dataTypeFactory->registerDataType( new DataType( 
'alphabetic', 'string', array( $alphabeticValidator, $lengthValidator ) ) );
+               $this->dataTypeFactory->registerDataType( new DataType( 
'numeric', 'string', array() ) );
+               $this->dataTypeFactory->registerDataType( new DataType( 
'alphabetic', 'string', array() ) );
 
                $p1 = new PropertyId( 'p1' );
                $p2 = new PropertyId( 'p2' );
@@ -61,6 +68,13 @@
                $this->propertyDataTypeLookup = new InMemoryDataTypeLookup();
                $this->propertyDataTypeLookup->setDataTypeForProperty( $p1, 
'numeric' );
                $this->propertyDataTypeLookup->setDataTypeForProperty( $p2, 
'alphabetic' );
+
+               $this->validatorFactory = $this->getMock( 
'Wikibase\Repo\DataTypeValidatorFactory' );
+               $this->validatorFactory->expects( $this->any() )
+                       ->method( 'getValidators' )
+                       ->will( $this->returnCallback( function( $dataTypeId ) 
use( $numericValidator, $alphabeticValidator, $lengthValidator ) {
+                                       return array( $dataTypeId === 'numeric' 
? $numericValidator : $alphabeticValidator, $lengthValidator );
+                               } ) );
        }
 
        public function provideValidateClaimSnaks() {
@@ -112,7 +126,7 @@
         * @dataProvider provideValidateClaimSnaks
         */
        public function testValidateClaimSnaks( Claim $claim, $description, 
$expectedValid = true ) {
-               $validator = new SnakValidator( $this->propertyDataTypeLookup, 
$this->dataTypeFactory );
+               $validator = $this->getSnakValidator();
 
                $result = $validator->validateClaimSnaks( $claim );
 
@@ -155,7 +169,7 @@
         * @dataProvider provideValidateReferences
         */
        public function testValidateReferences( ReferenceList $references, 
$description, $expectedValid = true ) {
-               $validator = new SnakValidator( $this->propertyDataTypeLookup, 
$this->dataTypeFactory );
+               $validator = $this->getSnakValidator();
 
                $result = $validator->validateReferences( $references );
 
@@ -189,11 +203,17 @@
                return $cases;
        }
 
+       private function getSnakValidator() {
+               return new SnakValidator(
+                       $this->propertyDataTypeLookup, $this->dataTypeFactory, 
$this->validatorFactory
+               );
+       }
+
        /**
         * @dataProvider provideValidateReference
         */
        public function testValidateReference( Reference $reference, 
$description, $expectedValid = true ) {
-               $validator = new SnakValidator( $this->propertyDataTypeLookup, 
$this->dataTypeFactory );
+               $validator = $this->getSnakValidator();
 
                $result = $validator->validateReference( $reference );
 
@@ -241,7 +261,7 @@
         * @dataProvider provideValidate
         */
        public function testValidate( Snak $snak, $description, $expectedValid 
= true ) {
-               $validator = new SnakValidator( $this->propertyDataTypeLookup, 
$this->dataTypeFactory );
+               $validator = $this->getSnakValidator();
 
                $result = $validator->validate( $snak );
 
@@ -264,7 +284,7 @@
         * @dataProvider provideValidateDataValue
         */
        public function testValidateDataValue( DataValue $dataValue, 
$dataTypeId, $propertyName, $description, $expectedValid = true ) {
-               $validator = new SnakValidator( $this->propertyDataTypeLookup, 
$this->dataTypeFactory );
+               $validator = $this->getSnakValidator();
 
                $result = $validator->validateDataValue( $dataValue, 
$dataTypeId, $propertyName );
 

-- 
To view, visit https://gerrit.wikimedia.org/r/189731
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I6fb14c135315c7c064ccf49c019670dfab68c857
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Adrian Lang <[email protected]>
Gerrit-Reviewer: Adrian Lang <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to