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