jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/378918 )

Change subject: Report errors to the UI
......................................................................


Report errors to the UI

To report errors we utilize default core's error reporting mechanism,
so that API users will not need to learn about custom error format.

To display errors in the UI correctly we reformat API error response
to the format that our UI "framework" understands, so it can be
migrated later without changing API code and response structure.

Change-Id: I5771ad0446074cc98ad22fe172ba286550c3bab4
---
M i18n/en.json
M i18n/qqq.json
M resources/entityChangers/FormChanger.js
M src/Api/AddForm.php
M src/Api/AddFormRequestParser.php
M src/Api/AddFormRequestParserResult.php
A src/Api/Error/ApiError.php
A src/Api/Error/FormMustHaveAtLeastOneRepresentation.php
A src/Api/Error/JsonFieldHasWrongType.php
A src/Api/Error/JsonFieldIsNotAnItemId.php
A src/Api/Error/JsonFieldIsRequired.php
A src/Api/Error/ParameterIsNotAJsonObject.php
A src/Api/Error/ParameterIsNotLexemeId.php
A src/Api/Error/ParameterIsRequired.php
A src/Api/Error/RepresentationLanguageCanNotBeEmpty.php
A src/Api/Error/RepresentationTextCanNotBeEmpty.php
A src/Api/Error/RepresentationsMustHaveUniqueLanguage.php
D tests/phpunit/composer/Api/AddFormRequestParserResultTest.php
A tests/phpunit/mediawiki/Api/AddFormRequestParserResultTest.php
M tests/phpunit/mediawiki/Api/AddFormRequestParserTest.php
M tests/phpunit/mediawiki/Api/AddFormTest.php
A tests/phpunit/mediawiki/Api/Error/ApiErrorTranslationTest.php
A 
tests/phpunit/mediawiki/Api/Error/RepresentationsMustHaveUniqueLanguageTest.php
M tests/qunit/entityChangers/FormChanger.tests.js
24 files changed, 1,175 insertions(+), 283 deletions(-)

Approvals:
  WMDE-leszek: Looks good to me, approved
  Jonas Kress (WMDE): Checked
  jenkins-bot: Verified



diff --git a/i18n/en.json b/i18n/en.json
index f52c3b1..8050d90 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -35,18 +35,15 @@
        "apihelp-wblexemeaddform-param-data": "JSON encoded data for Form i.e. 
representations and grammaticalFeatures",
        "apihelp-wblexemeaddform-param-bot": "Mark this edit as bot. This URL 
flag will only be respected if the user belongs to the group 
\"{{int:group-bot}}\".",
        "apihelp-wblexemeaddform-example-1": "Add Form to the Lexeme with ID 
<samp>$1</samp> having representations <samp>$2</samp> with languages 
<samp>$3</samp> respectively and grammatical features <samp>$4</samp>",
-       "wikibase-lexeme-api-addform-lexemeid-invalid": "Invalid lexeme ID 
provided as \"lexemeId\"",
-       "wikibase-lexeme-api-addform-lexemeid-not-lexeme-id": "$1 is not a 
lexeme ID",
-       "wikibase-lexeme-api-addform-data-not-array": "\"data\" must be an 
array",
-       "wikibase-lexeme-api-addform-data-representations-key-missing": "No 
\"representations\" key in \"data\"",
-       "wikibase-lexeme-api-addform-data-invalid-json": "\"data\" is not a 
valid JSON",
-       "wikibase-lexeme-api-addform-data-grammatical-features-key-missing": 
"No \"grammaticalFeatures\" key in \"data\"",
-       "wikibase-lexeme-api-addform-data-representations-not-array": 
"\"representations\" must be an array",
-       "wikibase-lexeme-api-addform-data-grammatical-features-not-array": 
"\"grammaticalFeatures\" must be an array",
-       "wikibase-lexeme-api-addform-representations-empty": "At least a single 
representation must be provided",
-       "wikibase-lexeme-api-addform-representation-text-missing": "No 
representation string provided for representation at position $1",
-       "wikibase-lexeme-api-addform-representation-language-missing": "No 
language provided for representation at position $1",
-       "wikibase-lexeme-api-addform-grammatical-feature-itemid-invalid": 
"Invalid grammatical feature: $1",
-       "wikibase-lexeme-api-addform-grammatical-feature-not-item-id": "Not an 
item ID: $1",
+       "wikibase-lexeme-api-error-parameter-required": "Parameter \"$1\" is 
required",
+       "wikibase-lexeme-api-error-parameter-invalid-json-object": "Parameter 
\"$1\" expected to be a valid JSON object, given \"$2\"",
+       "wikibase-lexeme-api-error-json-field-required": "Field \"$2\" in 
parameter \"$1\" is required",
+       "wikibase-lexeme-api-error-json-field-not-item-id": "Field \"$2\" in 
parameter \"$1\" expected to be na Item Id. Given: \"$3\"",
+       "wikibase-lexeme-api-error-json-field-has-wrong-type": "Field \"$2\" in 
parameter \"$1\" expected to be of type \"$3\". Given: \"$4\"",
+       "wikibase-lexeme-api-error-parameter-not-lexeme-id": "Parameter \"$1\" 
expected to be a Lexeme ID. Given: \"$2\"",
+       "wikibase-lexeme-api-error-representation-text-cannot-be-empty": 
"Representation text cannot be empty",
+       "wikibase-lexeme-api-error-representation-language-cannot-be-empty": 
"Representation language cannot be empty",
+       "wikibase-lexeme-api-error-form-must-have-at-least-one-representation": 
"Form must have at least one representation",
+       "wikibase-lexeme-api-error-representations-language-not-unique": 
"Representations must have unique language. Language \"$1\" is reused",
        "wikibase-lexeme-summary-add-form": "Added new form $3 with 
{{PLURAL:$1|representation|representations}}"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 972398d..452b351 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -40,18 +40,15 @@
        "apihelp-wblexemeaddform-param-data": 
"{{doc-apihelp-param|wblexemeaddform|data}}",
        "apihelp-wblexemeaddform-param-bot": 
"{{doc-apihelp-param|wblexemeaddform|bot}}",
        "apihelp-wblexemeaddform-example-1": 
"{{doc-apihelp-example|wblexemeaddform}}",
-       "wikibase-lexeme-api-addform-lexemeid-invalid": "API error description",
-       "wikibase-lexeme-api-addform-lexemeid-not-lexeme-id": "API error 
description",
-       "wikibase-lexeme-api-addform-data-not-array": "API error description",
-       "wikibase-lexeme-api-addform-data-representations-key-missing": "API 
error description",
-       "wikibase-lexeme-api-addform-data-invalid-json": "API error 
description",
-       "wikibase-lexeme-api-addform-data-grammatical-features-key-missing": 
"API error description",
-       "wikibase-lexeme-api-addform-data-representations-not-array": "API 
error description",
-       "wikibase-lexeme-api-addform-data-grammatical-features-not-array": "API 
error description",
-       "wikibase-lexeme-api-addform-representations-empty": "API error 
description",
-       "wikibase-lexeme-api-addform-representation-text-missing": "API error 
description",
-       "wikibase-lexeme-api-addform-representation-language-missing": "API 
error description",
-       "wikibase-lexeme-api-addform-grammatical-feature-itemid-invalid": "API 
error description",
-       "wikibase-lexeme-api-addform-grammatical-feature-not-item-id": "API 
error description",
+       "wikibase-lexeme-api-error-json-field-required": "API error message 
that is being reported when required field in JSON object is not present. 
\n\nParameters:\n* $1 - Name of the parameter that is expected to be JSON 
object. \n* $2 - Path of the field inside of the JSON object (e.g 
\"array-field-name/0/other-field-name\")",
+       "wikibase-lexeme-api-error-json-field-not-item-id": "API error message 
that is being reported when field in JSON object expected to contain serialized 
Item ID, but contains something else. \n\nParameters:\n* $1 - Name of the 
parameter that is expected to be JSON object.\n* $2 - Path of the field inside 
of the JSON object (e.g \"array-field-name/0/other-field-name\")\n* $3 - 
Provided value",
+       "wikibase-lexeme-api-error-json-field-has-wrong-type": "API error 
message that is being reported when field in JSON object expected to be of 
certain type, but value of another type was provided. \n\nParameters:\n* $1 - 
Name of the parameter that is expected to be JSON object.\n* $2 - Path of the 
field inside of the JSON object (e.g 
\"array-field-name/0/other-field-name\")\n* $3 - Expected type name (e.g. 
string, array, object) \n* $4 - Provided value type name",
+       "wikibase-lexeme-api-error-parameter-not-lexeme-id": "API error message 
that is being reported when parameter is expected to contain serialized Lexeme 
ID, but contains something else. \n\nParameters:\n* $1 - Name of the 
parameter\n* $2 - Provided value",
+       "wikibase-lexeme-api-error-representation-text-cannot-be-empty": "API 
error message that is being reported when one of provided representations is 
empty",
+       "wikibase-lexeme-api-error-representation-language-cannot-be-empty": 
"API error message that is being reported when one of provided representations' 
language is empty",
+       "wikibase-lexeme-api-error-form-must-have-at-least-one-representation": 
"API error message that is being reported when no representations were 
provided",
+       "wikibase-lexeme-api-error-representations-language-not-unique": "API 
error message that is being reported when at least one of the provided 
representations reuses the language\n\nParameters:\n* $1 - Laguage that was 
reused",
+       "wikibase-lexeme-api-error-parameter-required": "API error message that 
is being reported when parameter is missing.",
+       "wikibase-lexeme-api-error-parameter-invalid-json-object": "API error 
message that is being reported when provided data parameter is not a valid 
JSON",
        "wikibase-lexeme-summary-add-form": "{{wikibase summary 
messages|lexeme|Automatic edit summary generated when adding new Form to a 
Lexeme.}}\n\nParameters:\n* $1 - Number of representations of the form 
added.\n* $2 - ignore.\n* $3 - Form ID.\n\n{{Related|Wikibase-entity-summary}}"
 }
