Skip to main content

Refuse requests that do not match a schema

The orders API takes a new order as a JSON body in a POST to /orders. A body with an amount of zero, or in a currency the backend does not handle, should never reach the backend. request-validation reads each request body against a JSON Schema at the gateway. A body that does not match is answered by the gateway and is not passed on.

The check belongs on a route that takes only the new orders. On a route that also answers reads, a GET without a body is refused as well. This guide creates the writes route orders-writes beside orders-api and puts the check on it. At the end, an order with a positive amount in EUR or USD reaches your backend, and one with an amount of 0, or in GBP, is answered 422 with a sentence you choose.

Before you start​

  • This guide starts from orders-api as Route requests to a backend leaves it, with key-auth from Require an API key. A plugin from another guide that this page does not name can change the answers below; remove it first or work on a copy of the route.
  • orders-api takes GET and POST for /orders and everything under it, and sends them to the backend nodes 10.0.1.10:8080 and 10.0.1.11:8080.
  • The host name of your gateway. In the console it is the first part of the Address line on a route's Overview tab, api.example.com in this page's examples; replace it in every request below.
  • The requests below post to /orders. The one under What to know reads /orders/42.
  • The fields a new order must carry and the values each may take. The team that builds the backend knows them. When its own tests hold a schema, start from that one, so the gateway and the backend refuse the same bodies. This guide asks for an integer amount of at least 1 and a currency of EUR or USD.
  • The Editor or Environment administrator role in this environment.

Steps​

1. Create the writes route​

In the menu, select Routes, then New route. Fill it in the way Route requests to a backend fills in orders-api, with these differences.

FieldValueWhy
Nameorders-writesA name that tells the two routes apart.
Paths/ordersThe path a new order is posted to.
MethodsPOST onlyReads stay on orders-api, where no schema applies.
Priority10orders-api takes POST on /orders too. Of two routes that share a path, the one with the higher priority is tried first. The Priority of orders-api is empty, which counts as 0, so 10 wins.

Under Target, send it to the same two backend nodes as orders-api, 10.0.1.10:8080 and 10.0.1.11:8080, as in Route requests to a backend. Select Create route.

2. Choose request-validation​

Open orders-writes under Routes, select its Plugins tab, then Add plugin at the top right of the tab. Open the Traffic category, or type request-validation in the search box. Select request-validation, then Configure. A panel with the plugin's form opens.

3. Write the body schema​

Body schema is a box that takes JSON. Paste the schema of a new order into it. It asks for an object with an integer amount of at least 1 and a currency of EUR or USD.

{
"type": "object",
"required": [
"amount",
"currency"
],
"properties": {
"amount": {
"type": "integer",
"minimum": 1
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD"
]
}
}
}

Leave Header schema empty. This guide checks the body only.

4. Choose the refusal and add the plugin​

Fill in two more fields and leave Request body limit as it is.

FieldValueWhy
Rejected code422The status a refused order gets.
Rejected messageThe order body was not understood.The whole body of the refusal. Left empty, the client reads the validator's own sentence, which names the properties of your schema.

Select Add plugin at the bottom of the panel. The tab now lists request-validation, and the plugin is saved like this:

The Add request-validation panel with the JSON Schema an order body has to match.
request-validation · as the console saves it
{
"body_schema": {
"type": "object",
"required": [
"amount",
"currency"
],
"properties": {
"amount": {
"type": "integer",
"minimum": 1
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD"
]
}
}
},
"rejected_code": 422,
"rejected_msg": "The order body was not understood."
}

From now on, every new order whose body does not match the schema is answered by the gateway, and your backend never sees it.

5. Ask for a key on the writes route​

orders-writes does not carry the plugins of orders-api, so a new order needs no key yet. Add key-auth to orders-writes as steps 1 to 3 of Require an API key show, on orders-writes instead of orders-api.

Check it​

Post an order with an amount of 0, then one in GBP, then a valid one with an amount of 5 in EUR. With key-auth on orders-writes from step 5, send the key of mobile-app in the x-api-key header with every request below. The first one:

curl
curl -i -L -X POST 'https://<gateway host>/orders' \
-H 'Content-Type: application/json' \
-d '{"amount":0,"currency":"EUR"}'

The valid order:

curl
curl -i -L -X POST 'https://<gateway host>/orders' \
-H 'Content-Type: application/json' \
-d '{"amount":5,"currency":"EUR"}'
An amount of zero
An order whose amount is 0 is answered 422 with The order body was not understood.; the backend never sees it.
A currency the schema does not list
An order in a currency the schema does not list, GBP, is answered 422 with the same message.
A valid order
An order with a positive amount in euros is passed on and answered 200 by the backend.

The body of the last answer is whatever your backend answers to a new order.

What to know​

  • A request that orders-writes takes runs only the plugins of orders-writes, and none of those on orders-api. Put on the writes route every plugin a new order must pass, as step 5 does for key-auth.
  • The plugin checks every request that reaches its route, reads included. On a route that answers reads, it refuses a plain GET for /orders/42, because a request without a body does not match the schema.
curl
curl -i -L 'https://<gateway host>/orders/42'
A GET on a route that carries the plugin
A GET without a body on a route that carries this plugin is answered 422 too, so the plugin belongs on the route that takes the writes and not on the one that answers reads.
  • A body that passes does not reach the backend byte for byte. The backend gets the parsed JSON written out again, with keys reordered and spacing and number formatting changed.
  • Rejected code also takes a 2xx status. The refused request is then still not passed on, but the client reads a success status.
  • A body longer than Request body limit is refused with the Rejected code before the schema is applied.
  • Header schema checks the headers the same way. A header reaches that schema as text, so a property typed as a number never matches.
  • A plugin with neither a body schema nor a header schema is refused when you save.
G8 dev