aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--autoload.php1
-rw-r--r--includes/ParamValidator/TypeDef/ArrayDef.php33
-rw-r--r--includes/Rest/Validator/Validator.php5
-rw-r--r--includes/libs/ParamValidator/ParamValidator.php2
-rw-r--r--includes/libs/ParamValidator/TypeDef.php16
-rw-r--r--languages/i18n/en.json1
-rw-r--r--languages/i18n/qqq.json1
-rw-r--r--tests/phpunit/unit/includes/ParamValidator/TypeDef/ArrayDefTest.php27
-rw-r--r--tests/phpunit/unit/includes/Rest/Validator/ValidatorTest.php60
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' => [] ]
+ ];
}
/**