diff --git a/resources/entityChangers/FormChanger.js 
b/resources/entityChangers/FormChanger.js
index 3e2cf4f..45adf33 100644
--- a/resources/entityChangers/FormChanger.js
+++ b/resources/entityChangers/FormChanger.js
@@ -58,11 +58,37 @@
                                action: 'wblexemeaddform',
                                lexemeId: this.lexemeId,
                                data: JSON.stringify( serializedForm ),
+                               errorformat: 'plaintext',
                                bot: 1
                        } ).then( function ( data ) {
                                return lexemeDeserializer.deserializeForm( 
data.form );
-                       } ); // TODO: Error handling
+                       } ).catch( function ( code, response ) {
+                               throw convertPlainTextErrorsToRepoApiError( 
response.errors );
+                       } );
                }
        } );
 
+       function convertPlainTextErrorsToRepoApiError( errors ) {
+               var $ul = $( '<ul>' );
+
+               var code = '';
+               errors.forEach( function ( e ) {
+                       if ( !code ) {
+                               code = e.code;
+                       }
+
+                       var $li = $( '<li>' ).text( e[ '*' ] );
+                       $ul.append( $li );
+               } );
+
+               var action = 'save';
+               var detailedMessage = $ul.html();
+               return new wb.api.RepoApiError(
+                       code,
+                       detailedMessage,
+                       [],
+                       action
+               );
+       }
+
 }( mediaWiki, wikibase, jQuery ) );
diff --git a/src/Api/AddForm.php b/src/Api/AddForm.php
index dcc2c4c..7697455 100644
--- a/src/Api/AddForm.php
+++ b/src/Api/AddForm.php
@@ -4,16 +4,7 @@
 
 use ApiBase;
 use ApiMain;
-use Wikibase\DataModel\Entity\ItemId;
-use Wikibase\DataModel\Term\Term;
-use Wikibase\DataModel\Term\TermList;
-use Wikibase\Lexeme\ChangeOp\ChangeOpAddForm;
-use Wikibase\Lexeme\DataModel\Form;
-use Wikibase\Lexeme\DataModel\Serialization\ExternalLexemeSerializer;
 use Wikibase\Lexeme\DataModel\Serialization\FormSerializer;
-use Wikibase\Lexeme\DataModel\Serialization\LexemeSerializer;
-use Wikibase\Lexeme\DataModel\Serialization\StorageLexemeSerializer;
-use Wikibase\Repo\Api\ApiErrorReporter;
 use Wikibase\Repo\Api\EntitySavingHelper;
 use Wikibase\Summary;
 
@@ -28,16 +19,6 @@
         * @var AddFormRequestParser
         */
        private $requestParser;
-
-       /**
-        * @var ApiErrorReporter
-        */
-       private $errorReporter;
-
-       /**
-        * @var ExternalLexemeSerializer
-        */
-       private $lexemeSerializer;
 
        /**
         * @var FormSerializer
@@ -65,9 +46,6 @@
                        $formSerializer,
                        function ( $module ) use ( $apiHelperFactory ) {
                                return 
$apiHelperFactory->getEntitySavingHelper( $module );
-                       },
-                       function ( $module ) use ( $apiHelperFactory ) {
-                               return $apiHelperFactory->getErrorReporter( 
$module );
                        }
                );
        }
@@ -77,36 +55,52 @@
                $moduleName,
                AddFormRequestParser $requestParser,
                FormSerializer $formSerializer,
-               callable $entitySavingHelperInstantiator,
-               callable $errorReporterInstantiator
+               callable $entitySavingHelperInstantiator
        ) {
                parent::__construct( $mainModule, $moduleName );
 
                $this->entitySavingHelper = $entitySavingHelperInstantiator( 
$this );
-               $this->errorReporter = $errorReporterInstantiator( $this );
                $this->requestParser = $requestParser;
                $this->formSerializer = $formSerializer;
        }
 
        /**
         * @see ApiBase::execute()
+        *
+        * @throws \ApiUsageException
         */
        public function execute() {
-               //FIXME: Error reporting
+               /*
+                * {
+                         "representations": [
+                               {
+                                 "representation": "",
+                                 "language": ""
+                               },
+                               {
+                                 "representation": "",
+                                 "language": ""
+                               }
+                         ],
+                         "grammaticalFeatures": [
+                               "Q1",
+                               "Q2"
+                         ]
+                       }
+                *
+                */
+
                //FIXME: Response structure? - Added form
+               //FIXME: Representation text normalization
+
+               //TODO: Corresponding HTTP codes on failure (e.g. 400, 404, 
422) (?)
                //TODO: Documenting response structure. Is it possible?
 
                $parserResult = $this->requestParser->parse( 
$this->extractRequestParams() );
 
                if ( $parserResult->hasErrors() ) {
-                       $errorMessage = $parserResult->getErrors()[0];
-                       if ( is_array( $errorMessage ) ) {
-                               $errorMessage[0] = 
'wikibase-lexeme-api-addform-' . $errorMessage[0];
-                       } elseif ( is_string( $errorMessage ) ) {
-                               $errorMessage = 'wikibase-lexeme-api-addform-' 
. $errorMessage;
-                       }
-
-                       $this->errorReporter->dieWithError( $errorMessage, 
'invalid-param' );
+                       //TODO: Increase stats counter on failure
+                       $this->dieStatus( $parserResult->asFatalStatus() );
                }
 
                $request = $parserResult->getRequest();
diff --git a/src/Api/AddFormRequestParser.php b/src/Api/AddFormRequestParser.php
index f19f64a..81bb0ce 100644
--- a/src/Api/AddFormRequestParser.php
+++ b/src/Api/AddFormRequestParser.php
@@ -4,8 +4,21 @@
 
 use Wikibase\DataModel\Entity\EntityIdParser;
 use Wikibase\DataModel\Entity\EntityIdParsingException;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\ItemIdParser;
 use Wikibase\DataModel\Term\Term;
 use Wikibase\DataModel\Term\TermList;
