-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge "REST: Create validation and deserialization for SetPropertyDes…
…cription"
- Loading branch information
Showing
6 changed files
with
480 additions
and
0 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
repo/rest-api/src/Application/UseCaseRequestValidation/PropertyDescriptionEditRequest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace Wikibase\Repo\RestApi\Application\UseCaseRequestValidation; | ||
|
||
/** | ||
* @license GPL-2.0-or-later | ||
*/ | ||
interface PropertyDescriptionEditRequest extends PropertyIdRequest, LanguageCodeRequest { | ||
public function getDescription(): string; | ||
} |
71 changes: 71 additions & 0 deletions
71
...ication/UseCaseRequestValidation/PropertyDescriptionEditRequestValidatingDeserializer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace Wikibase\Repo\RestApi\Application\UseCaseRequestValidation; | ||
|
||
use LogicException; | ||
use Wikibase\DataModel\Entity\NumericPropertyId; | ||
use Wikibase\DataModel\Term\Term; | ||
use Wikibase\Repo\RestApi\Application\UseCases\UseCaseError; | ||
use Wikibase\Repo\RestApi\Application\Validation\PropertyDescriptionValidator; | ||
|
||
/** | ||
* @license GPL-2.0-or-later | ||
*/ | ||
class PropertyDescriptionEditRequestValidatingDeserializer { | ||
|
||
private PropertyDescriptionValidator $validator; | ||
|
||
public function __construct( PropertyDescriptionValidator $validator ) { | ||
$this->validator = $validator; | ||
} | ||
|
||
/** | ||
* @throws UseCaseError | ||
*/ | ||
public function validateAndDeserialize( PropertyDescriptionEditRequest $request ): Term { | ||
$language = $request->getLanguageCode(); | ||
$validationError = $this->validator->validate( | ||
new NumericPropertyId( $request->getPropertyId() ), | ||
$language, | ||
$request->getDescription() | ||
); | ||
|
||
if ( $validationError ) { | ||
$errorCode = $validationError->getCode(); | ||
$context = $validationError->getContext(); | ||
switch ( $errorCode ) { | ||
case PropertyDescriptionValidator::CODE_INVALID: | ||
throw new UseCaseError( | ||
UseCaseError::INVALID_DESCRIPTION, | ||
"Not a valid description: {$context[PropertyDescriptionValidator::CONTEXT_DESCRIPTION]}" | ||
); | ||
case PropertyDescriptionValidator::CODE_EMPTY: | ||
throw new UseCaseError( | ||
UseCaseError::DESCRIPTION_EMPTY, | ||
'Description must not be empty' | ||
); | ||
case PropertyDescriptionValidator::CODE_TOO_LONG: | ||
$limit = $context[PropertyDescriptionValidator::CONTEXT_LIMIT]; | ||
throw new UseCaseError( | ||
UseCaseError::DESCRIPTION_TOO_LONG, | ||
"Description must be no more than $limit characters long", | ||
[ | ||
UseCaseError::CONTEXT_VALUE => $context[PropertyDescriptionValidator::CONTEXT_DESCRIPTION], | ||
UseCaseError::CONTEXT_CHARACTER_LIMIT => $limit, | ||
] | ||
); | ||
case PropertyDescriptionValidator::CODE_LABEL_DESCRIPTION_EQUAL: | ||
throw new UseCaseError( | ||
UseCaseError::LABEL_DESCRIPTION_SAME_VALUE, | ||
"Label and description for language code '$language' can not have the same value", | ||
[ UseCaseError::CONTEXT_LANGUAGE => $context[PropertyDescriptionValidator::CONTEXT_LANGUAGE] ] | ||
); | ||
default: | ||
throw new LogicException( "Unexpected validation error code: $errorCode" ); | ||
} | ||
} | ||
|
||
return new Term( $language, $request->getDescription() ); | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
repo/rest-api/src/Application/Validation/PropertyDescriptionValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace Wikibase\Repo\RestApi\Application\Validation; | ||
|
||
use Wikibase\DataModel\Entity\PropertyId; | ||
|
||
/** | ||
* @license GPL-2.0-or-later | ||
*/ | ||
interface PropertyDescriptionValidator { | ||
|
||
public const CODE_INVALID = 'invalid-description'; | ||
public const CODE_EMPTY = 'description-empty'; | ||
public const CODE_TOO_LONG = 'description-too-long'; | ||
public const CODE_LABEL_DESCRIPTION_EQUAL = 'label-description-equal'; | ||
|
||
public const CONTEXT_LIMIT = 'character-limit'; | ||
public const CONTEXT_LANGUAGE = 'language'; | ||
public const CONTEXT_DESCRIPTION = 'description'; | ||
|
||
public function validate( PropertyId $propertyId, string $language, string $description ): ?ValidationError; | ||
|
||
} |
91 changes: 91 additions & 0 deletions
91
repo/rest-api/src/Infrastructure/WikibaseRepoPropertyDescriptionValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace Wikibase\Repo\RestApi\Infrastructure; | ||
|
||
use Wikibase\DataModel\Entity\PropertyId; | ||
use Wikibase\Repo\RestApi\Application\Validation\PropertyDescriptionValidator; | ||
use Wikibase\Repo\RestApi\Application\Validation\ValidationError; | ||
use Wikibase\Repo\RestApi\Domain\Services\PropertyRetriever; | ||
use Wikibase\Repo\Validators\TermValidatorFactory; | ||
|
||
/** | ||
* @license GPL-2.0-or-later | ||
*/ | ||
class WikibaseRepoPropertyDescriptionValidator implements PropertyDescriptionValidator { | ||
|
||
private TermValidatorFactory $termValidatorFactory; | ||
private PropertyRetriever $propertyRetriever; | ||
|
||
public function __construct( | ||
TermValidatorFactory $termValidatorFactory, | ||
PropertyRetriever $propertyRetriever | ||
) { | ||
$this->termValidatorFactory = $termValidatorFactory; | ||
$this->propertyRetriever = $propertyRetriever; | ||
} | ||
|
||
public function validate( PropertyId $propertyId, string $language, string $description ): ?ValidationError { | ||
return $this->validateDescription( $description ) | ||
?? $this->validateProperty( $propertyId, $language, $description ); | ||
} | ||
|
||
private function validateDescription( string $description ): ?ValidationError { | ||
$result = $this->termValidatorFactory | ||
->getDescriptionValidator() | ||
->validate( $description ); | ||
if ( !$result->isValid() ) { | ||
$error = $result->getErrors()[0]; | ||
switch ( $error->getCode() ) { | ||
case 'description-too-short': | ||
return new ValidationError( self::CODE_EMPTY ); | ||
case 'description-too-long': | ||
return new ValidationError( | ||
self::CODE_TOO_LONG, | ||
[ | ||
self::CONTEXT_DESCRIPTION => $description, | ||
self::CONTEXT_LIMIT => $error->getParameters()[0], | ||
] | ||
); | ||
default: | ||
return new ValidationError( | ||
self::CODE_INVALID, | ||
[ self::CONTEXT_DESCRIPTION => $description ] | ||
); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function validateProperty( PropertyId $propertyId, string $language, string $description ): ?ValidationError { | ||
$property = $this->propertyRetriever->getProperty( $propertyId ); | ||
|
||
// skip if Property does not exist | ||
if ( $property === null ) { | ||
return null; | ||
} | ||
|
||
// skip if description is unchanged | ||
if ( $property->getDescriptions()->hasTermForLanguage( $language ) && | ||
$property->getDescriptions()->getByLanguage( $language )->getText() === $description | ||
) { | ||
return null; | ||
} | ||
|
||
// skip if Property does not have a label | ||
if ( !$property->getLabels()->hasTermForLanguage( $language ) ) { | ||
return null; | ||
} | ||
|
||
$label = $property->getLabels()->getByLanguage( $language )->getText(); | ||
if ( $label === $description ) { | ||
return new ValidationError( | ||
self::CODE_LABEL_DESCRIPTION_EQUAL, | ||
[ self::CONTEXT_LANGUAGE => $language ] | ||
); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
} |
111 changes: 111 additions & 0 deletions
111
...ion/UseCaseRequestValidation/PropertyDescriptionEditRequestValidatingDeserializerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace Wikibase\Repo\Tests\RestApi\Application\UseCaseRequestValidation; | ||
|
||
use Generator; | ||
use PHPUnit\Framework\TestCase; | ||
use Wikibase\DataModel\Term\Term; | ||
use Wikibase\Repo\RestApi\Application\UseCaseRequestValidation\PropertyDescriptionEditRequest; | ||
use Wikibase\Repo\RestApi\Application\UseCaseRequestValidation\PropertyDescriptionEditRequestValidatingDeserializer; | ||
use Wikibase\Repo\RestApi\Application\UseCases\UseCaseError; | ||
use Wikibase\Repo\RestApi\Application\Validation\PropertyDescriptionValidator; | ||
use Wikibase\Repo\RestApi\Application\Validation\ValidationError; | ||
|
||
/** | ||
* @covers \Wikibase\Repo\RestApi\Application\UseCaseRequestValidation\PropertyDescriptionEditRequestValidatingDeserializer | ||
* | ||
* @group Wikibase | ||
* | ||
* @license GPL-2.0-or-later | ||
*/ | ||
class PropertyDescriptionEditRequestValidatingDeserializerTest extends TestCase { | ||
|
||
public function testGivenValidRequest_returnsDescription(): void { | ||
$request = $this->createStub( PropertyDescriptionEditRequest::class ); | ||
$request->method( 'getPropertyId' )->willReturn( 'P123' ); | ||
$request->method( 'getLanguageCode' )->willReturn( 'en' ); | ||
$request->method( 'getDescription' )->willReturn( 'that class of which this subject is a particular example and member' ); | ||
|
||
$this->assertEquals( | ||
new Term( 'en', 'that class of which this subject is a particular example and member' ), | ||
( new PropertyDescriptionEditRequestValidatingDeserializer( $this->createStub( PropertyDescriptionValidator::class ) ) ) | ||
->validateAndDeserialize( $request ) | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider invalidDescriptionProvider | ||
*/ | ||
public function testWithInvalidDescription( | ||
ValidationError $validationError, | ||
string $expectedErrorCode, | ||
string $expectedErrorMessage, | ||
array $expectedContext = [] | ||
): void { | ||
$request = $this->createStub( PropertyDescriptionEditRequest::class ); | ||
$request->method( 'getPropertyId' )->willReturn( 'P123' ); | ||
$request->method( 'getLanguageCode' )->willReturn( 'en' ); | ||
$request->method( 'getDescription' )->willReturn( 'my description' ); | ||
|
||
$propertyDescriptionValidator = $this->createStub( PropertyDescriptionValidator::class ); | ||
$propertyDescriptionValidator->method( 'validate' )->willReturn( $validationError ); | ||
|
||
try { | ||
( new PropertyDescriptionEditRequestValidatingDeserializer( $propertyDescriptionValidator ) ) | ||
->validateAndDeserialize( $request ); | ||
$this->fail( 'this should not be reached' ); | ||
} catch ( UseCaseError $error ) { | ||
$this->assertSame( $expectedErrorCode, $error->getErrorCode() ); | ||
$this->assertSame( $expectedErrorMessage, $error->getErrorMessage() ); | ||
$this->assertSame( $expectedContext, $error->getErrorContext() ); | ||
} | ||
} | ||
|
||
public static function invalidDescriptionProvider(): Generator { | ||
yield 'description empty' => [ | ||
new ValidationError( PropertyDescriptionValidator::CODE_EMPTY ), | ||
UseCaseError::DESCRIPTION_EMPTY, | ||
'Description must not be empty', | ||
]; | ||
|
||
$description = 'description that is too long...'; | ||
$limit = 40; | ||
yield 'description too long' => [ | ||
new ValidationError( | ||
PropertyDescriptionValidator::CODE_TOO_LONG, | ||
[ | ||
PropertyDescriptionValidator::CONTEXT_DESCRIPTION => $description, | ||
PropertyDescriptionValidator::CONTEXT_LIMIT => $limit, | ||
] | ||
), | ||
UseCaseError::DESCRIPTION_TOO_LONG, | ||
'Description must be no more than 40 characters long', | ||
[ | ||
UseCaseError::CONTEXT_VALUE => $description, | ||
UseCaseError::CONTEXT_CHARACTER_LIMIT => $limit, | ||
], | ||
]; | ||
|
||
$description = "tab characters \t not allowed"; | ||
yield 'invalid description' => [ | ||
new ValidationError( | ||
PropertyDescriptionValidator::CODE_INVALID, | ||
[ PropertyDescriptionValidator::CONTEXT_DESCRIPTION => $description ], | ||
), | ||
UseCaseError::INVALID_DESCRIPTION, | ||
"Not a valid description: $description", | ||
]; | ||
|
||
$language = 'en'; | ||
yield 'label and description are equal' => [ | ||
new ValidationError( | ||
PropertyDescriptionValidator::CODE_LABEL_DESCRIPTION_EQUAL, | ||
[ PropertyDescriptionValidator::CONTEXT_LANGUAGE => $language ], | ||
), | ||
UseCaseError::LABEL_DESCRIPTION_SAME_VALUE, | ||
"Label and description for language code '$language' can not have the same value", | ||
[ UseCaseError::CONTEXT_LANGUAGE => $language ], | ||
]; | ||
} | ||
|
||
} |
Oops, something went wrong.