How the mock picks a response
apigenie-server openapi.yaml answers every request from the document, without a line of code.
This page explains the decisions behind one answer, in the order the server takes them, so that
you can predict what a request gets and change it where you need to. The
Responses page of the reference states the same rules in
short.
The examples run against this document, saved as pets.yaml and served with
apigenie-server pets.yaml; curl -i shows the headers that say what was decided.
openapi: 3.1.0
info: {title: Pets, version: 1.0.0}
servers:
- url: https://api.example.com/v1
paths:
/pets/{id}:
get:
operationId: getPet
parameters:
- {name: id, in: path, required: true, schema: {type: integer}}
responses:
"200":
description: The pet
content:
application/json:
schema: {$ref: "#/components/schemas/Pet"}
examples:
cat: {value: {id: 1, name: Tom, kind: cat}}
dog: {value: {id: 2, name: Rex, kind: dog}}
"404":
description: No such pet
default:
description: An error
components:
schemas:
Pet:
type: object
required: [id, name]
properties:
id: {type: integer}
name: {type: string}
kind: {type: string, enum: [cat, dog]}
1. The route
The paths of the document are served under the path of its first server, /v1 here, and as they
are: GET /v1/pets/1 and GET /pets/1 both reach getPet. --base-path replaces the prefix,
and server.strict_base_path in the configuration serves the paths only under it. When a literal
path and a templated one both match, the literal one wins: /pets/mine over /pets/{id}.
A path the document doesn’t have is answered with 404, a method the path doesn’t declare with 405
and an Allow header, both as problem details:
$ curl -i -X DELETE http://127.0.0.1:4010/v1/pets/1
HTTP/1.1 405 Method Not Allowed
Content-Type: application/problem+json
Allow: GET
2. The request is checked
Before an answer is chosen, the request is checked against its operation: the parameters, the body and the security requirements. A request that doesn’t match gets 422 with the problems instead of an answer, and one without the credentials a security requirement asks for gets 401:
$ curl -i http://127.0.0.1:4010/v1/pets/abc
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://apigenie.pl/mock/errors#invalid-request",
"title": "The request does not match the specification",
"status": 422,
"detail": "path.id: expected type integer, found string",
...
validation.request: log in the configuration reports such a request and answers it anyway. The
__ query parameters of the next step are removed before the check, so ?__code=404 is not an
unknown parameter. Validation and security lists what is
checked.
3. Who answers
Several sources can answer a request. From the one that wins to the last resort:
- A script. The
onRequesthook of a script that returns a response answers before the document’s operations. - The request itself. The
Preferheader, or the__query parameters, ask for a status, a named example, a generated body, a delay or a dropped connection:Prefer: code=404,?__example=dog. - Scenarios and overrides. The rules of the scenarios that are on (
--scenario, the configuration,x-apigenie-mockin the document, the admin API) and theoperationsoverrides of the configuration set the status, an example, a body or headers for the requests they match. - Stubs. With
--stubs DIR, a recorded exchange whose method, path, query and body match answers (see Record and replay). - The document. Its examples, else values generated from its schemas.
Each layer sets what it names and leaves the rest to the next. With this scenario on, getPet
answers 503:
# apigenie-server.yaml
version: "1.0"
scenarios:
outage:
description: the backend is down
rules:
- match: {operationId: getPet}
status: 503
headers: {Retry-After: "30"}
$ apigenie-server pets.yaml --scenario outage
$ curl -i http://127.0.0.1:4010/v1/pets/1
HTTP/1.1 503 Service Unavailable
Retry-After: 30
X-Apigenie-Mock-Operation: getPet
X-Apigenie-Mock-Source: scenario:outage
A request with Prefer: code=200 still gets a 200 with the cat example, because the request’s
own ask comes first; one with ?__example=dog gets the 503, because the scenario set a status
that has no content, so there is no example to pick.
4. The status
When nothing above sets the status, the operation’s responses decide: the lowest 2xx response,
else 2XX, else the lowest declared status, else default. For getPet that is 200. An
operation with 204, 202 and 400 answers 202; one with 2XX alone answers 200; one with
404 and 500 only answers 404.
A status the request asks for has to be declared, by its code, a range such as 4XX, or
default: Prefer: code=418 gets a 418 here, because default covers it, and a 500 from an
operation without default, whose detail says which statuses the operation has.
5. The body
The body is picked for the status, in this order:
- The media type. Content negotiation follows the request’s
Accept; a request that accepts onlytext/plaingets 406. Without anAccept, or with*/*, the response takes the media type of the request’s body when it has one. - An example, else a generated value. In the default static mode the media type’s
example, else its first named example (cathere), else the schema’sexampleanswer. What has none is generated from the schema, and--dynamic(orresponses.mode: dynamic) generates every body. Generated values respect the types, the formats of the format registry, patterns, lengths, ranges,enum,const,allOf,oneOf,readOnlyandwriteOnly;x-fakeron a property names the generator of its values (Scenarios and extensions). - XML. A value for
application/xml,text/xmlor a+xmlmedia type is written as XML, as the schema’sxmlobjects describe it (XML bodies).
$ curl http://127.0.0.1:4010/v1/pets/1
{"id":1,"name":"Tom","kind":"cat"}
$ curl 'http://127.0.0.1:4010/v1/pets/1?__example=dog'
{"id":2,"name":"Rex","kind":"dog"}
$ curl -H 'Prefer: dynamic=true' http://127.0.0.1:4010/v1/pets/1
{"id":179,"name":"necessitatibus sint qui q"}
A status without content answers with an empty body, whatever example the request asks for.
6. The headers
The headers the response declares are filled in from their examples or schemas. Two headers of the mock’s own say what was decided, so that a surprising answer can be traced:
| Header | Says |
|---|---|
X-Apigenie-Mock-Operation |
The operation that answered: getPet. |
X-Apigenie-Mock-Source |
Where the answer came from: example:cat, schema-example, generated, scenario:outage, stub:getPet-1, empty for a status without content, proxy for a real server’s answer. |
The log prints the same two facts on each line, and the dashboard
shows them per operation. headers.diagnostics: false in the configuration leaves the two
headers out.
See also
- Responses: the rules in short, the
Preferheader and the network simulation. - Scenarios and extensions: the rules of a scenario,
x-apigenie-mockandx-faker. - Validation and security: what step 2 checks, and XML bodies.
- What the validating proxy checks: the same server in front of a real one.
- Mock and contract-test an API: the guide.