+use Wikibase\Lexeme\Api\Error\ApiError;
+use Wikibase\Lexeme\Api\Error\FormMustHaveAtLeastOneRepresentation;
+use Wikibase\Lexeme\Api\Error\JsonFieldHasWrongType;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsNotAnItemId;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsRequired;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotAJsonObject;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotLexemeId;
+use Wikibase\Lexeme\Api\Error\ParameterIsRequired;
+use Wikibase\Lexeme\Api\Error\RepresentationLanguageCanNotBeEmpty;
+use Wikibase\Lexeme\Api\Error\RepresentationsMustHaveUniqueLanguage;
+use Wikibase\Lexeme\Api\Error\RepresentationTextCanNotBeEmpty;
 use Wikibase\Lexeme\DataModel\LexemeId;
 
 /**
@@ -13,13 +26,21 @@
  */
 class AddFormRequestParser {
 
+       const PARAM_DATA = 'data';
+       const PARAM_LEXEME_ID = 'lexemeId';
        /**
         * @var EntityIdParser
         */
        private $entityIdParser;
 
+       /**
+        * @var ItemIdParser
+        */
+       private $itemIdParser;
+
        public function __construct( EntityIdParser $entityIdParser ) {
                $this->entityIdParser = $entityIdParser;
+               $this->itemIdParser = new ItemIdParser();
        }
 
        /**
@@ -27,14 +48,20 @@
         * @return AddFormRequestParserResult
         */
        public function parse( array $params ) {
+               //TODO: validate language. How?
+               //TODO: validate if all grammatical features exist
                $errors = $this->validateRequiredFieldsPresent( $params );
                if ( $errors ) {
                        return AddFormRequestParserResult::newWithErrors( 
$errors );
                }
 
-               $data = json_decode( $params['data'], true );
-               if ( json_last_error() !== JSON_ERROR_NONE ) {
-                       return AddFormRequestParserResult::newWithErrors( [ 
'data-invalid-json' ] );
+               $data = json_decode( $params[self::PARAM_DATA] );
+               if ( !is_object( $data ) ) {
+                       return AddFormRequestParserResult::newWithErrors(
+                               [
+                                       new ParameterIsNotAJsonObject( 
self::PARAM_DATA, $params[self::PARAM_DATA] )
+                               ]
+                       );
                }
 
                $errors = $this->validateDataStructure( $data );
@@ -42,9 +69,9 @@
                        return AddFormRequestParserResult::newWithErrors( 
$errors );
                }
 
-               $lexemeId = $this->parseLexemeId( $params['lexemeId'], $errors 
);
-               $representations = $this->parseRepresentations( 
$data['representations'], $errors );
-               $grammaticalFeatures = $this->parseGrammaticalFeatures( 
$data['grammaticalFeatures'], $errors );
+               $lexemeId = $this->parseLexemeId( 
$params[self::PARAM_LEXEME_ID], $errors );
+               $representations = $this->parseRepresentations( 
$data->representations, $errors );
+               $grammaticalFeatures = $this->parseGrammaticalFeatures( 
$data->grammaticalFeatures, $errors );
 
                if ( $errors ) {
                        return AddFormRequestParserResult::newWithErrors( 
$errors );
@@ -55,23 +82,29 @@
                );
        }
 
-       private function validateDataStructure( $data ) {
+       private function validateDataStructure( \stdClass $data ) {
                $errors = [];
 
-               if ( !is_array( $data ) ) {
-                       return [ 'data-not-array' ];
+               if ( !property_exists( $data, 'representations' ) ) {
+                       $errors[] = new JsonFieldIsRequired( self::PARAM_DATA, 
[ 'representations' ] );
+               } elseif ( !is_array( $data->representations ) ) {
+                       $errors[] = new JsonFieldHasWrongType(
+                               self::PARAM_DATA,
+                               [ 'representations' ],
+                               'array',
+                               gettype( $data->representations )
+                       );
                }
 
-               if ( !array_key_exists( 'representations', $data ) ) {
-                       $errors[] = 'data-representations-key-missing';
-               } elseif ( !is_array( $data['representations'] ) ) {
-                       $errors[] = 'data-representations-not-array';
-               }
-
-               if ( !array_key_exists( 'grammaticalFeatures', $data ) ) {
-                       $errors[] = 'data-grammatical-features-key-missing';
-               } elseif ( !is_array( $data['grammaticalFeatures'] ) ) {
-                       $errors[] = 'data-grammatical-features-not-array';
+               if ( !property_exists( $data, 'grammaticalFeatures' ) ) {
+                       $errors[] = new JsonFieldIsRequired( self::PARAM_DATA, 
[ 'grammaticalFeatures' ] );
+               } elseif ( !is_array( $data->grammaticalFeatures ) ) {
+                       $errors[] = new JsonFieldHasWrongType(
+                               self::PARAM_DATA,
+                               [ 'grammaticalFeatures' ],
+                               'array',
+                               gettype( $data->grammaticalFeatures )
+                       );
                }
 
                return $errors;
@@ -85,30 +118,59 @@
                try {
                        $lexemeId = $this->entityIdParser->parse( $id );
                } catch ( EntityIdParsingException $e ) {
-                       $errors[] = [ 'lexemeid-invalid', $id ];
+                       $errors[] = new ParameterIsNotLexemeId( 
self::PARAM_LEXEME_ID, $id );
                        return null;
                }
 
                if ( $lexemeId->getEntityType() !== 'lexeme' ) {
-                       $errors[] = [ 'lexemeid-not-lexeme-id', $id ];
+                       $errors[] = new ParameterIsNotLexemeId( 
self::PARAM_LEXEME_ID, $id );
                        return null;
                }
 
                return $lexemeId;
        }
 
-       private function parseRepresentations( array $data, array &$errors ) {
-               $representations = [];
+       /**
+        * @param \stdClass[] $givenRepresentations
+        * @param ApiError[] $errors
+        * @return TermList
+        */
+       private function parseRepresentations( array $givenRepresentations, 
array &$errors ) {
+               if ( empty( $givenRepresentations ) ) {
+                       $errors[] = new FormMustHaveAtLeastOneRepresentation( 
self::PARAM_DATA, [ 'representations' ] );
+               }
 
-               foreach ( $data as $index => $representationData ) {
+               //FIXME: Array may contain representation with empty text (or 
untrimmed) which won't be added
+               $result = [];
+
+               foreach ( $givenRepresentations as $index => $el ) {
                        $incomplete = false;
 
-                       if ( !array_key_exists( 'representation', 
$representationData ) ) {
-                               $errors[] = [ 'representation-text-missing', 
$index ];
+                       if ( !property_exists( $el, 'representation' ) ) {
+                               $errors[] = new JsonFieldIsRequired(
+                                       self::PARAM_DATA,
+                                       [ 'representations', $index, 
'representation' ]
+                               );
+                               $incomplete = true;
+                       } elseif ( empty( $el->representation ) ) {
+                               $errors[] = new RepresentationTextCanNotBeEmpty(
+                                       self::PARAM_DATA,
+                                       [ 'representations', $index, 
'representation' ]
+                               );
                                $incomplete = true;
                        }
-                       if ( !array_key_exists( 'language', $representationData 
) ) {
-                               $errors[] = [ 
'representation-language-missing', $index ];
+
+                       if ( !property_exists( $el, 'language' ) ) {
+                               $errors[] = new JsonFieldIsRequired(
+                                       self::PARAM_DATA,
+                                       [ 'representations', $index, 'language' 
]
+                               );
+                               $incomplete = true;
+                       } elseif ( empty( $el->language ) ) {
+                               $errors[] = new 
RepresentationLanguageCanNotBeEmpty(
+                                       self::PARAM_DATA,
+                                       [ 'representations', $index, 'language' 
]
+                               );
                                $incomplete = true;
                        }
 
@@ -116,32 +178,42 @@
                                continue;
                        }
 
-                       $representations[] = new Term(
-                               $representationData['language'],
-                               $representationData['representation']
-                       );
+                       if ( isset( $result[$el->language] ) ) {
+                               $errors[] = new 
RepresentationsMustHaveUniqueLanguage(
+                                       self::PARAM_DATA,
+                                       [ 'representations', $index, 'language' 
],
+                                       $el->language
+                               );
+                       }
+
+                       $result[$el->language] = $el->representation;
                }
 
-               if ( empty( $representations ) ) {
-                       $errors[] = 'representations-empty';
+               $terms = [];
+               foreach ( $result as $language => $representation ) {
+                       $terms[] = new Term( $language, $representation );
                }
 
-               return new TermList( $representations );
+               return new TermList( $terms );
        }
 
-       private function parseGrammaticalFeatures( $data, array &$errors ) {
+       /**
+        * @param string[] $data
+        * @param ApiError[] $errors
+        * @return ItemId[]
+        */
+       private function parseGrammaticalFeatures( array $data, array &$errors 
) {
                $features = [];
 
                foreach ( $data as $index => $featureId ) {
                        try {
-                               $id = $this->entityIdParser->parse( $featureId 
);
+                               $id = $this->itemIdParser->parse( $featureId );
                        } catch ( EntityIdParsingException $e ) {
-                               $errors[] = [ 
'grammatical-feature-itemid-invalid', $featureId ];
-                               continue;
-                       }
-
-                       if ( $id->getEntityType() !== 'item' ) {
-                               $errors[] = [ 
'grammatical-feature-not-item-id', $featureId ];
+                               $errors[] = new JsonFieldIsNotAnItemId(
+                                       self::PARAM_DATA,
+                                       [ 'grammaticalFeatures', $index ],
+                                       $featureId
+                               );
                                continue;
                        }
 
@@ -154,12 +226,12 @@
        private function validateRequiredFieldsPresent( array $params ) {
                $errors = [];
 
-               if ( !array_key_exists( 'lexemeId', $params ) ) {
-                       $errors[] = 'lexemeId-param-missing';
+               if ( !array_key_exists( self::PARAM_LEXEME_ID, $params ) ) {
+                       $errors[] = new ParameterIsRequired( 
self::PARAM_LEXEME_ID );
                }
 
-               if ( !array_key_exists( 'data', $params ) ) {
-                       $errors[] = 'data-parame-missing';
+               if ( !array_key_exists( self::PARAM_DATA, $params ) ) {
+                       $errors[] = new ParameterIsRequired( self::PARAM_DATA );
                }
 
                return $errors;
diff --git a/src/Api/AddFormRequestParserResult.php 
b/src/Api/AddFormRequestParserResult.php
index a4d8f77..2ca276d 100644
--- a/src/Api/AddFormRequestParserResult.php
+++ b/src/Api/AddFormRequestParserResult.php
@@ -2,30 +2,44 @@
 
 namespace Wikibase\Lexeme\Api;
 
+use Wikibase\Lexeme\Api\Error\ApiError;
+use Wikimedia\Assert\Assert;
+
 /**
  * @license GPL-2.0+
  */
 class AddFormRequestParserResult {
 
+       /**
+        * @var AddFormRequest|null
+        */
        private $request;
 
-       private $errors;
-
        /**
-        * @param AddFormRequest|null $request
-        * @param string[] $errors
+        * @var ApiError[]
         */
-       public function __construct( AddFormRequest $request = null, array 
$errors ) {
-               $this->request = $request;
-               $this->errors = $errors;
-       }
+       private $errors;
 
        public static function newWithRequest( AddFormRequest $request ) {
                return new self( $request, [] );
        }
 
+       /**
+        * @param ApiError[] $errors
+        * @return AddFormRequestParserResult
+        */
        public static function newWithErrors( array $errors ) {
                return new self( null, $errors );
+       }
+
+       /**
+        * @param AddFormRequest|null $request
+        * @param ApiError[] $errors
+        */
+       private function __construct( AddFormRequest $request = null, array 
$errors ) {
+               Assert::parameterElementType( ApiError::class, $errors, 
'$errors' );
+               $this->request = $request;
+               $this->errors = $errors;
        }
 
        public function getRequest() {
@@ -42,8 +56,19 @@
                return !empty( $this->errors );
        }
 
-       public function getErrors() {
-               return $this->errors;
+       /**
+        * @return \Status
+        */
+       public function asFatalStatus() {
+               if ( !$this->hasErrors() ) {
+                       throw new \LogicException( 'Succesful result can not be 
converted to fatal status' );
+               }
+
+               $status = \Status::newGood();
+               foreach ( $this->errors as $error ) {
+                       $status->fatal( $error->asApiMessage() );
+               }
+               return $status;
        }
 
 }
diff --git a/src/Api/Error/ApiError.php b/src/Api/Error/ApiError.php
new file mode 100644
index 0000000..3ff5af7
--- /dev/null
+++ b/src/Api/Error/ApiError.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+interface ApiError {
+
+       /**
+        * @return \ApiMessage
+        */
+       public function asApiMessage();
+
+}
diff --git a/src/Api/Error/FormMustHaveAtLeastOneRepresentation.php 
b/src/Api/Error/FormMustHaveAtLeastOneRepresentation.php
new file mode 100644
index 0000000..bcf935d
--- /dev/null
+++ b/src/Api/Error/FormMustHaveAtLeastOneRepresentation.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class FormMustHaveAtLeastOneRepresentation implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string[]
+        */
+       private $fieldPath;
+
+       /**
+        * @param string $parameterName
+        * @param string[] $fieldPath
+        */
+       public function __construct( $parameterName, array $fieldPath ) {
+               $this->parameterName = $parameterName;
+               $this->fieldPath = $fieldPath;
+       }
+
+       /**
+        * @return \ApiMessage
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       
'wikibase-lexeme-api-error-form-must-have-at-least-one-representation',
+                       []
+               );
+               return new \ApiMessage(
+                       $message,
+                       'unprocessable-request',
+                       [
+                               'parameterName' => $this->parameterName,
+                               'fieldPath' => $this->fieldPath
+                       ]
+               );
+       }
+
+}
diff --git a/src/Api/Error/JsonFieldHasWrongType.php 
b/src/Api/Error/JsonFieldHasWrongType.php
new file mode 100644
index 0000000..e09649c
--- /dev/null
+++ b/src/Api/Error/JsonFieldHasWrongType.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class JsonFieldHasWrongType implements ApiError {
+       /**
+        * @var string
+        */
+       private $parameterName;
+       /**
+        * @var string[]
+        */
+       private $fieldPath;
+       /**
+        * @var string
+        */
+       private $expectedType;
+       /**
+        * @var string
+        */
+       private $givenType;
+
+       /**
+        * JsonFieldHasWrongType constructor.
+        * @param string $parameterName
+        * @param string[] $fieldPath
+        * @param string $expectedType
+        * @param string $givenType
+        */
+       public function __construct( $parameterName, array $fieldPath, 
$expectedType, $givenType ) {
+               $this->parameterName = $parameterName;
+               $this->fieldPath = $fieldPath;
+               $this->expectedType = $expectedType;
+               $this->givenType = $givenType;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       'wikibase-lexeme-api-error-json-field-has-wrong-type',
+                       [
+                               $this->parameterName,
+                               implode( '/', $this->fieldPath ),
+                               $this->expectedType,
+                               $this->givenType
+                       ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/JsonFieldIsNotAnItemId.php 
b/src/Api/Error/JsonFieldIsNotAnItemId.php
new file mode 100644
index 0000000..b344caa
--- /dev/null
+++ b/src/Api/Error/JsonFieldIsNotAnItemId.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class JsonFieldIsNotAnItemId implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string[]
+        */
+       private $path;
+
+       /**
+        * @var string
+        */
+       private $given;
+
+       /**
+        * @param string $parameterName
+        * @param string[] $path
+        * @param string $given
+        */
+       public function __construct( $parameterName, array $path, $given ) {
+               $this->parameterName = $parameterName;
+               $this->path = $path;
+               $this->given = $given;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       'wikibase-lexeme-api-error-json-field-not-item-id',
+                       [ $this->parameterName, implode( '/', $this->path ), 
$this->given ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/JsonFieldIsRequired.php 
b/src/Api/Error/JsonFieldIsRequired.php
new file mode 100644
index 0000000..ce625e2
--- /dev/null
+++ b/src/Api/Error/JsonFieldIsRequired.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class JsonFieldIsRequired implements ApiError {
+       /**
+        * @var
+        */
+       private $parameterName;
+       /**
+        * @var string[]
+        */
+       private $fieldPath;
+
+       /**
+        * JsonFieldIsRequired constructor.
+        * @param string $parameterName
+        * @param string[] $fieldPath
+        */
+       public function __construct( $parameterName, array $fieldPath ) {
+               $this->parameterName = $parameterName;
+               $this->fieldPath = $fieldPath;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       'wikibase-lexeme-api-error-json-field-required',
+                       [ $this->parameterName, implode( '/', $this->fieldPath 
) ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/ParameterIsNotAJsonObject.php 
b/src/Api/Error/ParameterIsNotAJsonObject.php
new file mode 100644
index 0000000..21f1659
--- /dev/null
+++ b/src/Api/Error/ParameterIsNotAJsonObject.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class ParameterIsNotAJsonObject implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string
+        */
+       private $given;
+
+       /**
+        * @param string $parameterName
+        * @param string $given
+        */
+       public function __construct( $parameterName, $given ) {
+               $this->parameterName = $parameterName;
+               $this->given = $given;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       
'wikibase-lexeme-api-error-parameter-invalid-json-object',
+                       [ $this->parameterName, $this->given ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/ParameterIsNotLexemeId.php 
b/src/Api/Error/ParameterIsNotLexemeId.php
new file mode 100644
index 0000000..5d5a6e7
--- /dev/null
+++ b/src/Api/Error/ParameterIsNotLexemeId.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class ParameterIsNotLexemeId implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string
+        */
+       private $given;
+
+       /**
+        * @param string $parameterName
+        * @param string $given
+        */
+       public function __construct( $parameterName, $given ) {
+               $this->parameterName = $parameterName;
+               $this->given = $given;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               // Parameter "$1" expected to be a valid Lexeme ID (ex. "L10"), 
given "$2"
+               $message = new \Message(
+                       'wikibase-lexeme-api-error-parameter-not-lexeme-id',
+                       [ $this->parameterName, $this->given ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/ParameterIsRequired.php 
b/src/Api/Error/ParameterIsRequired.php
new file mode 100644
index 0000000..bf63ff7
--- /dev/null
+++ b/src/Api/Error/ParameterIsRequired.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class ParameterIsRequired implements ApiError {
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @param string $parameterName
+        */
+       public function __construct( $parameterName ) {
+               $this->parameterName = $parameterName;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       'wikibase-lexeme-api-error-parameter-required',
+                       [ $this->parameterName ]
+               );
+               return new \ApiMessage( $message, 'bad-request' );
+       }
+
+}
diff --git a/src/Api/Error/RepresentationLanguageCanNotBeEmpty.php 
b/src/Api/Error/RepresentationLanguageCanNotBeEmpty.php
new file mode 100644
index 0000000..374823d
--- /dev/null
+++ b/src/Api/Error/RepresentationLanguageCanNotBeEmpty.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class RepresentationLanguageCanNotBeEmpty implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string[]
+        */
+       private $fieldPath;
+
+       /**
+        * @param string $parameterName
+        * @param string[] $fieldPath
+        */
+       public function __construct( $parameterName, array $fieldPath ) {
+               $this->parameterName = $parameterName;
+               $this->fieldPath = $fieldPath;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       
'wikibase-lexeme-api-error-representation-language-cannot-be-empty',
+                       []
+               );
+               return new \ApiMessage(
+                       $message,
+                       'unprocessable-request',
+                       [
+                               'parameterName' => $this->parameterName,
+                               'fieldPath' => $this->fieldPath
+                       ]
+               );
+       }
+
+}
diff --git a/src/Api/Error/RepresentationTextCanNotBeEmpty.php 
b/src/Api/Error/RepresentationTextCanNotBeEmpty.php
new file mode 100644
index 0000000..3cf67fb
--- /dev/null
+++ b/src/Api/Error/RepresentationTextCanNotBeEmpty.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class RepresentationTextCanNotBeEmpty implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string[]
+        */
+       private $fieldPath;
+
+       /**
+        * @param string $parameterName
+        * @param string[] $fieldPath
+        */
+       public function __construct( $parameterName, array $fieldPath ) {
+               $this->parameterName = $parameterName;
+               $this->fieldPath = $fieldPath;
+       }
+
+       /**
+        * @see ApiError::asApiMessage()
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       
'wikibase-lexeme-api-error-representation-text-cannot-be-empty',
+                       []
+               );
+               return new \ApiMessage(
+                       $message,
+                       'unprocessable-request',
+                       [
+                               'parameterName' => $this->parameterName,
+                               'fieldPath' => $this->fieldPath
+                       ]
+               );
+       }
+
+}
diff --git a/src/Api/Error/RepresentationsMustHaveUniqueLanguage.php 
b/src/Api/Error/RepresentationsMustHaveUniqueLanguage.php
new file mode 100644
index 0000000..0a4da6f
--- /dev/null
+++ b/src/Api/Error/RepresentationsMustHaveUniqueLanguage.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Wikibase\Lexeme\Api\Error;
+
+class RepresentationsMustHaveUniqueLanguage implements ApiError {
+
+       /**
+        * @var string
+        */
+       private $parameterName;
+
+       /**
+        * @var string[]
+        */
+       private $languageFieldPath;
+
+       /**
+        * @var string
+        */
+       private $language;
+
+       public function __construct(
+               $parameterName,
+               array $languageFieldPath,
+               $language
+       ) {
+               $this->parameterName = $parameterName;
+               $this->languageFieldPath = $languageFieldPath;
+               $this->language = $language;
+       }
+
+       /**
+        * @return \ApiMessage
+        */
+       public function asApiMessage() {
+               $message = new \Message(
+                       
'wikibase-lexeme-api-error-representations-language-not-unique',
+                       [ $this->language ]
+               );
+               return new \ApiMessage(
+                       $message,
+                       'unprocessable-request',
+                       [
+                               'parameterName' => $this->parameterName,
+                               'fieldPath' => $this->languageFieldPath
+                       ]
+               );
+       }
+
+}
diff --git a/tests/phpunit/composer/Api/AddFormRequestParserResultTest.php 
b/tests/phpunit/composer/Api/AddFormRequestParserResultTest.php
deleted file mode 100644
index 8e0e0a0..0000000
--- a/tests/phpunit/composer/Api/AddFormRequestParserResultTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Wikibase\Lexeme\Tests\Api;
-
-use Wikibase\Lexeme\Api\AddFormRequestParserResult;
-
-/**
- * @covers \Wikibase\Lexeme\Api\AddFormRequestParserResult
- *
- * @group WikibaseLexeme
- *
- * @license GPL-2.0+
- */
-class AddFormRequestParserResultTest extends \PHPUnit_Framework_TestCase {
-
-       public function testGivenThereAreErrors_getRequestThrowsException() {
-               $result = AddFormRequestParserResult::newWithErrors( [ 'foobar' 
] );
-
-               $this->setExpectedException( \Exception::class );
-
-               $result->getRequest();
-       }
-
-}
diff --git a/tests/phpunit/mediawiki/Api/AddFormRequestParserResultTest.php 
b/tests/phpunit/mediawiki/Api/AddFormRequestParserResultTest.php
new file mode 100644
index 0000000..050dea4
--- /dev/null
+++ b/tests/phpunit/mediawiki/Api/AddFormRequestParserResultTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Wikibase\Lexeme\Tests\Api;
+
+use Wikibase\Lexeme\Api\AddFormRequest;
+use Wikibase\Lexeme\Api\AddFormRequestParserResult;
+use Wikibase\Lexeme\Api\Error\ApiError;
+
+/**
+ * @covers \Wikibase\Lexeme\Api\AddFormRequestParserResult
+ *
+ * @group WikibaseLexeme
+ *
+ * @license GPL-2.0+
+ */
+class AddFormRequestParserResultTest extends \PHPUnit_Framework_TestCase {
+
+       public function testGivenThereAreErrors_getRequestThrowsException() {
+               $result = AddFormRequestParserResult::newWithErrors( [ 
$this->newApiError() ] );
+
+               $this->setExpectedException( \Exception::class );
+
+               $result->getRequest();
+       }
+
+       public function 
testGivenResultIsSuccessful_asFatalStatusThrowsException() {
+               $result = AddFormRequestParserResult::newWithRequest( 
$this->newRequest() );
+
+               $this->setExpectedException( \Exception::class );
+               $result->asFatalStatus();
+       }
+
+       public function 
testGivenThereAreErrors_asFatalStatusReturnsFatalStatusWithTheErrorMessages() {
+               $error1 = $this->newApiError();
+               $error2 = $this->newApiError();
+               $result = AddFormRequestParserResult::newWithErrors( [ $error1, 
$error2 ] );
+
+               $status = $result->asFatalStatus();
+
+               $this->assertTrue( !$status->isGood(), "Status is not fatal" );
+               $gotErrors = $status->getErrors();
+               assertThat( $gotErrors, hasItem( hasKeyValuePair( 'message', 
$error1->asApiMessage() ) ) );
+               assertThat( $gotErrors, hasItem( hasKeyValuePair( 'message', 
$error2->asApiMessage() ) ) );
+       }
+
+       public function 
testCanNotCreateOnlyWithArrayContainingNotAnApiErrorInstance() {
+               $this->setExpectedException( \InvalidArgumentException::class );
+               AddFormRequestParserResult::newWithErrors( [ 'error' ] );
+       }
+
+       /**
+        * @return ApiError
+        */
+       public function newApiError() {
+               /** @var ApiError|\Prophecy\Prophecy\ObjectProphecy $error */
+               $error = $this->prophesize( ApiError::class );
+               $error->asApiMessage()
+                       ->willReturn( $this->prophesize( \ApiMessage::class 
)->reveal() );
+
+               return $error->reveal();
+       }
+
+       /**
+        * @return AddFormRequest
+        */
+       private function newRequest() {
+               return $this->prophesize( AddFormRequest::class )->reveal();
+       }
+
+}
diff --git a/tests/phpunit/mediawiki/Api/AddFormRequestParserTest.php 
b/tests/phpunit/mediawiki/Api/AddFormRequestParserTest.php
index 9f44cb7..4c6d2b5 100644
--- a/tests/phpunit/mediawiki/Api/AddFormRequestParserTest.php
+++ b/tests/phpunit/mediawiki/Api/AddFormRequestParserTest.php
@@ -8,11 +8,24 @@
 use Wikibase\DataModel\Term\TermList;
 use Wikibase\Lexeme\Api\AddFormRequest;
 use Wikibase\Lexeme\Api\AddFormRequestParser;
+use Wikibase\Lexeme\Api\AddFormRequestParserResult;
+use Wikibase\Lexeme\Api\Error\ApiError;
+use Wikibase\Lexeme\Api\Error\FormMustHaveAtLeastOneRepresentation;
+use Wikibase\Lexeme\Api\Error\JsonFieldHasWrongType;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsNotAnItemId;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsRequired;
+use Wikibase\Lexeme\Api\Error\JsonFieldMustNotBeEmpty;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotAJsonObject;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotLexemeId;
+use Wikibase\Lexeme\Api\Error\ParameterIsRequired;
+use Wikibase\Lexeme\Api\Error\RepresentationLanguageCanNotBeEmpty;
+use Wikibase\Lexeme\Api\Error\RepresentationsMustHaveUniqueLanguage;
+use Wikibase\Lexeme\Api\Error\RepresentationTextCanNotBeEmpty;
 use Wikibase\Lexeme\ChangeOp\ChangeOpAddForm;
 use Wikibase\Lexeme\DataModel\LexemeId;
 
 /**
- * @covers Wikibase\Lexeme\Api\AddFormRequestParser
+ * @covers \Wikibase\Lexeme\Api\AddFormRequestParser
  *
  * @group WikibaseLexeme
  *
@@ -23,12 +36,18 @@
        /**
         * @dataProvider provideInvalidParamsAndErrors
         */
-       public function testGivenInvalidParams_parseReturnsError( array $params 
) {
+       public function testGivenInvalidParams_parseReturnsError(
+               array $params,
+               array $expectedErrors
+       ) {
                $parser = $this->newAddFormRequestParser();
 
                $result = $parser->parse( $params );
 
-               $this->assertTrue( $result->hasErrors() );
+               $this->assertTrue( $result->hasErrors(), 'Result doesnt contain 
errors, but should' );
+               foreach ( $expectedErrors as $expectedError ) {
+                       $this->assertResultContainsError( $result, 
$expectedError );
+               }
        }
 
        public function provideInvalidParamsAndErrors() {
@@ -40,53 +59,173 @@
                );
 
                return [
-                       'no lexemeId param' => [ [ 'data' => 
$this->getDataParam() ] ],
-                       'no data param' => [ [ 'lexemeId' => 'L1' ] ],
-                       'invalid lexeme ID (random string not ID)' => [ [
-                               'lexemeId' => 'foo', 'data' => 
$this->getDataParam()
-                       ] ],
-                       'invalid lexeme ID (not a lexeme ID)' => [ [
-                               'lexemeId' => 'Q11', 'data' => 
$this->getDataParam()
-                       ] ],
-                       'data not a well-formed JSON' => [ [ 'lexemeId' => 
'L1', 'data' => '{foo' ] ],
-                       'data not an array' => [ [ 'lexemeId' => 'L1', 'data' 
=> 'foo' ] ],
-                       'no representations in data' => [ [
-                               'lexemeId' => 'L1', 'data' => 
$noRepresentationsInDataParams
-                       ] ],
-                       'no grammatical features in data' => [ [
-                               'lexemeId' => 'L1', 'data' => 
$noGrammaticalFeaturesInDataParams
-                       ] ],
-                       'representations not an array' => [ [
-                               'lexemeId' => 'L1', 'data' => 
$this->getDataParam( [ 'representations' => 'foo' ] )
-                       ] ],
-                       'grammatical features not an array' => [ [
-                               'lexemeId' => 'L1', 'data' => 
$this->getDataParam( [ 'grammaticalFeatures' => 'Q1' ] )
-                       ] ],
-                       'empty representation list in data' => [ [
-                               'lexemeId' => 'L1',
-                               'data' => $this->getDataParam( [ 
'representations' => [] ] )
-                       ] ],
-                       'no representation string in data' => [ [
-                               'lexemeId' => 'L1',
-                               'data' => $this->getDataParam( [ 
'representations' => [ [ 'language' => 'en' ] ] ] )
-                       ] ],
-                       'no representation language in data' => [ [
-                               'lexemeId' => 'L1',
-                               'data' => $this->getDataParam( [ 
'representations' => [ [ 'representation' => 'foo' ] ] ] )
-                       ] ],
-                       'invalid item ID as grammatical feature (random string 
not ID)' => [ [
-                               'lexemeId' => 'L1',
-                               'data' => $this->getDataParam(
-                                       [ 'representations' => [ [ 
'grammaticalFeatures' => [ 'foo' ] ] ] ]
-                               )
-                       ] ],
-                       'invalid item ID as grammatical feature (not an item 
ID)' => [ [
-                               'lexemeId' => 'L1',
-                               'data' => $this->getDataParam(
-                                       [ 'representations' => [ [ 
'grammaticalFeatures' => [ 'L2' ] ] ] ]
-                               )
-                       ] ],
+                       'no lexemeId param' => [
+                               [ 'data' => $this->getDataParam() ],
+                               [ new ParameterIsRequired( 'lexemeId' ) ]
+                       ],
+                       'no data param' => [
+                               [ 'lexemeId' => 'L1' ],
+                               [ new ParameterIsRequired( 'data' ) ]
+                       ],
+                       'invalid lexeme ID (random string not ID)' => [
+                               [ 'lexemeId' => 'foo', 'data' => 
$this->getDataParam() ],
+                               [ new ParameterIsNotLexemeId( 'lexemeId', 'foo' 
) ]
+                       ],
+                       'invalid lexeme ID (not a lexeme ID)' => [
+                               [ 'lexemeId' => 'Q11', 'data' => 
$this->getDataParam() ],
+                               [ new ParameterIsNotLexemeId( 'lexemeId', 'Q11' 
) ]
+                       ],
+                       'data not a well-formed JSON' => [
+                               [ 'lexemeId' => 'L1', 'data' => '{foo' ],
+                               [ new ParameterIsNotAJsonObject( 'data', '{foo' 
) ]
+                       ],
+                       'data not an object - string given' => [
+                               [ 'lexemeId' => 'L1', 'data' => '"foo"' ],
+                               [ new ParameterIsNotAJsonObject( 'data', 
'"foo"' ) ]
+                       ],
+                       'data not an object - array given' => [
+                               [ 'lexemeId' => 'L1', 'data' => '[]' ],
+                               [ new ParameterIsNotAJsonObject( 'data', '[]' ) 
]
+                       ],
+                       'no representations in data' => [
+                               [ 'lexemeId' => 'L1', 'data' => 
$noRepresentationsInDataParams ],
+                               [ new JsonFieldIsRequired( 'data', [ 
'representations' ] ) ]
+                       ],
+                       'no grammatical features in data' => [
+                               [ 'lexemeId' => 'L1', 'data' => 
$noGrammaticalFeaturesInDataParams ],
+                               [ new JsonFieldIsRequired( 'data', [ 
'grammaticalFeatures' ] ) ]
+                       ],
+                       'representations is a string' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam( [ 
'representations' => 'foo' ] )
+                               ],
+                               [ new JsonFieldHasWrongType( 'data', [ 
'representations' ], 'array', 'string' ) ]
+                       ],
+                       'representations is an object' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam( [ 
'representations' => [ 'foo' => 'bar' ] ] )
+                               ],
+                               [ new JsonFieldHasWrongType( 'data', [ 
'representations' ], 'array', 'object' ) ]
+                       ],
+                       'grammatical features not an array' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam( [ 
'grammaticalFeatures' => 'Q1' ] )
+                               ],
+                               [ new JsonFieldHasWrongType(
+                                       'data', [ 'grammaticalFeatures' ], 
'array', 'string'
+                               ) ]
+                       ],
+                       'empty representation list in data' => [
+                               [ 'lexemeId' => 'L1', 'data' => 
$this->getDataParam(
+                                       [ 'representations' => [] ]
+                               ) ],
+                               [ new FormMustHaveAtLeastOneRepresentation( 
'data', [ 'representations' ] ) ]
+                       ],
+                       'representation list contains only single empty 
representation' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'representations' => [ [ 
'representation' => '', 'language' => 'en' ] ] ]
+                                       )
+                               ],
+                               [ new RepresentationTextCanNotBeEmpty( 'data', 
[ 'representations', 0, 'representation' ] ) ]
+                       ],
+                       'representation list contains only representation with 
empty language' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'representations' => [ [ 
'representation' => 'goat', 'language' => '' ] ] ]
+                                       )
+                               ],
+                               [ new RepresentationLanguageCanNotBeEmpty( 
'data', [ 'representations', 0, 'language' ] ) ]
+                       ],
+                       'no representation string in data' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'representations' => [ [ 
'language' => 'en' ] ] ]
+                                       )
+                               ],
+                               [ new JsonFieldIsRequired( 'data', [ 
'representations', 0, 'representation' ] ) ]
+                       ],
+                       'no representation language in data' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'representations' => [ [ 
'representation' => 'foo' ] ] ]
+                                       )
+                               ],
+                               [ new JsonFieldIsRequired( 'data', [ 
'representations', 0, 'language' ] ) ]
+                       ],
+                       'two representations with the same language' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'representations' => [
+                                                       [ 'representation' => 
'r1',  'language' => 'en' ],
+                                                       [ 'representation' => 
'r2',  'language' => 'fr' ],
+                                                       [ 'representation' => 
'r3',  'language' => 'en' ],
+                                               ] ]
+                                       )
+                               ],
+                               [ new RepresentationsMustHaveUniqueLanguage(
+                                       'data',
+                                       [ 'representations', 2, 'language' ],
+                                       'en'
+                               ) ]
+                       ],
+                       'invalid item ID as grammatical feature (random string 
not ID)' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'grammaticalFeatures' => [ 
'foo' ] ]
+                                       )
+                               ],
+                               [ new JsonFieldIsNotAnItemId(
+                                       'data',
+                                       [ 'grammaticalFeatures', 0 ],
+                                       'foo'
+                               ) ]
+                       ],
+                       'invalid item ID as grammatical feature (not an item 
ID)' => [
+                               [
+                                       'lexemeId' => 'L1',
+                                       'data' => $this->getDataParam(
+                                               [ 'grammaticalFeatures' => [ 
'L2' ] ]
+                                       )
+                               ] ,
+                               [ new JsonFieldIsNotAnItemId(
+                                       'data',
+                                       [ 'grammaticalFeatures', 0 ],
+                                       'L2'
+                               ) ] ],
                ];
