Search code examples
haskelldocumentation-generationhaddockservant

Auto-generate Servant-API documentation for Haskell developers


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.


Solution

  • 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.