packages feed

helic-0.1.0.0: lib/Helic/Net/Server.hs

{-# options_haddock prune #-}
-- |HTTP Server Plumbing, Internal
module Helic.Net.Server where

import qualified Network.Wai.Handler.Warp as Warp
import Network.Wai.Handler.Warp (
  defaultSettings,
  setBeforeMainLoop,
  setGracefulShutdownTimeout,
  setHost,
  setInstallShutdownHandler,
  setPort,
  )
import Network.Wai.Middleware.RequestLogger (logStdout)
import Polysemy.Conc (Interrupt, Sync)
import qualified Polysemy.Conc.Effect.Interrupt as Interrupt
import qualified Polysemy.Conc.Effect.Sync as Sync
import Polysemy.Final (withWeavingToFinal)
import qualified Polysemy.Log as Log
import Polysemy.Log (Log)
import Servant (
  Context,
  DefaultErrorFormatters,
  ErrorFormatters,
  Handler (Handler),
  HasContextEntry,
  HasServer,
  ServerError,
  ServerT,
  err500,
  hoistServerWithContext,
  serveWithContext,
  type (.++),
  )

newtype ApiError =
  ApiError { unApiError :: Text }
  deriving stock (Eq, Show)
  deriving newtype (IsString)

data ServerReady =
  ServerReady
  deriving (Eq, Show)

logErrors ::
  Member Log r =>
  Sem r (Either ServerError a) ->
  Sem r (Either ServerError a)
logErrors ma =
  ma >>= \case
    Right a -> pure (Right a)
    Left err -> Left err <$ Log.error (show err)

toHandler :: IO (Maybe a) -> Handler a
toHandler =
  Handler . ExceptT . fmap (maybe (Left err500) Right)

runServerWithContext ::
  ∀ (api :: Type) context r .
  HasServer api context =>
  HasContextEntry (context .++ DefaultErrorFormatters) ErrorFormatters =>
  Members [Sync ServerReady, Log, Interrupt, Final IO] r =>
  ServerT api (Sem r) ->
  Context context ->
  Int ->
  Sem r ()
runServerWithContext srv context port = do
  Log.info [exon|server port: #{show port}|]
  withWeavingToFinal \ s wv ins -> do
    let
      app =
        serveWithContext (Proxy @api) context (hoistServerWithContext (Proxy @api) (Proxy @context) hoist srv)
      hoist :: Sem r a -> Handler a
      hoist =
        toHandler . fmap ins . wv . (<$ s)
      shut h =
        void (wv (Interrupt.register "api" h <$ s))
      settings =
        setHost "*6" $
        setPort port $
        setBeforeMainLoop (void (wv (Sync.putBlock ServerReady <$ s))) $
        setInstallShutdownHandler shut $
        setGracefulShutdownTimeout (Just 0) $
        defaultSettings
    (<$ s) <$> Warp.runSettings settings (logStdout app)