+       }
+
+       public function 
testGivenOneRepresentationMissingText_parseReturnsRequestWithOnlyThisError() {
+               $data = [
+                       'representations' => [
+                               [
+                                       'language' => 'en',
+                                       'representation' => ''
+                               ]
+                       ],
+                       'grammaticalFeatures' => [],
+               ];
+
+               $parser = $this->newAddFormRequestParser();
+               $result = $parser->parse( [ 'lexemeId' => 'L1', 'data' => 
json_encode( $data ) ] );
+
+               $errors = $result->asFatalStatus()->getErrors();
+               $this->assertCount( 1, $errors );
+               $expectedError = new RepresentationTextCanNotBeEmpty(
+                       'data',
+                       [ 'representations', 0, 'representation' ]
+               );
+               $this->assertResultContainsError( $result, $expectedError );
        }
 
        public function testGivenValidData_parseReturnsRequestAndNoErrors() {
@@ -149,4 +288,17 @@
                return new AddFormRequestParser( $idParser );
        }
 
+       private function assertResultContainsError(
+               AddFormRequestParserResult $result,
+               ApiError $expectedError
+       ) {
+               $status = $result->asFatalStatus();
+               $errors = $status->getErrors();
+
+               assertThat(
+                       $errors,
+                       hasItem( hasKeyValuePair( 'message', 
$expectedError->asApiMessage() ) )
+               );
+       }
+
 }
