Good validation error messages make API consumers happy
All input to an API must be validated to avoid unintended or disallowed use. Invalid input should return a status code in the 4XX range (e.g 400, 403 or 409). But this is not enough.
When I as an API consumer receive 400 (Bad request) I am able to understand that something is wrong with the request but without more information I will have a hard time to understand what is wrong with my request. I have to search in documentation to understand what is failing. That is – if there is any documentation… Or else I would resort to trail and error or maybe get in contact with the team providing the API to be able to proceed. This is time-consuming and frustrating – a bad developer experience.
A good API must return detailed feedback to the client to inform on what is failing.
Invalid request example:
POST /examples/hello-you
{
"name": "Peter Andersson"
}
Detailed error message example (based on JSON Schema validation):
400
{
"errors": {
"/body/name": {
"rejectedValue": "Peter Andersson",
"violations": {
"/properties/body/additionalProperties": false,
"description": "Property 'name' has not been defined and schema does not allow additional properties"
}
},
"/body": {
"rejectedValue": {
"name": "Peter Andersson"
},
"violations": {
"/properties/body/required": [
"firstName",
"lastName"
]
}
}
},
"valid": false
}
The error is concerning the /body/name which contains an unsupported field (name) and /body is required to have two fields named firstName and lastName.
Informative error messages to the client
There are different ways to implement detailed feedback to the API client. One way is to write custom conditions (if-then-else statements) and provide custom error messages for each field to be validated. This may work when validation only concerns a few fields. Another approach is to use a schema for validation. This approach works for any size of input and makes it easy to unify the error messages into one format for all input validations.
JSON Schema
JSON Schema is a format for describing JSON structures. A schema for validating the input according to the examples above could look like this:
{
"type": "object",
"properties": {
"body": {
"type": "object",
"required": [
"firstName",
"lastName"
],
"properties": {
"firstName": {
"type": "string",
"maxLength": 10
},
"lastName": {
"type": "string",
"maxLength": 10
}
},
"additionalProperties": false
}
}
}
Try it out: Hello You API with detailed error messages
Example of invalid call to /examples/hello-you:
curl --location --request POST 'https://api.zuunr.com/examples/hello-you' \
--header 'Content-Type: application/json' \
--data-raw '{"name":"Peter Andersson"}'
Example of a valid call to /examples/hello-you:
curl --location --request POST 'https://api.zuunr.com/examples/hello-you' \
--header 'Content-Type: application/json' \
--data-raw '{"firstName":"Peter", "lastName": "Andersson"}'
Create API: Hello You API with JSON Schema input validation
Below is the full configuration to create the Hello You API using the JSON Schema from the example above. It consists of one processor – onRequest:
- validates the input and puts the result on the model in
/requestBodyValidationResult - based on the value of
/requestBodyValidationResult/valid- …either puts the failing validation result on the /response/body and sets status to 400
- …or puts a concatenated string to /response/body/message and sets status to 200
- return the /response by putting sendResponse /next
Copy the snippet, replace {{SANDBOX_AUTH}} and {{SANDBOX_BASE_PATH}} and execute the command:
curl --location --request POST 'https://api.zuunr.com/endpoint-configs' \
--header 'Authorization: {{SANDBOX_AUTHORIZATION}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"path": "{{SANDBOX_BASE_PATH}}/hello-you",
"method": "post",
"processors": {
"onRequest": {
"Translator": {
"transformations": [
{
"/requestBodyValidationResult": {
"$schema.validate": {
"schema": {
"type": "object",
"properties": {
"body": {
"type": "object",
"required": [
"firstName",
"lastName"
],
"properties": {
"firstName": {
"type": "string",
"maxLength": 10
},
"lastName": {
"type": "string",
"maxLength": 10
}
},
"additionalProperties": false
}
}
},
"value": "$/request",
"outputFormat": "WEB_API_ERRORS_OBJECT"
}
}
},
{
"/response": {
"$if": "$/requestBodyValidationResult/valid",
"$then": {
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"message": {
"$concat": [
"Hello ",
"$/request/body/firstName",
" ",
"$/request/body/lastName",
"!"
]
}
}
},
"$else": {
"status": 400,
"body": "$/requestBodyValidationResult"
}
}
},
{
"/next": "sendResponse"
}
]
}
}
}
}'
And here is how to call your version of the API.
Copy the snippet, replace {{SANDBOX_BASE_PATH}} and execute the command:
curl --location --request POST 'https://api.zuunr.com{{SANDBOX_BASE_PATH}}/hello-you' \
--header 'Content-Type: application/json' \
--data-raw '{"firstName":"Peter", "lastName": "Andersson"}'
Detailed explanation
Please let med know if you want more details on this example 🙂
