Home > Software engineering >  Haskell Servant - What is the Purpose of serveWithContext and What Does it do That a ReaderT Can
Haskell Servant - What is the Purpose of serveWithContext and What Does it do That a ReaderT Can

Time:11-29

I'm trying to understand the purpose of Servant's serveWithContext function. The documentation states that it's not a replacement for the ReaderT Monad, but I'm uncertain as to what problem it's trying to solve that isn't already addressed by ReaderT.

For example, this is from the example on the servant-auth GitHub page:

unprotected :: CookieSettings -> JWTSettings -> Server Unprotected
unprotected cs jwts = checkCreds cs jwts :<|> serveDirectory "example/static"

server :: CookieSettings -> JWTSettings -> Server (API auths)
server cs jwts = protected :<|> unprotected cs jwts
let jwtCfg = defaultJWTSettings myKey
      cfg = defaultCookieSettings :. jwtCfg :. EmptyContext
      api = Proxy :: Proxy (API '[JWT])
  _ <- forkIO $ run 7249 $ serveWithContext api cfg (server defaultCookieSettings jwtCfg)

It seems that serveWithContext is being used to pass Cookie and JWT settings to the handler, but I don't see why this couldn't be accomplished with a ReaderT. Furthermore, serveWithContext appears to be passing these values in twice: once as a Context object bound to cfg and again as parameters in the function call server defaultCookieSettings jwtCfg.

I'd appreciate it if someone could demystify Servant's Context type for me.

CodePudding user response:

It seems like the machinery of Servant doesn't make assumptions about the underlying monad in which you choose to define the handlers. This means it can't force you to choose any particular monad (like, say, ReaderT) in response to some combinator present in the routes, and it doesn't "react" to your choice of monad in order to enable some behavior.

In the case of servant-auth, we need to provide some extra information to servant about how to handle cookies and the like.

What the Context system does it to collect that extra information in a positional parameter for route, hoistServerWithContext and serveWithContext, while still letting you choose whatever monad you wish. The exact type of the parameter depends on what route combinators are present in the API.

The servant tutorial has some paragraphs about Contexts:

When someone makes a request to our "private" API, we’re going to need to provide to servant the logic for validating usernames and passwords. [...] Servant 0.5 introduced Context to handle this. [...] the idea is simple: provide some data to the serve function, and that data is propagated to the functions that handle each combinator.

As for

Furthermore, serveWithContext appears to be passing these values in twice

I'm not sure, but I suspect checkCreds taking cs and jwts as parameters is done only as an example of how authentication would be performed if done purely in handlers, without help from Servant itself. In contrast, the protected endpoint already receives the result of the authentication as a parameter; it doesn't have to perform it itself.

In a real-world application, server wouldn't take those parameters, they would only be passed in the Context.

  • Related