diff --git a/tests/phpunit/mediawiki/Api/AddFormTest.php 
b/tests/phpunit/mediawiki/Api/AddFormTest.php
index 3a655c6..b1c8f4d 100644
--- a/tests/phpunit/mediawiki/Api/AddFormTest.php
+++ b/tests/phpunit/mediawiki/Api/AddFormTest.php
@@ -25,113 +25,80 @@
        /**
         * @dataProvider provideInvalidParams
         */
-       public function testGivenInvalidParameter_errorIsReturned( array 
$params, $expectedError ) {
+       public function testGivenInvalidParameter_errorIsReturned(
+               array $params,
+               array $expectedError
+       ) {
+               $this->setContentLang( 'qqq' );
                $params = array_merge(
                        [ 'action' => 'wblexemeaddform' ],
                        $params
                );
 
-               // TODO: this is ugly but apparently WikibaseApiTestCase always 
uses 'en' message reporter
-               // so uselang/errorlang set to qqx are ignored so it is needed 
to use en messages in asserts
-               if ( is_string( $expectedError ) ) {
-                       $msgKey = $expectedError;
-                       $msgParams = [];
-               } else {
-                       $msgKey = array_shift( $expectedError );
-                       $msgParams = $expectedError;
-               }
-               $errorMessage = new \Message( $msgKey, $msgParams, new 
\Language( 'en' ) );
-
                try {
                        $this->doApiRequestWithToken( $params );
-                       $this->fail( 'No API error raised' );
+                       $this->fail( 'No API error was raised' );
                } catch ( \ApiUsageException $e ) {
-                       $this->assertEquals( ApiErrorFormatter::stripMarkup( 
$errorMessage->text() ), $e->getMessage() );
+                       /** @var \ApiMessage $message */
+                       $message = $e->getMessageObject();
+
+                       $this->assertInstanceOf( \ApiMessage::class, $message );
+                       $this->assertEquals( $expectedError['message-key'], 
$message->getKey(), 'Wrong message codes' );
+                       $this->assertEquals(
+                               $expectedError['message-parameters'],
+                               $message->getParams(),
+                               'Wrong message parameters'
+                       );
+                       $this->assertEquals(
+                               $expectedError['api-error-code'],
+                               $message->getApiCode(),
+                               'Wrong api code'
+                       );
+                       $this->assertEquals(
+                               $expectedError['api-error-data'],
+                               $message->getApiData(),
+                               'Wrong api data'
+                       );
                }
        }
 
        public function provideInvalidParams() {
-               $noRepresentationsInDataParams = json_encode(
-                       [ 'grammaticalFeatures' => [] ]
-               );
-               $noGrammaticalFeaturesInDataParams = json_encode(
-                       [ 'representations' => [ 'language' => 'en', 
'representation' => 'goat' ] ]
-               );
-
                return [
                        'no lexemeId param' => [
                                [ 'data' => $this->getDataParam() ],
-                               [ 'apierror-missingparam', 'lexemeId' ]
+                               [
+                                       'message-key' => 
'apierror-missingparam',
+                                       'message-parameters' => [ 'lexemeId' ],
+                                       'api-error-code' => 'nolexemeId',
+                                       'api-error-data' => []
+                               ],
                        ],
-                       'no data param' => [ [ 'lexemeId' => 'L1' ], [ 
'apierror-missingparam', 'data' ] ],
+                       'no data param' => [
+                               [ 'lexemeId' => 'L1' ],
+                               [
+                                       'message-key' => 
'apierror-missingparam',
+                                       'message-parameters' => [ 'data' ],
+                                       'api-error-code' => 'nodata',
+                                       'api-error-data' => []
+                               ],
+                       ],
                        'invalid lexeme ID (random string not ID)' => [
                                [ 'lexemeId' => 'foo', 'data' => 
$this->getDataParam() ],
-                               'wikibase-lexeme-api-addform-lexemeid-invalid'
+                               [
+                                       'message-key' => 
'wikibase-lexeme-api-error-parameter-not-lexeme-id',
+                                       'message-parameters' => [ 'lexemeId', 
'foo' ],
+                                       'api-error-code' => 'bad-request',
+                                       'api-error-data' => []
+                               ]
                        ],
-                       'invalid lexeme ID (not a lexeme ID)' => [
-                               [ 'lexemeId' => 'Q11', 'data' => 
$this->getDataParam() ],
-                               [ 
'wikibase-lexeme-api-addform-lexemeid-not-lexeme-id', 'Q11' ],
-                       ],
-                       'data not a well-formed JSON' => [
+                       'data not a well-formed JSON object' => [
                                [ 'lexemeId' => 'L1', 'data' => '{foo' ],
-                               'wikibase-lexeme-api-addform-data-invalid-json'
-                       ],
-                       'data not an array' => [
-                               [ 'lexemeId' => 'L1', 'data' => json_encode( 
'foo' ) ],
-                               'wikibase-lexeme-api-addform-data-not-array'
-                       ],
-                       'no representations in data' => [
-                               [ 'lexemeId' => 'L1', 'data' => 
$noRepresentationsInDataParams ],
-                               
'wikibase-lexeme-api-addform-data-representations-key-missing'
-
-                       ],
-                       'no grammatical features in data' => [
-                               [ 'lexemeId' => 'L1', 'data' => 
$noGrammaticalFeaturesInDataParams ],
-                               
'wikibase-lexeme-api-addform-data-grammatical-features-key-missing'
-
-                       ],
-                       'representations not an array' => [
-                               [ 'lexemeId' => 'L1', 'data' => 
$this->getDataParam( [ 'representations' => 'foo' ] ) ],
-                               
'wikibase-lexeme-api-addform-data-representations-not-array'
-                       ],
-                       'grammatical features not an array' => [
-                               [ 'lexemeId' => 'L1', 'data' => 
$this->getDataParam( [ 'grammaticalFeatures' => 'Q1' ] ) ],
-                               
'wikibase-lexeme-api-addform-data-grammatical-features-not-array'
-                       ],
-                       'empty representation list in data' => [
                                [
-                                       'lexemeId' => 'L1',
-                                       'data' => $this->getDataParam( [ 
'representations' => [] ] )
+                                       'message-key' => 
'wikibase-lexeme-api-error-parameter-invalid-json-object',
+                                       'message-parameters' => [ 'data', 
'{foo' ],
+                                       'api-error-code' => 'bad-request',
+                                       'api-error-data' => []
                                ],
-                               
'wikibase-lexeme-api-addform-representations-empty'
-                       ],
-                       'no representation string in data' => [
-                               [
-                                       'lexemeId' => 'L1',
-                                       'data' => $this->getDataParam( [ 
'representations' => [ [ 'language' => 'en' ] ] ] )
-                               ],
-                               [ 
'wikibase-lexeme-api-addform-representation-text-missing', 0 ]
-                       ],
-                       'no representation language in data' => [
-                               [
-                                       'lexemeId' => 'L1',
-                                       'data' => $this->getDataParam( [ 
'representations' => [ [ 'representation' => 'foo' ] ] ] )
-                               ],
-                               [ 
'wikibase-lexeme-api-addform-representation-language-missing', 0 ]
-                       ],
-                       'invalid item ID as grammatical feature (random string 
not ID)' => [
-                               [
-                                       'lexemeId' => 'L1',
-                                       'data' => $this->getDataParam( [ 
'grammaticalFeatures' => [ 'foo' ] ] )
-                               ],
-                               [ 
'wikibase-lexeme-api-addform-grammatical-feature-itemid-invalid', 'foo' ]
-                       ],
-                       'invalid item ID as grammatical feature (not an item 
ID)' => [
-                               [
-                                       'lexemeId' => 'L1',
-                                       'data' => $this->getDataParam( [ 
'grammaticalFeatures' => [ 'L2' ] ] )
-                               ],
-                               [ 
'wikibase-lexeme-api-addform-grammatical-feature-not-item-id', 'L2' ]
                        ],
                ];
        }
