servant-checked-exceptions
Checked exceptions for Servant APIs.
https://github.com/cdepillabout/servant-checked-exceptions
LTS Haskell 23.1: | 2.2.0.1 |
Stackage Nightly 2024-12-26: | 2.2.0.1 |
Latest on Hackage: | 2.2.0.1 |
servant-checked-exceptions-2.2.0.1@sha256:ca5d0b74299f1f0888f1388c91cad0661c1f668ff56ed5dcf95f0a85006a5955,4706
Module documentation for 2.2.0.1
Servant.Checked.Exceptions
servant-checked-exceptions
provides a way to specify errors thrown by a
Servant api on the type level. It allows easy composition between different
error types.
servant-checked-exceptions
provides the
Throws
data type to signify which errors can be thrown by an api. For instance,
imagine a getAuthor
api that returns an Author
based on an AuthorId
:
-- This is a servant-compatible type describing our api.
type Api =
"author" :>
Capture "author-id" AuthorId :>
Throws DatabaseError :>
Throws AuthorNotFoundError :>
Get '[JSON] Author
-- These are the two errors that can be thrown:
data DatabaseError = DatabaseError
data AuthorNotFoundError = AuthorNotFoundError
The corresponding handler function uses the
Envelope
data type to model the possibility of returning an Author
successfully, or
either DatabaseError
or AuthorNotFoundError
unsuccessfully.
Internally, Envelope
is using an open sum-type to easily represent multiple
different errors:
getAuthorHandler
:: AuthorId
-> Handler (Envelope '[DatabaseError, AuthorNotFoundError] Author)
getAuthorHandler authorId = ...
For more documentation and usage examples, see the documentation on Hackage.
Why would I want to use this?
Using Envelope
with its open sum-type to represent errors gives us an easy
way to reuse errors on multiple routes.
For instance, imagine that we had another api for updating an author’s name,
given the author’s ID. Using Throws
and Envelope
, it might look like this:
type Api =
"update-author-name" :>
Capture "author-id" AuthorId :>
Capture "author-name" AuthorName :>
Throws DatabaseError :>
Throws AuthorNotFoundError :>
Throws AuthorNameTooShort :>
Post '[JSON] Author
data AuthorNameTooShort = AuthorNameTooShort
postChangeAuthorName
:: AuthorId
-> AuthorName
-> Handler (Envelope '[DatabaseError, AuthorNotFoundError, AuthorNameTooShort] Author)
postChangeAuthorName authorId newAuthorName = ...
We are able to reuse the DatabaseError
and AuthorNotFoundError
. If we try
to return an error that is not declared using Throws
, GHC will give us an
error. We get flexiblity and type-safety.
When using servant-docs to
create documentation, only one instance of ToSample
needs to be created for
each error (DatabaseError
, AuthorNotFoundError
, and AuthorNameTooShort
).
Multiple instances of ToSample
do not need to be created for every
different Envelope
used in a handler.
Example
This repository contains an example of using
servant-checked-exceptions
. This includes an api,
server, client, and
documentation.
Below I show how to compile and run these examples.
Compile
The examples can be compiled by using the buildexample
flag:
$ stack build --flag servant-checked-exceptions-core:buildexample --flag servant-checked-exceptions:buildexample
This creates three executables. A server, a client, and a documentaiton generator.
Run the server
The server is a small example that will take search queries and return results. The server can be run with the following command:
$ stack exec -- servant-checked-exceptions-example-server
This runs the server on port 8201. Here is an example of using curl
to
access the server. This will send the query hello
:
$ curl \
--request POST \
--header 'Accept: application/json' \
'http://localhost:8201/lax-search/hello'
{"data":"good"}
If you try to send a query that is not hello
, the server will return an error:
$ curl \
--request POST \
--header 'Accept: application/json' \
'http://localhost:8201/lax-search/goodbye'
{"err":"BadSearchTermErr"}
There is also a strict api, that requires hello
to be capitalized like Hello
:
$ curl \
--request POST \
--header 'Accept: application/json' \
'http://localhost:8201/strict-search/hello'
{"err":"IncorrectCapitalization"}
$ curl \
--request POST \
--header 'Accept: application/json' \
'http://localhost:8201/strict-search/Hello'
{"data":"good"}
Run the client
The client provides a small command line application to query the server. In order to use the client, the server must be running.
Use the client to access the lax search api:
$ stack exec -- servant-checked-exceptions-example-client foobar
the search term was not "Hello"
$ stack exec -- servant-checked-exceptions-example-client hello
Success: good
Use the client to access the strict search api:
$ stack exec -- servant-checked-exceptions-example-client --strict hello
the search term was not capitalized correctly
$ stack exec -- servant-checked-exceptions-example-client --strict Hello
Success: good
Run the documentation generator
The documentation generator will generate documentation for the api in Markdown:
$ stack exec -- servant-checked-exceptions-core-example-docs
Here is a small example of the documentation that will be generated for the lax search api:
## POST /lax-search/:query
#### Captures:
- *query*: a search string like "hello" or "bye"
#### Response:
- Status code 200
- Headers: []
- Supported content types are:
- `application/json`
- This is a successful response.
{"data":"good"}
- a completely incorrect search term was used
{"err":"BadSearchTermErr"}
You can see that both the success and error responses are documented.
Packaging the core types
servant-checked-exceptions-core
exports the core types need for building an API with checked exceptions,
allowing you to avoid depending on server-side libraries like warp
, Glob
and servant-server
. This can be useful if you are writing an API meant to be
shared with ghcjs and run in a browser, where these dependencies aren’t
available.
Limitations
Currently, servant-client
only treats HTTP responses as successful if they
have a status code of 2XX. This means that any non-2XX errors thrown by
servant-checked-exceptions
don’t get parsed into a typed Envelope
as
expected, but raised as a Servant ClientError
. For more information, see
issue #27.
Maintainers
Changes
2.2.0.1
- Fix small import problem in the tests. #38
2.2.0.0
-
Add the
EnvelopeT
monad transformer. #32 -
Add a few combinators for
Envelope
:envelopeRemove
envelopeHandle
relaxEnvelope
liftA2Envelope
bindEnvelope
-
Add an example of using
EnvelopeT
inservant-checked-exceptions/example/EnvelopeT.hs
. #32
2.1.0.0
- Add support for servant-0.16 and remove support for all previous version of servant. #31 Thanks Schell Carl Scivally!
2.0.0.0
-
Split into two package
servant-checked-exceptions-core
andservant-checked-exceptions
. The former defines the core types and functions for using checked exceptions in a servant API; the latter reexports the former and adds instances forHasServer
andHasClient
. The rationale is described further in issue 25Most users should only depend on
servant-checked-exceptions
. But users who need access to core types without incurring a dependency onservant-server
andservant-client
can depend onservant-checked-exceptions-core
instead. -
Split
Exceptions
module intoEnvelope
andVerbs
inservant-checked-exceptions-core
, for better module organization. More information in issue 18
1.1.0.0
- Updated the servant dependency to >= 0.12.
1.0.0.0
- Add a
ErrStatus
class that can be used to set the HTTP Status Code. Given an endpoint that returns aEnvelope '[e1, e2] a
, you must declare an instance ofErrStatus
fore1
ande2
. This is a breaking change.
0.4.1.0
- Add
NoThrow
type to represent handlers that don’t throw any errors, but do return a result wrapped in anEnvelope
.