diff options
-rw-r--r-- | autoload.php | 1 | ||||
-rw-r--r-- | includes/ParamValidator/TypeDef/ArrayDef.php | 33 | ||||
-rw-r--r-- | includes/Rest/Validator/Validator.php | 5 | ||||
-rw-r--r-- | includes/libs/ParamValidator/ParamValidator.php | 2 | ||||
-rw-r--r-- | includes/libs/ParamValidator/TypeDef.php | 16 | ||||
-rw-r--r-- | languages/i18n/en.json | 1 | ||||
-rw-r--r-- | languages/i18n/qqq.json | 1 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/ParamValidator/TypeDef/ArrayDefTest.php | 27 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php | 60 |
9 files changed, 142 insertions, 4 deletions
diff --git a/autoload.php b/autoload.php index ab2543b4c706..50bfb4ce109b 100644 --- a/autoload.php +++ b/autoload.php @@ -1788,6 +1788,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Pager\\TablePager' => __DIR__ . '/includes/pager/TablePager.php', 'MediaWiki\\Pager\\UploadStashPager' => __DIR__ . '/includes/specials/pagers/UploadStashPager.php', 'MediaWiki\\Pager\\UsersPager' => __DIR__ . '/includes/specials/pagers/UsersPager.php', + 'MediaWiki\\ParamValidator\\TypeDef\\ArrayDef' => __DIR__ . '/includes/ParamValidator/TypeDef/ArrayDef.php', 'MediaWiki\\ParamValidator\\TypeDef\\NamespaceDef' => __DIR__ . '/includes/ParamValidator/TypeDef/NamespaceDef.php', 'MediaWiki\\ParamValidator\\TypeDef\\TagsDef' => __DIR__ . '/includes/ParamValidator/TypeDef/TagsDef.php', 'MediaWiki\\ParamValidator\\TypeDef\\TitleDef' => __DIR__ . '/includes/ParamValidator/TypeDef/TitleDef.php', diff --git a/includes/ParamValidator/TypeDef/ArrayDef.php b/includes/ParamValidator/TypeDef/ArrayDef.php new file mode 100644 index 000000000000..36f40298c773 --- /dev/null +++ b/includes/ParamValidator/TypeDef/ArrayDef.php @@ -0,0 +1,33 @@ +<?php + +namespace MediaWiki\ParamValidator\TypeDef; + +use Wikimedia\ParamValidator\TypeDef; + +/** + * Type definition for array structures, typically + * used for validating JSON request bodies. + * + * Failure codes: + * - 'notarray': The value is not an array. + * + * @todo implement validation based on a JSON schema + * + * @since 1.42 + */ +class ArrayDef extends TypeDef { + + public function supportsArrays() { + return true; + } + + public function validate( $name, $value, array $settings, array $options ) { + if ( !is_array( $value ) ) { + // Message used: paramvalidator-notarray + $this->failure( 'notarray', $name, $value, $settings, $options ); + } + + return $value; + } + +} diff --git a/includes/Rest/Validator/Validator.php b/includes/Rest/Validator/Validator.php index f4f47b22f620..bc82b8a00173 100644 --- a/includes/Rest/Validator/Validator.php +++ b/includes/Rest/Validator/Validator.php @@ -2,6 +2,7 @@ namespace MediaWiki\Rest\Validator; +use MediaWiki\ParamValidator\TypeDef\ArrayDef; use MediaWiki\ParamValidator\TypeDef\TitleDef; use MediaWiki\ParamValidator\TypeDef\UserDef; use MediaWiki\Permissions\Authority; @@ -84,6 +85,9 @@ class Validator { 'class' => UserDef::class, 'services' => [ 'UserIdentityLookup', 'TitleParser', 'UserNameUtils' ] ], + 'array' => [ + 'class' => ArrayDef::class, + ], ]; /** @var string[] HTTP request methods that we expect never to have a payload */ @@ -306,6 +310,7 @@ class Validator { 'expiry-param' => [ 'type' => 'string', 'format' => 'mw-expiry' ], 'title-param' => [ 'type' => 'string', 'format' => 'mw-title' ], 'user-param' => [ 'type' => 'string', 'format' => 'mw-user' ], + 'array-param' => [ 'type' => 'object' ], ]; /** diff --git a/includes/libs/ParamValidator/ParamValidator.php b/includes/libs/ParamValidator/ParamValidator.php index 59a6f324cce8..a6269b4d0266 100644 --- a/includes/libs/ParamValidator/ParamValidator.php +++ b/includes/libs/ParamValidator/ParamValidator.php @@ -598,7 +598,7 @@ class ParamValidator { // T326764: If the type of the actual param value is different from // the type that is defined via getParamSettings(), throw an exception // because this is a type to value mismatch. - if ( is_array( $value ) ) { + if ( is_array( $value ) && !$typeDef->supportsArrays() ) { throw new ValidationException( DataMessageValue::new( 'paramvalidator-notmulti', [], 'badvalue' ) ->plaintextParams( $name, gettype( $value ) ), diff --git a/includes/libs/ParamValidator/TypeDef.php b/includes/libs/ParamValidator/TypeDef.php index 29214d0bac20..d13649881002 100644 --- a/includes/libs/ParamValidator/TypeDef.php +++ b/includes/libs/ParamValidator/TypeDef.php @@ -12,9 +12,9 @@ use Wikimedia\Message\MessageValue; * passed to ParamValidator::getValue(), ParamValidator::validateValue(), and the like * and is intended for communication of non-global state to the Callbacks. * - * @stable to extend * @since 1.34 - * @unstable + * @unstable for use in extensions. Intended to become stable to extend, at + * least for use in MediaWiki, which already defines some subclasses. */ abstract class TypeDef { @@ -31,6 +31,18 @@ abstract class TypeDef { } /** + * Whether the value may be an array. + * Note that this is different from multi-value. + * This should only return true if each value can be an array. + * @since 1.41 + * @stable to override + * @return bool + */ + public function supportsArrays() { + return false; + } + + /** * Record a failure message * * Depending on `$fatal`, this will either throw a ValidationException or diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 2ad2e2db972a..0f33f4b2f85a 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -4602,6 +4602,7 @@ "paramvalidator-help-type-user-subtype-id": "user ID (e.g. \"#12345\")", "paramvalidator-badtitle": "Invalid value \"$2\" for title parameter <var>$1</var>: not a valid title string.", "paramvalidator-missingtitle": "Invalid value \"$2\" for title parameter <var>$1</var>: the page does not exist.", + "paramvalidator-notarray": "Invalid value for parameter <var>$1</var>: array expected.", "paramvalidator-help-type-title": "Type: page title", "paramvalidator-help-type-title-must-exist": "Only accepts pages that exist.", "paramvalidator-help-type-title-no-must-exist": "Accepts non-existent pages.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 924e1fd5e6cf..3b99067bfc14 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -4860,6 +4860,7 @@ "paramvalidator-help-type-user-subtype-id": "Used with {{msg-mw|paramvalidator-help-type-user}} to indicate that users may be specified by user ID number, prefixed with a \"#\" character.", "paramvalidator-badtitle": "Error in API parameter validation. Parameters:\n* $1 - Parameter name.\n* $2 - Parameter value.", "paramvalidator-missingtitle": "Error in API parameter validation. Parameters:\n* $1 - Parameter name.\n* $2 - Parameter value.", + "paramvalidator-notarray": "Error in API parameter and request body validation. Parameters:\n* $1 - Parameter name.", "paramvalidator-help-type-title": "Used to indicate that a parameter is a page title or list of page titles.", "paramvalidator-help-type-title-must-exist": "Used with {{msg-mw|paramvalidator-help-type-title}} to indicate that only titles referring to existing pages are valid.", "paramvalidator-help-type-title-no-must-exist": "Used with {{msg-mw|paramvalidator-help-type-title}} to indicate that titles referring to non-existent pages are also valid.", diff --git a/tests/phpunit/unit/includes/ParamValidator/TypeDef/ArrayDefTest.php b/tests/phpunit/unit/includes/ParamValidator/TypeDef/ArrayDefTest.php new file mode 100644 index 000000000000..46d32f7ff4be --- /dev/null +++ b/tests/phpunit/unit/includes/ParamValidator/TypeDef/ArrayDefTest.php @@ -0,0 +1,27 @@ +<?php + +namespace MediaWiki\Tests\ParamValidator\TypeDef; + +use MediaWiki\ParamValidator\TypeDef\ArrayDef; +use Wikimedia\ParamValidator\SimpleCallbacks; + +/** + * @covers \MediaWiki\ParamValidator\TypeDef\ArrayDef + */ +class ArrayDefTest extends TypeDefUnitTestCase { + + protected function getInstance( SimpleCallbacks $callbacks, array $options ) { + return new ArrayDef( $callbacks ); + } + + public function provideValidate() { + yield 'assoc array' => [ [ 'x' => 1 ], [ 'x' => 1 ], ]; + yield 'indexed array' => [ [ 'x' ], [ 'x' ], ]; + yield 'array' => [ [], [], ]; + + $notComplex = self::getValidationException( 'notarray', null ); + yield 'null' => [ null, $notComplex ]; + yield 'string' => [ 'foo', $notComplex ]; + yield 'zero' => [ 0, $notComplex ]; + } +} diff --git a/tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php b/tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php index 04599483336e..996d9c922046 100644 --- a/tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php +++ b/tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php @@ -195,6 +195,24 @@ class ValidatorTest extends MediaWikiUnitTestCase { ] ]; + yield 'array parameter' => [ + [ + ParamValidator::PARAM_TYPE => 'array', + Validator::PARAM_SOURCE => 'body', + Validator::PARAM_DESCRIPTION => 'just a test', + ParamValidator::PARAM_REQUIRED => true, + ], + [ + 'schema' => [ + 'type' => 'object', + ], + 'required' => true, + 'description' => 'just a test', + 'in' => 'body', + 'name' => 'test', + ] + ]; + yield 'enum parameter' => [ [ ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ], @@ -350,7 +368,7 @@ class ValidatorTest extends MediaWikiUnitTestCase { $paramNames = [ "path" => "pathParams", "query" => "queryParams", - "post" => "postParams", + "post" => "postParams" ]; foreach ( $sources as $source ) { $cases = self::generateParamValidationCases( $source, $paramNames[ $source ] ); @@ -416,6 +434,46 @@ class ValidatorTest extends MediaWikiUnitTestCase { new RequestData( [ 'pathParams' => [ 'foo' => 'test' ] ] ), [] // The parameter from an unknown source should be ignored. ]; + + yield "valid complex value" => [ + [ + 'foo' => [ + ParamValidator::PARAM_TYPE => 'array', + Validator::PARAM_SOURCE => 'body', + ParamValidator::PARAM_REQUIRED => true + ] + ], + new RequestData( [ 'parsedBody' => [ + 'foo' => [ 'x' => 1 ] // this is a complex value + ] ] ), + [ 'foo' => [ 'x' => 1 ] ] + ]; + + yield "invalid complex value" => [ + [ + 'foo' => [ + ParamValidator::PARAM_TYPE => 'array', + Validator::PARAM_SOURCE => 'body', + ParamValidator::PARAM_REQUIRED => true + ] + ], + new RequestData( [ 'parsedBody' => [ + 'foo' => 'xyzzy' // not a complex value + ] ] ), + new LocalizedHttpException( new MessageValue( 'paramvalidator-notarray' ), 400 ) + ]; + + yield "default complex value" => [ + [ + 'foo' => [ + ParamValidator::PARAM_TYPE => 'array', + Validator::PARAM_SOURCE => 'body', + ParamValidator::PARAM_DEFAULT => [] + ] + ], + new RequestData( [] ), + [ 'foo' => [] ] + ]; } /** |