Search code examples
haskellservant

In servant, how can I select my exception based on Accept header?


For context, this is an authentication situation. In my application, if the client is not authenticated, then the app obviously needs to respond appropriately. The challenge comes up when I want to choose a different response based on the type of application calling into the server.

Here is an example of a route:

server =    Header "Cookie" (AuthToken Unverified)
         :> "api" :> "history" :> Get '[HTML, JSON] HistoryPage

So, an HTML response would be for a CGI app. In general, it should either render an authentication page, or it should throw a 303 to direct the user to an authentication page.

But a JSON response would be for a Javascript app, and there I want to simply return a 404, because the Javascript would have other ways of doing authentication.

Here is my top-level handler:

newtype WebM a = WebM (ReaderT Context (ExceptT WebExc IO) a)
data WebExc = OtherExceptionTypes
            | AppUnauthorized

runWeb :: Context -> WebM :~> Handler
runWeb ctx@Context{..} = Nat $ \(WebM action) -> withExceptT trExc $ runReaderT action ctx
  where
    trExc :: WebExc -> ServantErr
    trExc AppUnauthorized = err303 { .. }

I've tried creating my own Javascript content-type, but the MimeRenderer doesn't allow me to throw exceptions. The only idea I have so far is to capture the "Accept" header and throw either 303 or 404 from within the handler. But that is gross because the handler isn't supposed to know anything about the actual client app.

Is there any cleaner way to handle this?


Solution

  • Not exactly answering what you asked, but in the greater picture, this sounds like a use-case for two separate routes, with a common bit of implementation shared between them.