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 Context
s:
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
.