Search code examples
haskellservant

How do I resolve a type error using Enter from the Servant library?


I'm trying to use the enter function to allow me to run my API handlers with one set of exceptions that I'll translate to Servant at a high level, but I'm having troubles with a type match.

Given this minimal set of definitions:

-- server :: Config -> Server Routes
server :: Config -> ServerT Routes (ExceptT ServantErr IO)
server c = enter runApp (handlers c)

-- runApp :: AppM :~> Handler
runApp :: ExceptT AppErr IO :~> ExceptT ServantErr IO
runApp = Nat undefined

handlers :: Config -> ServerT Routes (ExceptT AppErr IO)
handlers = undefined

I end up with this type error:

Couldn't match type `IO' with `ExceptT ServantErr IO'
arising from a functional dependency between:
  constraint `servant-0.7.1:Servant.Utils.Enter.Enter
                (IO ResponseReceived)
                (ExceptT AppErr IO :~> ExceptT ServantErr IO)
                (IO ResponseReceived)'
    arising from a use of `enter'
  instance `servant-0.7.1:Servant.Utils.Enter.Enter
              (m a) (m :~> n) (n a)'
    at <no location info>
In the expression: enter runApp (handlers c)
In an equation for `server': server c = enter runApp (handlers c)

The exception comes from the line with the call to enter. For reference, the declaration of enter:

class Enter typ arg ret | typ arg -> ret, typ ret -> arg where
  enter :: arg -> typ -> ret

instance Enter (m a) (m :~> n) (n a) where
  -- enter :: (m :~> n) -> m a -> n a

So, when I call enter runApp, I expect the types to go as such:

enter :: (m :~> n) -> m a -> n a
enter (runApp :: m ~ ExceptT AppErr IO :~> n ~ ExceptT ServantErr IO) :: ExceptT AppErr IO a -> ExceptT ServantErr IO a

(where above I used n ~ ExceptT ServantErr IO to illustrate my type substitutions)

And in reality, I know from other code (that I have tried to mimic, and I don't understand where I went wrong), that enter runApp should/must have this type:

enter runApp :: ServerT Routes (ExceptT AppErr IO a) -> ServerT Routes (ExceptT ServantErr IO a)

So, the questions are legion:

  • what are the actual types of enter runApp (not what ghci will give me, but a more descriptive explanation)?
  • where is that (IO ResponseReceived) constraint coming from?
  • how do I tweak the above code to work such that the entire handler gets passed through the natural transformation?

Solution

  • You cannot have a Raw endpoint in the Api type. The way to do it is to get the Raw endpoint out of the handlers and call it e.g.

    server c = enter runApp (handlers c) :<|> handleRaw