{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_HADDOCK prune #-} {- | Support integrating web services created by __servant__ with handlers defined in the 'Program' monad. This is a thin wrapper which creates an 'Application' which can be used with 'Core.Webserver.Warp.launchWebserver'. @ import "Core.Program" import "Core.Webserver.Servant" import "Core.Webserver.Warp" import MyServer (api, routes) main :: 'IO' () main = do 'Core.Program.Execute.execute' $ do application <- 'prepareRoutes' api routes 'launchWebserver' 8080 application @ -} module Core.Webserver.Servant ( prepareRoutes, prepareRoutesWithContext, ) where import Control.Exception.Safe qualified as Safe (try, throw) import Control.Monad.Except (ExceptT (..)) import Core.Program import Core.System (Exception (..)) import Core.Webserver.Warp import Data.Proxy (Proxy) import GHC.Base (Type) import Network.Wai (Application) import Servant qualified as Servant ( Handler (..), ServerT, ) import Servant.Server qualified as Servant ( Context (..), HasServer, ServerContext, serveWithContextT, ) {- | Convert a __servant__ API and set of handlers into a __warp__ 'Application'. This 'Application' must be used with 'Core.Webserver.Warp.launchWebserver' so that the necessary internal connections are made. Usage is straight forward: @ application <- 'prepareRoutes' api routes 'launchWebserver' 8080 application @ This code creates an Application which has sufficient information to unlift back to the 'Program' monad so that your handlers can be take advantage of the logging and telemetry facilities of __core-program__ and __core-telemetry__. -} prepareRoutes :: forall τ (api :: Type). Servant.HasServer api '[] => Proxy api -> Servant.ServerT api (Program τ) -> Program τ Application prepareRoutes proxy = prepareRoutesWithContext proxy Servant.EmptyContext {- | Prepare routes as with 'prepareRoutes' above, but providing a __servant__ 'Servant.Server.Context' in order to give detailed control of the setup. @since 0.1.1 -} prepareRoutesWithContext :: forall τ (api :: Type) context. (Servant.HasServer api context, Servant.ServerContext context) => Proxy api -> Servant.Context context -> Servant.ServerT api (Program τ) -> Program τ Application prepareRoutesWithContext proxy sContext (routes :: Servant.ServerT api (Program τ)) = pure application where application :: Application application = \request sendResponse -> do -- The type application in `contextFromRequest` is important, as -- otherwise the compiler cannot infer that the type of -- `transformProgram` is of the same `τ` as the one in `Program τ` -- This exception will happen in the case where this is not being run -- by `launchWebserver`, since we need the Context to be stashed in -- the request by the Middleware there. context <- case contextFromRequest @τ request of Just context' -> pure context' Nothing -> Safe.throw ContextNotFoundInRequest Servant.serveWithContextT proxy sContext (transformProgram context) routes request sendResponse transformProgram :: Context τ -> Program τ α -> Servant.Handler α transformProgram context program = let output = Safe.try $ subProgram context $ do program in Servant.Handler (ExceptT output)