I'm using custom monad (with reader) to easily pass data like DB pool to my handlers (before using custom monad I used to pass connection as fn argument).
This is how I've defined my custom monad :
newtype Controller a = Controller
{ runController :: ReaderT ServerEnvironment Handler a
} deriving ( Functor, Applicative, Monad, MonadReader ServerEnvironment,
MonadError ServantErr, MonadIO )
This ServerEnvironment
is just custom datatype I use to carry my data.
Problem is that for my AuthHandler
I have to specifically use function with:
r -> Handler usr
as authentication handler, I can't use my custom handler which would be :
r -> Controller usr
and I also have no way to pass in my ConnectionPool
because signature can't be :
ConnPool -> r -> Handler usr
So, how does one pass extra data to authentication handler in servant without using global IO
state?
The AuthHandler
you put into the context doesn't have to be defined at the top-level! Generally, you'll want to do it in main
so you have access to the database connections etc. which you've created:
type API =
... :<|> (AuthProtect "myProtection" :> ...) :<|> ...
type instance AuthServerData (AuthProtect "myProtection") = User
server :: ServerEnvironment -> Server API
server env = ...
setupEnv :: IO ServerEnvironment
setupEnv = ..
-- This is essentially a 'Controller'.
authenticate :: ServerEnvironment -> Handler User
authenticate conn = ...
main :: IO ()
main = do
env <- setupEnv
-- Now, because we have access to the env, we can turn our
-- 'authenticate' into the right type before putting it
-- in the context
let ctx = authenticate env :. EmptyContext
run 8080 $ serveWithContext myAPI (server conn) ctx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With