Drop support for ServantT transformer type for server
After burning a few hours wrestling with the type system, I decided to revert to the simpler `Server API` type instead of the `ServantT` transformer type. The problem is that I couldn't write a MonadError instance for `RIO Context`, which is my `AppM` (i.e. application monad). Using `throwIO` in the server handlers results in 500 errors, which is not what I wanted. I'm still pretty fuzzy about what's happening; I now know that exception handling in Haskell is pretty gnaryly. I may revisit this at a later time when my knowledge is more extensive. For now: time to fry bigger fish. An easier abstract is for me to pass `T.Context` into `server` as an argument, which after all is what a Reader does. TL;DR: - Read server, client ports from .envrc - Define a top-level Failure type (empty for now) - Define a top-level Success type - Define App as RIO Context (Either Failure Success)
This commit is contained in:
parent
f61ed25755
commit
4ff1ea291c
3 changed files with 59 additions and 16 deletions
|
@ -15,12 +15,11 @@ import qualified GoogleSignIn
|
||||||
import qualified Types as T
|
import qualified Types as T
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
server :: ServerT API T.App
|
server :: T.Context -> Server API
|
||||||
server = verifyGoogleSignIn
|
server T.Context{..} = verifyGoogleSignIn
|
||||||
where
|
where
|
||||||
verifyGoogleSignIn :: T.VerifyGoogleSignInRequest -> T.App NoContent
|
verifyGoogleSignIn :: T.VerifyGoogleSignInRequest -> Handler NoContent
|
||||||
verifyGoogleSignIn T.VerifyGoogleSignInRequest{..} = do
|
verifyGoogleSignIn T.VerifyGoogleSignInRequest{..} = do
|
||||||
T.Context{..} <- ask
|
|
||||||
validationResult <- liftIO $ GoogleSignIn.validateJWT False (EncodedJWT idToken)
|
validationResult <- liftIO $ GoogleSignIn.validateJWT False (EncodedJWT idToken)
|
||||||
case validationResult of
|
case validationResult of
|
||||||
Valid _ -> do
|
Valid _ -> do
|
||||||
|
@ -30,19 +29,18 @@ server = verifyGoogleSignIn
|
||||||
-- Redirect the SPA to the sign-up / payment page
|
-- Redirect the SPA to the sign-up / payment page
|
||||||
pure NoContent
|
pure NoContent
|
||||||
err -> do
|
err -> do
|
||||||
-- TODO: I would prefer to use `throwError` here, but after changing
|
throwError err401 { errBody = err |> GoogleSignIn.explainResult |> cs }
|
||||||
-- to ServerT, I couldn't get the code to compile.
|
|
||||||
throwIO err401 { errBody = err |> GoogleSignIn.explainResult |> cs }
|
|
||||||
|
|
||||||
run :: T.App ()
|
run :: T.App
|
||||||
run = do
|
run = do
|
||||||
ctx@T.Context{..} <- ask
|
ctx@T.Context{..} <- ask
|
||||||
server
|
ctx
|
||||||
|> hoistServer (Proxy @ API) (runRIO ctx)
|
|> server
|
||||||
|> serve (Proxy @ API)
|
|> serve (Proxy @ API)
|
||||||
|> cors (const $ Just corsPolicy)
|
|> cors (const $ Just corsPolicy)
|
||||||
|> Warp.run contextServerPort
|
|> Warp.run contextServerPort
|
||||||
|> liftIO
|
|> liftIO
|
||||||
|
pure $ Right ()
|
||||||
where
|
where
|
||||||
corsPolicy :: CorsResourcePolicy
|
corsPolicy :: CorsResourcePolicy
|
||||||
corsPolicy = simpleCorsResourcePolicy
|
corsPolicy = simpleCorsResourcePolicy
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
module Main where
|
module Main where
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
import RIO
|
import RIO
|
||||||
import Prelude (putStrLn)
|
import Prelude (putStr, putStrLn)
|
||||||
|
|
||||||
import qualified Types as T
|
import qualified Types as T
|
||||||
import qualified System.Envy as Envy
|
import qualified System.Envy as Envy
|
||||||
|
@ -18,8 +18,8 @@ getAppContext = do
|
||||||
Left err -> pure $ Left err
|
Left err -> pure $ Left err
|
||||||
Right T.Env{..} -> pure $ Right T.Context
|
Right T.Env{..} -> pure $ Right T.Context
|
||||||
{ contextGoogleClientID = envGoogleClientID
|
{ contextGoogleClientID = envGoogleClientID
|
||||||
, contextClientPort = 8000
|
, contextServerPort = envServerPort
|
||||||
, contextServerPort = 3000
|
, contextClientPort = envClientPort
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -27,4 +27,10 @@ main = do
|
||||||
mContext <- getAppContext
|
mContext <- getAppContext
|
||||||
case mContext of
|
case mContext of
|
||||||
Left err -> putStrLn err
|
Left err -> putStrLn err
|
||||||
Right ctx -> runRIO ctx App.run
|
Right ctx -> do
|
||||||
|
result <- runRIO ctx App.run
|
||||||
|
case result of
|
||||||
|
Left err -> do
|
||||||
|
putStr "Something went wrong when executing the application: "
|
||||||
|
putStrLn $ show err
|
||||||
|
Right _ -> putStrLn "The application successfully executed."
|
||||||
|
|
|
@ -9,11 +9,15 @@ import System.Envy (FromEnv, fromEnv, env)
|
||||||
-- | Read from .envrc
|
-- | Read from .envrc
|
||||||
data Env = Env
|
data Env = Env
|
||||||
{ envGoogleClientID :: !String
|
{ envGoogleClientID :: !String
|
||||||
|
, envServerPort :: !Int
|
||||||
|
, envClientPort :: !Int
|
||||||
} deriving (Eq, Show)
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
instance FromEnv Env where
|
instance FromEnv Env where
|
||||||
fromEnv _ = do
|
fromEnv _ = do
|
||||||
envGoogleClientID <- env "GOOGLE_CLIENT_ID"
|
envGoogleClientID <- env "GOOGLE_CLIENT_ID"
|
||||||
|
envServerPort <- env "SERVER_PORT"
|
||||||
|
envClientPort <- env "CLIENT_PORT"
|
||||||
pure Env {..}
|
pure Env {..}
|
||||||
|
|
||||||
-- | Application context: a combination of Env and additional values.
|
-- | Application context: a combination of Env and additional values.
|
||||||
|
@ -23,8 +27,18 @@ data Context = Context
|
||||||
, contextClientPort :: !Int
|
, contextClientPort :: !Int
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Type synonym for my application monad.
|
-- | Top-level except for our application, as RIO recommends defining.
|
||||||
type App = RIO Context
|
type Failure = ()
|
||||||
|
|
||||||
|
-- | When our app executes along the "happy path" this is the type of result it
|
||||||
|
-- produces.
|
||||||
|
type Success = ()
|
||||||
|
|
||||||
|
-- | This is our application monad.
|
||||||
|
type AppM = RIO Context
|
||||||
|
|
||||||
|
-- | The concrete type of our application.
|
||||||
|
type App = AppM (Either Failure Success)
|
||||||
|
|
||||||
data VerifyGoogleSignInRequest = VerifyGoogleSignInRequest
|
data VerifyGoogleSignInRequest = VerifyGoogleSignInRequest
|
||||||
{ idToken :: !Text
|
{ idToken :: !Text
|
||||||
|
@ -34,3 +48,28 @@ instance FromJSON VerifyGoogleSignInRequest where
|
||||||
parseJSON = withObject "" $ \x -> do
|
parseJSON = withObject "" $ \x -> do
|
||||||
idToken <- x .: "idToken"
|
idToken <- x .: "idToken"
|
||||||
pure VerifyGoogleSignInRequest{..}
|
pure VerifyGoogleSignInRequest{..}
|
||||||
|
|
||||||
|
data GoogleLinkedAccount = GoogleLinkedAccount
|
||||||
|
{
|
||||||
|
-- { googleLinkedAccountUUID :: UUID
|
||||||
|
-- , googleLinkedAccountEmail :: Email
|
||||||
|
-- , googleLinkedAccountTsCreated :: Timestamp
|
||||||
|
googleLinkedAccountGivenName :: !(Maybe Text)
|
||||||
|
, googleLinkedAccountFamilyName :: !(Maybe Text)
|
||||||
|
, googleLinkedAccountFullName :: !(Maybe Text)
|
||||||
|
-- , googleLinkedAccountPictureURL :: URL
|
||||||
|
-- , googleLinkedAccountLocale :: Maybe Locale
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
data PayingCustomer = PayingCustomer
|
||||||
|
{
|
||||||
|
-- { payingCustomerAccountUUID :: UUID
|
||||||
|
-- , payingCustomerTsCreated :: Timestamp
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
data Session = Session
|
||||||
|
{
|
||||||
|
-- { sessionUUID :: UUID
|
||||||
|
-- , sessionAccountUUID :: UUID
|
||||||
|
-- , sessionTsCreated :: Timestamp
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
Loading…
Reference in a new issue