diff options
author | Brad Jorsch <bjorsch@wikimedia.org> | 2019-09-11 13:05:47 -0400 |
---|---|---|
committer | Brad Jorsch <bjorsch@wikimedia.org> | 2019-10-17 15:57:54 -0400 |
commit | 2a3b546f3b7f7b9a69dd3b2bcef794c18e5a7106 (patch) | |
tree | f17f77c751ae170e7ad4c678332b599ef05a0347 | |
parent | a2aa77740f4a464a5df2c6a64728f6a9755e2d25 (diff) | |
download | mediawikicore-2a3b546f3b7f7b9a69dd3b2bcef794c18e5a7106.tar.gz mediawikicore-2a3b546f3b7f7b9a69dd3b2bcef794c18e5a7106.zip |
libs/Message: Add DataMessageValue
MediaWiki's ApiMessage was a hack, but a useful one. Let's provide that
functionality here without it being a hack.
Change-Id: Icaf88950ae6e083fc1fb89a3cadbb7f353822eaf
-rw-r--r-- | includes/libs/Message/DataMessageValue.php | 90 | ||||
-rw-r--r-- | includes/libs/Message/MessageValue.php | 32 | ||||
-rw-r--r-- | includes/libs/Message/README.md | 24 | ||||
-rw-r--r-- | tests/phpunit/includes/libs/Message/DataMessageValueTest.php | 59 |
4 files changed, 189 insertions, 16 deletions
diff --git a/includes/libs/Message/DataMessageValue.php b/includes/libs/Message/DataMessageValue.php new file mode 100644 index 000000000000..686b2ed4bfd9 --- /dev/null +++ b/includes/libs/Message/DataMessageValue.php @@ -0,0 +1,90 @@ +<?php + +namespace Wikimedia\Message; + +/** + * Value object representing a message for i18n with alternative + * machine-readable data. + * + * This augments a MessageValue with an additional machine-readable code and + * structured data. The intended use is to facilitate error reporting in APIs. + * + * For example, a MessageValue reporting an "integer out of range" error might + * use one of three message keys, depending on whether there is a minimum, a + * maximum, or both. But an API would likely want to use one code for all three + * cases, and would likely want the endpoints represented along the lines of + * `[ 'min' => 1, 'max' => 10 ]` rather than + * `[ 0 => new ScalarParam( ParamType::TEXT, 1 ), 1 => new ScalarParam( ParamType::TEXT, 10 ) ]`. + * + * DataMessageValues are pure value objects and are safely newable. + */ +class DataMessageValue extends MessageValue { + /** @var string */ + private $code; + + /** @var array|null */ + private $data; + + /** + * @param string $key + * @param (MessageParam|MessageValue|string|int|float)[] $params + * @param string|null $code String representing the concept behind + * this message. + * @param array|null $data Structured data representing the concept + * behind this message. + */ + public function __construct( $key, $params = [], $code = null, array $data = null ) { + parent::__construct( $key, $params ); + + $this->code = $code ?? $key; + $this->data = $data; + } + + /** + * Static constructor for easier chaining of `->params()` methods + * @param string $key + * @param (MessageParam|MessageValue|string|int|float)[] $params + * @param string|null $code + * @param array|null $data + * @return DataMessageValue + */ + public static function new( $key, $params = [], $code = null, array $data = null ) { + return new DataMessageValue( $key, $params, $code, $data ); + } + + /** + * Get the message code + * @return string + */ + public function getCode() { + return $this->code; + } + + /** + * Get the message's structured data + * @return array|null + */ + public function getData() { + return $this->data; + } + + public function dump() { + $contents = ''; + if ( $this->getParams() ) { + $contents = '<params>'; + foreach ( $this->getParams() as $param ) { + $contents .= $param->dump(); + } + $contents .= '</params>'; + } + + if ( $this->data !== null ) { + $contents .= '<data>' . htmlspecialchars( json_encode( $this->data ), ENT_NOQUOTES ) . '</data>'; + } + + return '<datamessage key="' . htmlspecialchars( $this->getKey() ) . '"' + . ' code="' . htmlspecialchars( $this->code ) . '">' + . $contents + . '</datamessage>'; + } +} diff --git a/includes/libs/Message/MessageValue.php b/includes/libs/Message/MessageValue.php index 73b7c34241ba..78e3c22cb463 100644 --- a/includes/libs/Message/MessageValue.php +++ b/includes/libs/Message/MessageValue.php @@ -61,7 +61,7 @@ class MessageValue { * Chainable mutator which adds text parameters and MessageParam parameters * * @param MessageParam|MessageValue|string|int|float ...$values - * @return MessageValue + * @return $this */ public function params( ...$values ) { foreach ( $values as $value ) { @@ -79,7 +79,7 @@ class MessageValue { * * @param string $type One of the ParamType constants * @param MessageValue|string|int|float ...$values Scalar values - * @return MessageValue + * @return $this */ public function textParamsOfType( $type, ...$values ) { foreach ( $values as $value ) { @@ -94,7 +94,7 @@ class MessageValue { * @param string $listType One of the ListType constants * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value * is an array of items suitable to pass as $params to ListParam::__construct() - * @return MessageValue + * @return $this */ public function listParamsOfType( $listType, ...$values ) { foreach ( $values as $value ) { @@ -107,7 +107,7 @@ class MessageValue { * Chainable mutator which adds parameters of type text (ParamType::TEXT). * * @param MessageValue|string|int|float ...$values - * @return MessageValue + * @return $this */ public function textParams( ...$values ) { return $this->textParamsOfType( ParamType::TEXT, ...$values ); @@ -117,7 +117,7 @@ class MessageValue { * Chainable mutator which adds numeric parameters (ParamType::NUM). * * @param int|float ...$values - * @return MessageValue + * @return $this */ public function numParams( ...$values ) { return $this->textParamsOfType( ParamType::NUM, ...$values ); @@ -131,7 +131,7 @@ class MessageValue { * more verbose. * * @param int|float ...$values - * @return MessageValue + * @return $this */ public function longDurationParams( ...$values ) { return $this->textParamsOfType( ParamType::DURATION_LONG, ...$values ); @@ -145,7 +145,7 @@ class MessageValue { * compact. * * @param int|float ...$values - * @return MessageValue + * @return $this */ public function shortDurationParams( ...$values ) { return $this->textParamsOfType( ParamType::DURATION_SHORT, ...$values ); @@ -156,7 +156,7 @@ class MessageValue { * * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library, * or "infinity" - * @return MessageValue + * @return $this */ public function expiryParams( ...$values ) { return $this->textParamsOfType( ParamType::EXPIRY, ...$values ); @@ -166,7 +166,7 @@ class MessageValue { * Chainable mutator which adds parameters which are a number of bytes (ParamType::SIZE). * * @param int ...$values - * @return MessageValue + * @return $this */ public function sizeParams( ...$values ) { return $this->textParamsOfType( ParamType::SIZE, ...$values ); @@ -177,7 +177,7 @@ class MessageValue { * second (ParamType::BITRATE). * * @param int|float ...$values - * @return MessageValue + * @return $this */ public function bitrateParams( ...$values ) { return $this->textParamsOfType( ParamType::BITRATE, ...$values ); @@ -191,7 +191,7 @@ class MessageValue { * documenting what that intended output format is. * * @param string ...$values - * @return MessageValue + * @return $this */ public function rawParams( ...$values ) { return $this->textParamsOfType( ParamType::RAW, ...$values ); @@ -205,7 +205,7 @@ class MessageValue { * so as to be represented as plain text rather than as any sort of markup. * * @param string ...$values - * @return MessageValue + * @return $this */ public function plaintextParams( ...$values ) { return $this->textParamsOfType( ParamType::PLAINTEXT, ...$values ); @@ -219,7 +219,7 @@ class MessageValue { * * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value * is an array of items suitable to pass as $params to ListParam::__construct() - * @return MessageValue + * @return $this */ public function commaListParams( ...$values ) { return $this->listParamsOfType( ListType::COMMA, ...$values ); @@ -233,7 +233,7 @@ class MessageValue { * * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value * is an array of items suitable to pass as $params to ListParam::__construct() - * @return MessageValue + * @return $this */ public function semicolonListParams( ...$values ) { return $this->listParamsOfType( ListType::SEMICOLON, ...$values ); @@ -247,7 +247,7 @@ class MessageValue { * * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value * is an array of items suitable to pass as $params to ListParam::__construct() - * @return MessageValue + * @return $this */ public function pipeListParams( ...$values ) { return $this->listParamsOfType( ListType::PIPE, ...$values ); @@ -261,7 +261,7 @@ class MessageValue { * two elements joined with "and". * * @param (MessageParam|string)[] ...$values - * @return MessageValue + * @return $this */ public function textListParams( ...$values ) { return $this->listParamsOfType( ListType::AND, ...$values ); diff --git a/includes/libs/Message/README.md b/includes/libs/Message/README.md index 9f6255a3f424..c6d1386c968c 100644 --- a/includes/libs/Message/README.md +++ b/includes/libs/Message/README.md @@ -74,6 +74,20 @@ has two implementations: appropriate separators. It has a "list type" (using constants defined in the **ListType** class) defining the desired separators. +#### Machine-readable messages + +**DataMessageValue** represents a message with additional machine-readable +data. In addition to the key and message parameters, it holds a "code" and +structured data that would be a useful representation of the message in an API +response or the like. + +For example, a message for an "integer out of range" error might have one of +three different keys depending on whether the range has a minimum, maximum, or +both. But all should have the same code (representing the concept of "integer +out of range") and should likely have structured data representing the range +directly as `[ 'min' => 1, 'max' => 10 ]` rather than as a flat array of +MessageParam objects. + ### Formatters A formatter for a particular language is obtained from an implementation of @@ -279,6 +293,16 @@ Formatter implementations should be able to consume message data supplied in this format, either directly via registration of i18n directories to check or by providing tooling to incorporate it during a build step. +### Machine-readable data + +Libraries producing MessageValues as error messages should generally produce +DataMessageValues instead. Codes should be similar to message keys but need +not be prefixed. Data should be restricted to values that will produce valid +output when passed to `json_encode()`. + +Libraries producing MessageValues in other contexts should consider whether the +same applies to those contexts. + --- [jQuery.i18n]: https://github.com/wikimedia/jquery.i18n diff --git a/tests/phpunit/includes/libs/Message/DataMessageValueTest.php b/tests/phpunit/includes/libs/Message/DataMessageValueTest.php new file mode 100644 index 000000000000..d72032f99352 --- /dev/null +++ b/tests/phpunit/includes/libs/Message/DataMessageValueTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Wikimedia\Tests\Message; + +use Wikimedia\Message\DataMessageValue; +use Wikimedia\Message\ParamType; +use Wikimedia\Message\ScalarParam; + +/** + * @covers \Wikimedia\Message\DataMessageValue + */ +class DataMessageValueTest extends \PHPUnit\Framework\TestCase { + public static function provideConstruct() { + return [ + [ + [ 'key' ], + '<datamessage key="key" code="key"></datamessage>', + ], + [ + [ 'key', [ 'a' ] ], + '<datamessage key="key" code="key"><params><text>a</text></params></datamessage>' + ], + [ + [ 'key', [], 'code' ], + '<datamessage key="key" code="code"></datamessage>' + ], + [ + [ 'key', [ new ScalarParam( ParamType::NUM, 1 ) ], 'code', [ 'value' => 1 ] ], + '<datamessage key="key" code="code">' + . '<params><num>1</num></params>' + . '<data>{"value":1}</data>' + . '</datamessage>' + ], + ]; + } + + /** @dataProvider provideConstruct */ + public function testConstruct( $args, $expected ) { + $mv = new DataMessageValue( ...$args ); + $this->assertSame( $expected, $mv->dump() ); + } + + /** @dataProvider provideConstruct */ + public function testNew( $args, $expected ) { + $mv = DataMessageValue::new( ...$args ); + $this->assertSame( $expected, $mv->dump() ); + } + + public function testGetCode() { + $mv = new DataMessageValue( 'key', [], 'code' ); + $this->assertSame( 'code', $mv->getCode() ); + } + + public function testGetData() { + $mv = new DataMessageValue( 'key', [], 'code', [ 'data' => 'foobar' ] ); + $this->assertSame( [ 'data' => 'foobar' ], $mv->getData() ); + } + +} |