This package offers kinds and types for constructing “API description types”.
These API description types---technically, types of kind Api *---contain much
of the information one needs to understand how to interact with an API. Other
packages in the Serv ecosystem adjoin a little more information to these Api
types and then use them to swiftly generate servers, clients, and
documentation.
We’ll start from the basic building blocks of Api types and move upward.
The first type of interest is called Respond hs b. It corresponds to a
possible response from the server. For example:
import Serv.Api.Prelude
type Respond1 = Respond ‘[] Emptyindicates a response with no response headers and an empty body. On the other hand,
type Respond2 =
Respond
‘[LastModified ::: UTCTime]
(Body ‘[JSON, PlainText] User)indicates that the server will respond with a User value at either a JSON or
plain text content type (depending on what the client negoatiates for). It also
sets the Last-Modified response header to a UTCTime value.
Servers which produce this type must select a UTCTime to set as
Last-Modified and must ensure that Users have encodings in both plain text
and JSON. Clients offer this data back to people and must know how to decode
User of at least one of these content types.
If you hit a HTTP server at a given endpoint with a given verb it might respond
with any number of possible Respond types corresponding to different response
statuses. The Outputs type allows us to describe this situation.
For instance, let’s say that if we GET this resource we’ll either get our
User value (with a 200 response) or nothing at all (with a 404) response.
We model this situation like so:
type Handler1 =
Ouputs
‘[ Ok ::: Respond2
, NotFound ::: Respond1
]In particular, we’ve constructed a Handler-kinded type which consists of a
set of Respond types corresponding to every error code which may be returned
by the server.
At the handler level we might also suggest that we need more data from the request. For instance, we have the following types
CaptureBody contentTypes ty nextHandler
CaptureHeaders headerSpec nextHandler
CaptureQuery querySpec nextHandlerwhich each modify a handler to capture some further information. For instance,
if our request must specify a User in the query then we can represent that
here
type Handler2 = CaptureQuery ‘[“id” ::: UserId] Handler1As a side note we might go ahead and explain this
(:::)type operator that keeps showing up: it’s just syntax sugar for a type-level tuple so thata ::: bis the same as’(a, b). Infix syntax can be convenient for theseApitypes, but it’s completely optional.
We’re finally ready to specify a full Api. To do this, we just need to bundle
a few Handlers under a specific Endpoint. For instance, let’s say we have
some more Handlers numbered 3 and 4, here’s our first Endpoint
type Api1 =
Endpoint ()
‘[ GET ::: Handler2
, PUT ::: Handler3
, DELETE ::: Handler4
]That’s all there is to it! (Ignore the () argument. It’s useful later for
annotation and documentation.)
Well, no, because we often want to describe whole Apis where there are
choices of Endpoints and they exist at various paths.
type Api2 =
OneOf
‘[ Const “user” :> Api1
, Const “book” :> Api2
, Const “car” :> OneOf ‘[ Const “sedan” :> Api3, Const “truck” :> Api4 ]
[We might also want to capture data from the path as we go down it. For this we
just replace Const path segments with Seg capture types
type Api3 =
Seg “factory” FactoryId :> Seg “employee” EmployeeId :> ApiFactoryEmployeeThat’s basically all you need to know to start describing Apis! There are
more types available for this purpose, but these are the basics. Feel free to
examine the documentation for Serv.Api to learn more.