I build a web-API using servant
that grows increasingly large.
I am aware of two ways to automatically create documentation for the api.
First, there's haddock. Haddock turns my code into hyperlinked HTML-pages. Neat! This is especially helpful, because my api endpoints tend to stretch out over several modules and now I can browse through them and find relevant type information.
However, haddock
doesn't exactly have a way of displaying lines like these properly:
type Public =
"new" :> ReqBody '[JSON] UserNewRequest :> Post '[JSON] UserNewResponse
:<|> "exists" :> ReqBody '[JSON] UserExistsRequest :> Post '[JSON] Bool
:<|> "login" :> ReqBody '[JSON] LoginRequest :> Post '[JSON] LoginResponse
Haddock turns it into something like this:
type Public = ("new" :> (ReqBody '[JSON] UserNewRequest :> Post '[JSON] UserNewResponse)) :<|> (("exists" :> (ReqBody '[JSON] UserExistsRequest :> Post '[JSON] Bool)) :<|> ("login" :> (ReqBody '[JSON] LoginRequest :> Post '[JSON] LoginResponse)))
... even adding parentheses. Ironically, formatting is prettier in the code, simply because of the line breaks.
Second, there ist servant-docs
. However, servant-docs
quite consistently builds a documentation of the endpoints, with nice hooks to add examples that are shown e.g. in JSON. Servant-docs
doesn't aim to provide haskell type information - which is all I am after.
So either, I find a way to have haddock
display long types in a pretty way, OR I find a way to display haskell types with servant-docs
.
In both cases, it doesn't seem to fit their designs. I might need something else entirely.
What I tried already with haddock
:
type Public =
-- create new user
"new" :> ReqBody '[JSON] UserNewRequest :> Post '[JSON] UserNewResponse
-- check if user exists
:<|> "exists" :> ReqBody '[JSON] UserExistsRequest :> Post '[JSON] Bool
-- user login
:<|> "login" :> ReqBody '[JSON] LoginRequest :> Post '[JSON] LoginResponse
It's valid haskell, but the comments are ignored by haddock
. Using haddock title syntax --|
or -- *
results in haddock compile errors.
Using per endpoint type
aliases was mentioned in comments. However, newer servant
has Servant.API.Generic
(read more at https://haskell-servant.readthedocs.io/en/stable/cookbook/generic/Generic.html) which let's you write your API in more structured way:
data Public route = Public
-- | create new user
{ routeNewUser :: route :- "new" :> ReqBody '[JSON] UserNewRequest :> Post '[JSON] UserNewResponse
-- | check if user exists
, routeExists :: route :- "exists" :> ReqBody '[JSON] UserExistsRequest :> Post '[JSON] Bool
-- | user login
, routeLogin :: route :- "login" :> ReqBody '[JSON] LoginRequest :> Post '[JSON] LoginResponse
}
This approach is a bit more tricky with nested APIs, but it has a lot of benefits in "linear" apis.