mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/TemplateData
synced 2024-09-23 10:21:45 +00:00
Implement new "maps" property in Root
Add "maps" as an allowed root value to template data JSON. Maps allow applications to map equivalencies between their application-specific parameters and valid template parameters. Each Map has arbitrary keys (from the consuming application) that are mapped to one or more template parameters. * Added root.maps to TemplateDataBlob * Validates that values are strings or arrays * Validates that values are valid params * Added templatedata-invalid-param to i18n/en.json * Richer error message for when invalid value is a param * Fixed existing tests to work with new "maps" property * Added two new tests for maps * Test for invalid value (!string or !array) * Test for invalid parameter * Added specification for root.maps property and Map object Change-Id: I3bf5e002ad6c1632e02c4c2e393b244c51f77177
This commit is contained in:
parent
e03a12b393
commit
d6cac31082
|
@ -6,7 +6,7 @@
|
|||
<dt>This version</dt>
|
||||
<dd><a href="https://git.wikimedia.org/blob/mediawiki%2Fextensions%2FTemplateData/master/Specification.md">https://git.wikimedia.org/blob/mediawiki%2Fextensions%2FTemplateData/master/Specification.md</a></dd>
|
||||
<dt>Editors</dt>
|
||||
<dd>Timo Tijhof, Trevor Parscal, James D. Forrester</dd>
|
||||
<dd>Timo Tijhof, Trevor Parscal, James D. Forrester, Marielle Volz</dd>
|
||||
</dl>
|
||||
|
||||
***
|
||||
|
@ -83,6 +83,20 @@ Authors MUST ensure that the `sets` object contains only `Set` objects. Authors
|
|||
|
||||
A Consumer MAY encourage users to interact with parameters in a `Set` together (e.g. add, edit, or remove those parameters at once).
|
||||
|
||||
#### 3.1.5 `maps`
|
||||
* Required
|
||||
* Value: `Object`
|
||||
|
||||
An object describing which parameter(s) specific Consumers SHOULD use for some purpose.
|
||||
|
||||
The `maps` property contains key–value pairs where the key identifies a given Consumer, and the value a `Map` object.
|
||||
|
||||
Consumers are NOT REQUIRED to have a corresponding `Map` object.
|
||||
|
||||
Consumers that look for a `Map` SHOULD publicly document their identifier key.
|
||||
|
||||
Authors MUST ensure that the `maps` object contains only `Map` objects. Authors MAY include a parameter in multiple `Map` objects. Authors are NOT REQUIRED to reference each parameter in at least one `Map` object.
|
||||
|
||||
### 3.2 Param
|
||||
* Value: `Object`
|
||||
|
||||
|
@ -231,6 +245,13 @@ One of the following:
|
|||
A free-form string (no wikitext) in the content-language of the wiki, or,
|
||||
an object containing those strings keyed by language code.
|
||||
|
||||
### 3.6 Map
|
||||
* Value: `Object`
|
||||
|
||||
Each key in a `Map` object can be arbitrary. The value must match a parameter name, a list of parameter names, or list containing lists of parameter names.
|
||||
|
||||
The key corresponds to the name of a Consumer variable that relates to the specified parameter(s).
|
||||
|
||||
## 4 Examples
|
||||
|
||||
### 4.1 The "Unsigned" template
|
||||
|
@ -276,7 +297,19 @@ an object containing those strings keyed by language code.
|
|||
"label": "Date",
|
||||
"params": ["year", "month", "day"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"maps": {
|
||||
"ExampleConsumer": {
|
||||
"foo": "user",
|
||||
"bar": ["year", "month", "day"],
|
||||
"quux": [
|
||||
"date",
|
||||
["day", "month"],
|
||||
["month", "year"],
|
||||
"year"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ class TemplateDataBlob {
|
|||
* @return TemplateDataBlob
|
||||
*/
|
||||
public static function newFromJSON( $json ) {
|
||||
|
||||
$tdb = new self( json_decode( $json ) );
|
||||
|
||||
$status = $tdb->parse();
|
||||
|
@ -46,6 +47,7 @@ class TemplateDataBlob {
|
|||
$tdb->data->description = null;
|
||||
$tdb->data->params = new stdClass();
|
||||
$tdb->data->sets = array();
|
||||
$tdb->data->maps = new stdClass();
|
||||
}
|
||||
$tdb->status = $status;
|
||||
return $tdb;
|
||||
|
@ -79,6 +81,7 @@ class TemplateDataBlob {
|
|||
'params',
|
||||
'paramOrder',
|
||||
'sets',
|
||||
'maps',
|
||||
);
|
||||
|
||||
static $paramKeys = array(
|
||||
|
@ -424,6 +427,77 @@ class TemplateDataBlob {
|
|||
}
|
||||
}
|
||||
|
||||
// Root.maps
|
||||
if ( isset( $data->maps ) ) {
|
||||
if ( !is_object( $data->maps ) ) {
|
||||
return Status::newFatal( 'templatedata-invalid-type', 'maps', 'object' );
|
||||
}
|
||||
} else {
|
||||
$data->maps = new stdClass();
|
||||
}
|
||||
|
||||
foreach ( $data->maps as $consumerId => $map ) {
|
||||
if ( !is_object( $map ) ) {
|
||||
return Status::newFatal( 'templatedata-invalid-type', 'maps', 'object' );
|
||||
}
|
||||
|
||||
foreach ( $map as $key => $value ) {
|
||||
// Key is not validated as this is used by a third-party application
|
||||
// Value must be 2d array of parameter names, 1d array of parameter names, or valid
|
||||
// parameter name
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key2 => $value2 ) {
|
||||
if ( is_array( $value2 ) ) {
|
||||
foreach ( $value2 as $key3 => $value3 ) {
|
||||
if ( !is_string( $value3 ) ) {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-type',
|
||||
"maps.{$consumerId}.{$key}[$key2][$key3]",
|
||||
'string'
|
||||
);
|
||||
}
|
||||
if ( !isset( $data->params->$value3 ) ) {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-param',
|
||||
$value3,
|
||||
"maps.{$consumerId}.{$key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $value2 ) ){
|
||||
if ( !isset( $data->params->$value2 ) ) {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-param',
|
||||
$value2,
|
||||
"maps.{$consumerId}.{$key}"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-type',
|
||||
"maps.{$consumerId}.{$key}[$key2]",
|
||||
'string|array'
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $value ) ) {
|
||||
if ( !isset( $data->params->$value ) ) {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-param',
|
||||
$value,
|
||||
"maps.{$consumerId}.{$key}"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Status::newFatal(
|
||||
'templatedata-invalid-type',
|
||||
"maps.{$consumerId}.{$key}",
|
||||
'string|array'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$length = strlen( $this->getJSONForDatabase() );
|
||||
if ( $length > self::MAX_LENGTH ) {
|
||||
return Status::newFatal( 'templatedata-invalid-length', $length, self::MAX_LENGTH );
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"templatedata-invalid-empty-array": "Property \"$1\" must have at least one value in its array.",
|
||||
"templatedata-invalid-length": "Data too large to save ({{formatnum:$1}} {{PLURAL:$1|byte|bytes}}, {{PLURAL:$2|limit is}} {{formatnum:$2}})",
|
||||
"templatedata-invalid-missing": "Required property \"$1\" not found.",
|
||||
"templatedata-invalid-param": "Invalid parameter \"$1\" for property \"$2\".",
|
||||
"templatedata-invalid-parse": "Syntax error in JSON.",
|
||||
"templatedata-invalid-type": "Property \"$1\" is expected to be of type \"$2\".",
|
||||
"templatedata-invalid-unknown": "Unexpected property \"$1\".",
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"templatedata-invalid-empty-array": "Error message when a property that must be non-empty is empty. Parameters:\n* $1 - property name (\"paramOrder\" or \"sets.{$setNr}.params\")",
|
||||
"templatedata-invalid-length": "Error message when generated JSON's length exceed database limits.\n* $1 - length of generated JSON\n* $2 - maximal allowed length",
|
||||
"templatedata-invalid-missing": "Error message when a required property is not found.\n* $1 - name of property. e.g. \"params\"\n* $2 - type of property (Unused)",
|
||||
"templatedata-invalid-param": "Error message for when the supplied parameter is invalid.\n* $1 - name of parameter. e.g. \"username\"\n* $2 - name of property. e.g. maps property \"applicationUser\"",
|
||||
"templatedata-invalid-parse": "Error message when there is a syntax error in JSON.",
|
||||
"templatedata-invalid-type": "Error message when a property is of the wrong type.\n* $1 - name of property. e.g. \"params.1.required\"\n* $2 - expected type of property. e.g. \"boolean\"",
|
||||
"templatedata-invalid-unknown": "Error message when an unknown property is found.\n* $1 - name of property. e.g. \"params.1.foobar\"",
|
||||
|
|
|
@ -49,7 +49,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": null,
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'status' => true,
|
||||
|
@ -100,7 +101,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["foo"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps": {}
|
||||
}
|
||||
',
|
||||
'msg' => 'Optional properties are added if missing'
|
||||
|
@ -130,7 +132,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["comment"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps": {}
|
||||
}
|
||||
',
|
||||
'msg' => 'Old string/* types are mapped to the unprefixed versions'
|
||||
|
@ -174,7 +177,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["nickname"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps": {}
|
||||
}
|
||||
',
|
||||
'msg' => 'InterfaceText is expanded to langcode-keyed object, assuming content language'
|
||||
|
@ -228,7 +232,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["1d", "2d"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'msg' => 'The inherits property copies over properties from another parameter '
|
||||
|
@ -348,7 +353,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
},
|
||||
"params": ["bar", "quux"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"maps": {}
|
||||
}',
|
||||
'status' => true
|
||||
),
|
||||
|
@ -392,18 +398,78 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["bar"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'msg' => 'Parameter attributes preserve information.'
|
||||
),
|
||||
array(
|
||||
'input' => '{
|
||||
"params": {
|
||||
"foo": {
|
||||
},
|
||||
"bar": {
|
||||
}
|
||||
},
|
||||
"sets": [],
|
||||
"maps": {
|
||||
"application": {
|
||||
"things": [
|
||||
"foo",
|
||||
["bar", "quux"]
|
||||
]
|
||||
}
|
||||
}
|
||||
}',
|
||||
'status' => 'Invalid parameter "quux" for property "maps.application.things".'
|
||||
),
|
||||
array(
|
||||
'input' => '{
|
||||
"params": {
|
||||
"foo": {
|
||||
},
|
||||
"bar": {
|
||||
}
|
||||
},
|
||||
"sets": [],
|
||||
"maps": {
|
||||
"application": {
|
||||
"things": {
|
||||
"appbar": "bar",
|
||||
"appfoo": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
'status' => 'Property "maps.application.things" is expected to be of type "string|array".'
|
||||
),
|
||||
array(
|
||||
'input' => '{
|
||||
"params": {
|
||||
"foo": {
|
||||
},
|
||||
"bar": {
|
||||
}
|
||||
},
|
||||
"sets": [],
|
||||
"maps": {
|
||||
"application": {
|
||||
"things": [
|
||||
[ true ]
|
||||
]
|
||||
}
|
||||
}
|
||||
}',
|
||||
'status' => 'Property "maps.application.things[0][0]" is expected to be of type "string".'
|
||||
),
|
||||
array(
|
||||
// Should be long enough to trigger this condition after gzipping.
|
||||
'input' => '{
|
||||
"description": "' . self::generatePseudorandomString( 100000, 42 ) . '",
|
||||
"params": {}
|
||||
}',
|
||||
'status' => 'Data too large to save (75,195 bytes, limit is 65,535)'
|
||||
'status' => 'Data too large to save (75,204 bytes, limit is 65,535)'
|
||||
),
|
||||
);
|
||||
$calls = array();
|
||||
|
@ -471,7 +537,7 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
if ( !isset( $case['output'] ) ) {
|
||||
if ( is_string( $case['status'] ) ) {
|
||||
$case['output'] = '{ "description": null, "params": {}, "sets": [] }';
|
||||
$case['output'] = '{ "description": null, "params": {}, "sets": [], "maps": {} }';
|
||||
} else {
|
||||
$case['output'] = $case['input'];
|
||||
}
|
||||
|
@ -560,7 +626,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": "German",
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'de',
|
||||
|
@ -575,7 +642,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": "Hi",
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -594,7 +662,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": "Dutch",
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -612,7 +681,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": null,
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -631,7 +701,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
'output' => '{
|
||||
"description": "German",
|
||||
"params": {},
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'de-formal',
|
||||
|
@ -665,7 +736,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["foo"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -699,7 +771,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["foo"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -742,7 +815,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
"label": "Spanish",
|
||||
"params": ["foo"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"maps": {}
|
||||
}
|
||||
',
|
||||
'lang' => 'fr',
|
||||
|
@ -837,7 +911,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["foo", "bar", "baz"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'msg' => 'Normalisation adds paramOrder'
|
||||
|
@ -890,7 +965,8 @@ class TemplateDataBlobTest extends MediaWikiTestCase {
|
|||
}
|
||||
},
|
||||
"paramOrder": ["baz", "foo", "bar"],
|
||||
"sets": []
|
||||
"sets": [],
|
||||
"maps" : {}
|
||||
}
|
||||
',
|
||||
'msg' => 'Custom paramOrder'
|
||||
|
|
Loading…
Reference in a new issue