@@ -151,7 +118,7 @@
        }
 
        public function testGivenValidData_addsForm() {
-               $lexeme = NewLexeme::havingId( new LexemeId( 'L1' ) )->build();
+               $lexeme = NewLexeme::havingId( 'L1' )->build();
 
                $this->saveLexeme( $lexeme );
 
@@ -173,7 +140,7 @@
        }
 
        public function testGivenValidData_responseContainsSuccessMarker() {
-               $lexeme = NewLexeme::havingId( new LexemeId( 'L1' ) )->build();
+               $lexeme = NewLexeme::havingId( 'L1' )->build();
 
                $this->saveLexeme( $lexeme );
 
diff --git a/tests/phpunit/mediawiki/Api/Error/ApiErrorTranslationTest.php 
b/tests/phpunit/mediawiki/Api/Error/ApiErrorTranslationTest.php
new file mode 100644
index 0000000..a5638bb
--- /dev/null
+++ b/tests/phpunit/mediawiki/Api/Error/ApiErrorTranslationTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Wikibase\Lexeme\Tests\MediaWiki\Api\Error;
+
+use Wikibase\Lexeme\Api\Error\ApiError;
+use Wikibase\Lexeme\Api\Error\FormMustHaveAtLeastOneRepresentation;
+use Wikibase\Lexeme\Api\Error\JsonFieldHasWrongType;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsNotAnItemId;
+use Wikibase\Lexeme\Api\Error\JsonFieldIsRequired;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotAJsonObject;
+use Wikibase\Lexeme\Api\Error\ParameterIsNotLexemeId;
+use Wikibase\Lexeme\Api\Error\ParameterIsRequired;
+use Wikibase\Lexeme\Api\Error\RepresentationLanguageCanNotBeEmpty;
+use Wikibase\Lexeme\Api\Error\RepresentationsMustHaveUniqueLanguage;
+use Wikibase\Lexeme\Api\Error\RepresentationTextCanNotBeEmpty;
+
+class ApiErrorTranslationTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @dataProvider provideApiErrors
+        */
+       public function testApiErrorsAreTranslated( ApiError $error, array 
$paramValues ) {
+               $apiMessage = $error->asApiMessage();
+
+               $this->assertInstanceOf( \ApiMessage::class, $apiMessage );
+               $messageKey = $apiMessage->getKey();
+               $this->assertTrue( $apiMessage->exists(), "Message 
'{$messageKey}' is not translated" );
+               $this->assertEnglishTranslationContainsAllTheParameters( 
$apiMessage, $paramValues );
+       }
+
+       public function provideApiErrors() {
+               return [
+                       ParameterIsNotAJsonObject::class => [
+                               new ParameterIsNotAJsonObject( 'param-1', 
'given-param' ),
+                               [ 'param-1', 'given-param' ]
+                       ],
+                       JsonFieldIsRequired::class => [
+                               new JsonFieldIsRequired( 'param-1', [ 'a', 1, 
'b' ] ),
+                               [ 'param-1', 'a/1/b' ]
+                       ],
+                       JsonFieldIsNotAnItemId::class => [
+                               new JsonFieldIsNotAnItemId( 'param-1', [ 'a', 
1, 'b' ], 'foo' ),
+                               [ 'param-1', 'a/1/b', 'foo' ]
+                       ],
+                       JsonFieldHasWrongType::class => [
+                               new JsonFieldHasWrongType( 'param-1', [ 'a', 1, 
'b' ], 'string', 'array' ),
+                               [ 'param-1', 'a/1/b', 'string', 'array' ]
+                       ],
+                       ParameterIsNotLexemeId::class => [
+                               new ParameterIsNotLexemeId( 'param-1', 'foo' ),
+                               [ 'param-1', 'foo' ]
+                       ],
+                       ParameterIsRequired::class => [
+                               new ParameterIsRequired( 'param-1' ),
+                               [ 'param-1' ]
+                       ],
+                       RepresentationTextCanNotBeEmpty::class => [
+                               new RepresentationTextCanNotBeEmpty( 'param-1', 
[ 'a', 1, 'b' ] ),
+                               []
+                       ],
+                       RepresentationLanguageCanNotBeEmpty::class => [
+                               new RepresentationLanguageCanNotBeEmpty( 
'param-1', [ 'a', 1, 'b' ] ),
+                               []
+                       ],
+                       FormMustHaveAtLeastOneRepresentation::class => [
+                               new FormMustHaveAtLeastOneRepresentation( 
'param-1', [ 'a', 1, 'b' ] ),
+                               []
+                       ],
+                       RepresentationsMustHaveUniqueLanguage::class => [
+                               new RepresentationsMustHaveUniqueLanguage(
+                                       'param-1',
+                                       [ 'representations', 1 ],
+                                       'dummy-language'
+                               ),
+                               [ 'dummy-language' ]
+                       ],
+               ];
+       }
+
+       private function assertEnglishTranslationContainsAllTheParameters(
+               \Message $apiMessage,
+               array $paramValues
+       ) {
+               $text = $apiMessage->inLanguage( 'en' )->text();
+               foreach ( $paramValues as $paramValue ) {
+                       $this->assertContains( $paramValue, $text );
+               }
+       }
+
+}
diff --git 
a/tests/phpunit/mediawiki/Api/Error/RepresentationsMustHaveUniqueLanguageTest.php
 
b/tests/phpunit/mediawiki/Api/Error/RepresentationsMustHaveUniqueLanguageTest.php
new file mode 100644
index 0000000..fd8a83a
--- /dev/null
+++ 
b/tests/phpunit/mediawiki/Api/Error/RepresentationsMustHaveUniqueLanguageTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Wikibase\Lexeme\Tests\MediaWiki\Api\Error;
+
+use Wikibase\Lexeme\Api\Error\RepresentationsMustHaveUniqueLanguage;
+
+class RepresentationsMustHaveUniqueLanguageTest extends 
\PHPUnit_Framework_TestCase {
+
+       public function testApiMessageHasUnprocessableRequestCode() {
+               $apiError = new RepresentationsMustHaveUniqueLanguage( 
'some-param', [], 'some-language' );
+
+               $apiMessage = $apiError->asApiMessage();
+
+               $this->assertEquals( 'unprocessable-request', 
$apiMessage->getApiCode() );
+       }
+
+       public function testApiMessageHasFieldPathInData() {
+               $fieldPath = [ 'a', 1, 'b' ];
+               $apiError = new RepresentationsMustHaveUniqueLanguage(
+                       'some-param',
+                       $fieldPath,
+                       'some-language'
+               );
+
+               $apiMessage = $apiError->asApiMessage();
+
+               assertThat(
+                       $apiMessage->getApiData(),
+                       hasKeyValuePair( 'fieldPath', equalTo( $fieldPath ) )
+               );
+       }
+
+       public function 
testApiMessageHasDataAttributeWithParameterNameInWhichErrorOccured() {
+               $apiError = new RepresentationsMustHaveUniqueLanguage( 
'some-param', [], 'some-language' );
+
+               $apiMessage = $apiError->asApiMessage();
+
+               assertThat(
+                       $apiMessage->getApiData(),
+                       hasKeyValuePair( 'parameterName', equalTo( 'some-param' 
) )
+               );
+       }
+
+}
diff --git a/tests/qunit/entityChangers/FormChanger.tests.js 
b/tests/qunit/entityChangers/FormChanger.tests.js
index 55bf114..332fd24 100644
--- a/tests/qunit/entityChangers/FormChanger.tests.js
+++ b/tests/qunit/entityChangers/FormChanger.tests.js
@@ -31,17 +31,18 @@
                var gotParameters = callArguments[ 1 ];
                var gotData = JSON.parse( gotParameters.data );
 
-               assert.equal( 'csrf', gotTokenType, 'Token type' );
-               assert.equal( 1, gotParameters.bot, 'BOT flag' );
-               assert.equal( lexemeId, gotParameters.lexemeId, 'lexemeId 
parameter' );
+               assert.equal( gotTokenType, 'csrf', 'Token type' );
+               assert.equal( gotParameters.errorformat, 'plaintext', 'Plain 
text error format' );
+               assert.equal( gotParameters.bot, 1, 'BOT flag' );
+               assert.equal( gotParameters.lexemeId, lexemeId, 'lexemeId 
parameter' );
                assert.deepEqual(
-                       [ { language: 'en', representation: 'test 
representation' } ],
                        gotData.representations,
+                       [ { language: 'en', representation: 'test 
representation' } ],
                        'Representation list'
                );
                assert.deepEqual(
-                       [ 'Q1', 'Q2' ],
                        gotData.grammaticalFeatures,
+                       [ 'Q1', 'Q2' ],
                        'Grammatical feature set'
                );
        } );
@@ -86,4 +87,54 @@
                } ).catch( done );
        } );
 
+       QUnit.test(
+               'New form - save fails with errors - converts errors to single 
RepoApiError',
+               function ( assert ) {
+                       var done = assert.async();
+
+                       var api = {
+                               postWithToken: function () {
+                                       return $.Deferred().reject(
+                                               'some-generic-error-code',
+                                               {
+                                                       errors: [
+                                                               createError( 
'error-code-1', 'Some text 1' ),
+                                                               createError( 
'error-code-1', 'Some text 2' )
+                                                       ],
+                                                       '*': 'Some info'
+                                               }
+                                       ).promise();
+                               }
+                       };
+
+                       var changer = new FormChanger( api, 'L1' );
+
+                       var form = new Form( null, null, [] );
+
+                       changer.save( form ).catch( function ( error ) {
+                               assert.ok(
+                                       error instanceof wb.api.RepoApiError,
+                                       'Error is instance of RepoApiError'
+                               );
+                               assert.ok(
+                                       error.detailedMessage.indexOf( 'Some 
text 1' ) > -1,
+                                       'Detailed message contains text of the 
first error'
+                               );
+                               assert.ok(
+                                       error.detailedMessage.indexOf( 'Some 
text 2' ) > -1,
+                                       'Detailed message contains text of the 
second error'
+                               );
+                               done();
+                       } );
+
+                       function createError( code, text ) {
+                               return {
+                                       code: code,
+                                       data: {},
+                                       module: 'wblexemeaddform',
+                                       '*': text
+                               };
+                       }
+               } );
+
 }( jQuery, wikibase, QUnit, sinon ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/378918
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I5771ad0446074cc98ad22fe172ba286550c3bab4
Gerrit-PatchSet: 43
Gerrit-Project: mediawiki/extensions/WikibaseLexeme
Gerrit-Branch: master
Gerrit-Owner: Aleksey Bekh-Ivanov (WMDE) <[email protected]>
Gerrit-Reviewer: Aleksey Bekh-Ivanov (WMDE) <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Jakob <[email protected]>
Gerrit-Reviewer: Jonas Kress (WMDE) <[email protected]>
Gerrit-Reviewer: Lydia Pintscher (WMDE) <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to