Prefer ServantT for server to consume App context

Long story -> short: I'd like to access my App monad from within my Servant
handlers.

While this code type-checks, I'm not sure it's working as intended. Needing to
change throwError to throwIO fails the "smell test". I expect to refactor this
code, but I'm calling it a night for now.
This commit is contained in:
William Carroll 2020-08-09 23:15:12 +01:00
parent bbcd0bf27d
commit f61ed25755
2 changed files with 33 additions and 24 deletions

View file

@ -6,41 +6,47 @@ import Servant
import API import API
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Network.Wai.Middleware.Cors
import GoogleSignIn (EncodedJWT(..), ValidationResult(..)) import GoogleSignIn (EncodedJWT(..), ValidationResult(..))
import Utils import Utils
import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Middleware.Cors as Cors
import qualified GoogleSignIn import qualified GoogleSignIn
import qualified Types as T import qualified Types as T
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
server :: Server API server :: ServerT API T.App
server = verifyGoogleSignIn server = verifyGoogleSignIn
where where
verifyGoogleSignIn :: T.VerifyGoogleSignInRequest -> Handler NoContent verifyGoogleSignIn :: T.VerifyGoogleSignInRequest -> T.App NoContent
verifyGoogleSignIn T.VerifyGoogleSignInRequest{..} = do verifyGoogleSignIn T.VerifyGoogleSignInRequest{..} = do
validationResult <- liftIO $ GoogleSignIn.validateJWT False (EncodedJWT idToken) T.Context{..} <- ask
case validationResult of validationResult <- liftIO $ GoogleSignIn.validateJWT False (EncodedJWT idToken)
Valid _ -> do case validationResult of
-- If GoogleLinkedAccounts has email from JWT: Valid _ -> do
-- create a new session for email -- If GoogleLinkedAccounts has email from JWT:
-- Else: -- create a new session for email
-- Redirect the SPA to the sign-up / payment page -- Else:
pure NoContent -- Redirect the SPA to the sign-up / payment page
err -> do pure NoContent
throwError err401 { errBody = err |> GoogleSignIn.explainResult |> cs } err -> do
-- TODO: I would prefer to use `throwError` here, but after changing
-- to ServerT, I couldn't get the code to compile.
throwIO err401 { errBody = err |> GoogleSignIn.explainResult |> cs }
run :: RIO T.Context () run :: T.App ()
run = do run = do
T.Context{..} <- ask ctx@T.Context{..} <- ask
liftIO $ Warp.run contextServerPort (enforceCors $ serve (Proxy @ API) $ server) server
|> hoistServer (Proxy @ API) (runRIO ctx)
|> serve (Proxy @ API)
|> cors (const $ Just corsPolicy)
|> Warp.run contextServerPort
|> liftIO
where where
enforceCors = Cors.cors (const $ Just corsPolicy) corsPolicy :: CorsResourcePolicy
corsPolicy :: Cors.CorsResourcePolicy corsPolicy = simpleCorsResourcePolicy
corsPolicy = { corsOrigins = Just (["http://localhost:8000"], True)
Cors.simpleCorsResourcePolicy , corsMethods = simpleMethods ++ ["PUT", "PATCH", "DELETE", "OPTIONS"]
{ Cors.corsOrigins = Just (["http://localhost:8000"], True) , corsRequestHeaders = simpleHeaders ++ ["Content-Type", "Authorization"]
, Cors.corsMethods = Cors.simpleMethods ++ ["PUT", "PATCH", "DELETE", "OPTIONS"] }
, Cors.corsRequestHeaders = Cors.simpleHeaders ++ ["Content-Type", "Authorization"]
}

View file

@ -23,6 +23,9 @@ data Context = Context
, contextClientPort :: !Int , contextClientPort :: !Int
} }
-- | Type synonym for my application monad.
type App = RIO Context
data VerifyGoogleSignInRequest = VerifyGoogleSignInRequest data VerifyGoogleSignInRequest = VerifyGoogleSignInRequest
{ idToken :: !Text { idToken :: !Text
} deriving (Eq, Show) } deriving (Eq, Show)