I am trying to get access to my custom monad in the Generalized Auth Handler But i Haven't been able to solve the TypeErros I am getting. I've tried following along with the docs but I haven't been able to convert the examples over to my use case (this is probably because I lack a fully developed understanding of the type level machinery going on in the server). I have an auth situation which doesn't quite fit the Servant-Auth case. Here is the minimal server.
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
module Adapter.Servant.TodoAPI2 (todoApp) where
import ClassyPrelude hiding (Handler)
import Servant
import Servant.Server
import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData,
mkAuthHandler)
import Control.Monad.Except
import Domain.Types.AppT
import Domain.Types.AppEnv
import Network.Wai.Handler.Warp
import Network.Wai
import Network.Wai.Middleware.RequestLogger
readerToHandler :: AppEnv -> AppT a -> Handler a
readerToHandler env appt = do
val <- liftIO $ runExceptT $ runReaderT (runAppT appt) env
case val of
Left e -> throwError e
Right s -> return s
type TodoAPI = "a" :> Get '[JSON] Int
:<|> "b" :> Get '[JSON] Bool
:<|> "c" :> Get '[JSON] Int
:<|> "d" :> AuthProtect "JWT" :> Get '[JSON] Int
todoAPI :: Proxy TodoAPI
todoAPI = Proxy
todoServerT :: ServerT TodoAPI AppT
todoServerT = a
:<|> b
:<|> c
:<|> d
where
a :: AppT Int
a = return 1797
b :: AppT Bool
b = return True
c :: AppT Int
c = throwError $ (ServerError 500 "Failed Computation" "" [])
d :: AuthUser -> AppT Int
d au = do
sec <- asks secret
liftIO $ print $ sec
return $ 1798
todoServer :: AppEnv -> Server TodoAPI
todoServer env = hoistServer todoAPI (readerToHandler env) todoServerT
todoApp :: AppEnv -> Application
todoApp env = serveWithContext todoAPI (genAuthServerContext env) (todoServer env)
data AuthUser = AuthUser
{ auth_user_id :: Int64
, auth_user_email :: Text
} deriving (Eq, Show, Generic)
type instance AuthServerData (AuthProtect "JWT") = AuthUser
authHandler :: AppEnv -> AuthHandler Request (AuthUser)
authHandler env =
let handler req =
case lookup "Authorization" (requestHeaders req) of
Nothing ->
throwError (err401 {errBody = "Missing 'Authorization' header"})
Just token -> do
liftIO $ print $ secret env
-- sec <- asks secret
case (token == "HELLOWORLD") of
False ->
throwError (err401 {errBody = "Wrong 'Authorization' token"})
-- True -> do
True -> return $ AuthUser 1 "[email protected]"
-- return $ AuthUser 1 "[email protected]"
in mkAuthHandler handler
genAuthServerContext :: AppEnv -> Context (AuthHandler Request (AuthUser) ': '[])
genAuthServerContext env = (authHandler env) :. EmptyContext
If possible I would like to not pass my AppEnv to my handler and simply treat it as if it were just part of my Reader and use asks
.
Below are my AppEnv and AppT.
module Domain.Types.AppEnv (AppEnv(..)) where
import ClassyPrelude hiding (Handler)
import Data.Pool
import Database.PostgreSQL.Simple
data AppEnv = AppEnv
{ pgEnv :: !(Pool Connection)
, secret :: Text
}
module Domain.Types.AppT (AppT(..)) where
import ClassyPrelude
import Servant.Server
import Control.Monad.Except
import Control.Monad.Catch (MonadThrow)
import Domain.Types.AppEnv
newtype AppT a = AppT
{ runAppT :: ReaderT AppEnv (ExceptT ServerError IO) a
} deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv, MonadThrow, MonadError ServerError)
EDIT: Errors
Error:
• No instance for (HasContextEntry
'[] (AuthHandler Request AuthUser))
arising from a use of ‘hoistServer’
• In the expression:
hoistServer todoAPI (readerToHandler env) todoServerT
In an equation for ‘todoServer’:
todoServer env
= hoistServer todoAPI (readerToHandler env) todoServerT
|
55 | todoServer env = hoistServer todoAPI (readerToHandler env) todoServerT
hoistServer :: HasServer api '[] => Proxy api -> (forall x. m x -> n x) -> ServerT api m -> ServerT api n
You're calling hoistServer
where api ~ TodoAPI
, so we need to solve the constraint HasServer TodoAPI '[]
. The relevant instance for the problem you're having is this one:
instance (HasServer api context, HasContextEntry context (AuthHandler Request (AuthServerData (AuthProtect tag)))) => HasServer (AuthProtect tag :> api :: Type) context
So now we need to solve the constraint HasContextEntry context (AuthHandler Request (AuthServerData (AuthProtect tag)))
. tag
there is "JWT"
, so plug that in, then apply your type instance
, and we get that we need to solve HasContextEntry context (AuthHandler Request AuthUser)
.
The trouble is, what's context
there? It's '[]
. Well, that's not going to work. To fix it, you need to use hoistServerWithContext
instead of hoistServer
, like this:
todoServer env = hoistServerWithContext todoAPI (Proxy :: Proxy '[AuthHandler Request AuthUser]) (readerToHandler env) todoServerT
That has this type:
hoistServerWithContext :: HasServer api context => Proxy api -> Proxy context -> (forall x. m x -> n x) -> ServerT api m -> ServerT api n
So we get to solve HasServer TodoAPI '[AuthHandler Request AuthUser]
instead, so now we can solve the HasContextEntry
constraint, and it compiles fine.