Designing Responses
Praxis allows API designers to define the set of response templates the
application can return. By designing a response, you are describing a set of
expectations about its structure and attributes. You then give it a name and
tell Praxis about it using response_template
.
Here is an example of how to design a response definition:
This response definition is named ok
, it has a human-readable description (that will be be rendered as markdown and html by the doc browser),
and Praxis expects it to always:
- have a status code of 200
- return an “application/json” media type (the value of the Content-Type header)
- contain, at least, the following two headers:
X-Header1
which must always match a literal value “foo”X-Header2
which must always match the regular expression/bar/
Any action may refer to a response by name when declaring its list of expected
responses. For example, here is a snippet of an action definition describing
that it can return the ok
response defined above.
Description text is copied as is in the generated JSON files. However, the Doc browser will appropriately render any markdown or HTML tags that it contains.
See ActionDefinitions for more information about how to specify which responses an action can return.
Having responses that have all fields statically defined makes it more
difficult to reuse them for different actions. For example, you may want to use
the ok
definition above for an action that returns an a media-type other than
application/json
.
For this reason, response definitons are parameterizable via block parameters.
For example, introducing a media_type
parameter in our previous ok
response
could be done like this:
Parameterized responses allow you to reuse registered responses in action definitions with some flexibility:
This example response definition has not just a media_type
parameter but
also a default value of ‘application/json’. This allows you to override the
media_type
only when necessary. If you don’t specify the media_type
,
you’ll get application/json
. If you don’t specify a default, you will have to
explicitly pass a value in every action that uses this response definition.
Defining response templates allows the API designer to describe a typical set of parametrizable response expectations, identified by name, so that actions can refer to them and customize them.
Preregistered Responses
Praxis comes with many common response definitions pre-registered. Some of them also have customizable parameters. Here are examples of some response definitions that are already registered:
And there are many others. In particular, Praxis automatically creates a
response definition for every Response class in
responses/http.rb.
Each of those definitions will have the name and status code specified in the
self.response_name
and self.status
of those classes.
You can use any of these response definitions within your application without having to register them yourself. You can also override any of them with your own definition if the default one does not suit your needs.
Defining Response Expectations
When defining a response, you may include expectations for: status code, headers, location, media_type and multipart parts.
All expectations in definitions are optional and potentially parameterizable with the exception of status code. A status code must always be defined and set statically. It is possible for multiple responses to share the same status code as they might differ in other expectations (i.e. different headers) which may be enough to warrant a different name.
Headers
The headers
DSL in a response definition can take either a Hash, Array or a
String object:
- Hash
- a set of header names in the Hash keys and a set of corresponding values for
each one. This way enforces not only that the header names exist in the
response, but also that their contents match the given value. Values to match
can be one of two types:
- String values: will perform a literal match of the header contents
- Regexp values: will match the header contents agains the provided regular expression.
- Array
- a set of header names as array elements. This enforces that headers with the given names exist; actual values are not checked.
- String
- a single string enforces that the named header exists; its value is not checked.
Here’s an example of each type of header definition:
MediaType
Use the media_type
method to set expectations for the media type of a
response. The media_type
method can accept:
- a string indicating the full Internet media type identifier
- a complete Praxis MediaType class
For example:
Using a string for the media-type is equivalent to defining the ‘Content-Type’
header with a string value. The reason for providing a media-type DSL is just a
convenience since (just like Location
) it is a common header to use.
Location
A response definition can directly define a ‘Location’ header using the
location
method. This method can accept the same value matchers as any other
headers, a string or a regular expression to match against the header’s value.
Multipart responses
Praxis also allows you to define expectations on the individual parts of a
multipart response. You can do this by using the parts
DSL.
Currently, the parts
DSL only allows you to define a common template
definition to which all parts must comply. In the future, we will extend the
DSL to allow you to define different expectations for each individual part.
The idea behind native multipart support in Praxis is that it provides a very clean and consistent way to handle bulk operations. So Praxis makes it straightforward to define a multipart reponse as a way to ‘package’ a list or related sub-responses (i.e. the outcome of a bulk operation).
For this reason, the DSL lets you define parts as if they were complete sub-requests. This can be done in either of the following ways:
- pass a block with a full response definition. This allows you to define a part inline, as if it were a new and complete sub-request.
- pass a
like
parameter which names an existing response definition
Since a multipart part does not have a status code, Praxis will enforce the expectation by looking at the value of a special ‘Status’ header. The rest of the fields (headers, location and media_type) are native fields of the part.
Here are two examples of how to add part expectations using both of these approaches. Using a response block:
Using the :like
option:
Obviously, good reuse of these definitions requires parameterization, so a more realistic definition of a multipart response could look like this:
You could allow an action to customize parts using the like
option or by
passing a full response definition block. Here are some more examples:
The only reason to not pass the block directly in the response line is that
Ruby would never pass it to the parts
parameter, but rather to the response
function. Splitting into a different line to create a proc
looks cleaner than
adding the right parenthesis in one line.
For more information on multipart responses, please see Responses.