diff options
Diffstat (limited to 'hsm-core')
-rw-r--r-- | hsm-core/Hsm/Core/App.hs | 21 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Env.hs | 26 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Fsm.hs | 58 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Log.hs | 27 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Message.hs | 25 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Options.hs | 40 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Zmq.hs | 29 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Zmq/Client.hs | 93 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Zmq/Server.hs | 74 | ||||
-rw-r--r-- | hsm-core/hsm-core.cabal | 42 |
10 files changed, 0 insertions, 435 deletions
diff --git a/hsm-core/Hsm/Core/App.hs b/hsm-core/Hsm/Core/App.hs deleted file mode 100644 index 11759be..0000000 --- a/hsm-core/Hsm/Core/App.hs +++ /dev/null @@ -1,21 +0,0 @@ -module Hsm.Core.App - ( launch - ) where - -import Data.Aeson (FromJSON) -import Data.Text (Text) -import Effectful.Log (LogLevel, Logger) -import Hsm.Core.Env (environment) -import Hsm.Core.Options (Options(Options), options) -import Log.Backend.StandardOutput (withStdOutLogger) - -launch :: - FromJSON env - => Text - -> (IO app -> IO app) - -> (env -> Logger -> LogLevel -> IO app) - -> IO app -launch name wrapper app = do - Options path level <- options name - env <- environment name path - wrapper $ withStdOutLogger $ \logger -> app env logger level diff --git a/hsm-core/Hsm/Core/Env.hs b/hsm-core/Hsm/Core/Env.hs deleted file mode 100644 index 8ef7464..0000000 --- a/hsm-core/Hsm/Core/Env.hs +++ /dev/null @@ -1,26 +0,0 @@ -module Hsm.Core.Env - ( environment - , deriveFromYaml - ) where - -import Data.Aeson (FromJSON, Result(Error, Success), fromJSON) -import Data.Aeson.Key (fromText) -import Data.Aeson.KeyMap ((!?)) -import Data.Aeson.TH (defaultOptions, deriveFromJSON, rejectUnknownFields) -import Data.Maybe (fromMaybe) -import Data.Text (Text, unpack) -import Data.Yaml (decodeFileThrow) -import Language.Haskell.TH (Dec, Name, Q) - -environment :: FromJSON env => Text -> Text -> IO env -environment name = fmap (check . fromJSON . load) . decodeFileThrow . unpack - where - load keymap = - fromMaybe - (error $ "Service configuration for " <> unpack name <> " not found)") - $ keymap !? fromText name - check (Success env) = env - check (Error str) = error str - -deriveFromYaml :: Name -> Q [Dec] -deriveFromYaml = deriveFromJSON defaultOptions {rejectUnknownFields = True} diff --git a/hsm-core/Hsm/Core/Fsm.hs b/hsm-core/Hsm/Core/Fsm.hs deleted file mode 100644 index d1c2f5d..0000000 --- a/hsm-core/Hsm/Core/Fsm.hs +++ /dev/null @@ -1,58 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE OverloadedStrings #-} - -module Hsm.Core.Fsm - ( FsmState(FsmState) - , FsmOutput(FsmOutput) - , FsmResult(FsmResult) - , fsm - ) where - -import Data.Maybe (fromJust, isJust) -import Data.Text (Text) -import Effectful (Eff, (:>)) -import Effectful.Log (Log, LogLevel, localDomain, logAttention_, logTrace_) -import Effectful.Reader.Static (Reader, ask) -import Effectful.State.Static.Local (State, get, put) -import Hsm.Core.Log (logTup) -import Streamly.Data.Stream qualified as S (Stream, mapM, takeWhile) - -data FsmState i o env sta = - FsmState Text (i -> env -> sta -> FsmOutput i o env sta) - -data FsmOutput i o env sta = - FsmOutput (Maybe (FsmResult i o env sta)) [(LogLevel, Text)] - -data FsmResult i o env sta = - FsmResult o sta (FsmState i o env sta) - --- Finite state machines allow processing of stream elements using pure --- functions. One or more FSMs can be included within a `Streamly` pipeline. -fsm :: - forall i o env sta es. - ( Log :> es - , Reader env :> es - , State (FsmState i o env sta) :> es - , State sta :> es - ) - => S.Stream (Eff es) i - -> S.Stream (Eff es) o -fsm = S.mapM (return . fromJust) . S.takeWhile isJust . S.mapM run - where - exit = do - logAttention_ "No state returned, exiting FSM" - return Nothing - push (FsmResult out sta next) = do - put sta - put next - return $ Just out - run input = - localDomain "fsm" $ do - FsmState name action <- get - sta <- get @sta - env <- ask @env - logTrace_ $ "Entering state " <> name - FsmOutput res logs <- return $ action input env sta - localDomain name $ mapM_ logTup logs - logTrace_ $ "Exiting state " <> name - maybe exit push res diff --git a/hsm-core/Hsm/Core/Log.hs b/hsm-core/Hsm/Core/Log.hs deleted file mode 100644 index 6930e90..0000000 --- a/hsm-core/Hsm/Core/Log.hs +++ /dev/null @@ -1,27 +0,0 @@ -module Hsm.Core.Log - ( withLogIO - , logTup - , flushLogger - ) where - -import Data.Aeson.Types (emptyObject) -import Data.Text (Text) -import Data.Time.Clock (getCurrentTime) -import Effectful (Eff, (:>)) -import Effectful.Dispatch.Static (unsafeEff_) -import Effectful.Log qualified as L - --- Helper function allows logging within IO, Useful during `resourcet` --- allocation and release operations. -withLogIO :: L.Log :> es => Eff es (L.LogLevel -> Text -> IO ()) -withLogIO = do - logIO <- L.getLoggerIO - return $ \level message -> do - now <- getCurrentTime - logIO now level message emptyObject - -logTup :: L.Log :> es => (L.LogLevel, Text) -> Eff es () -logTup (level, message) = L.logMessage level message emptyObject - -flushLogger :: L.Log :> es => Eff es () -flushLogger = L.getLoggerEnv >>= unsafeEff_ . L.waitForLogger . L.leLogger diff --git a/hsm-core/Hsm/Core/Message.hs b/hsm-core/Hsm/Core/Message.hs deleted file mode 100644 index b2a9f23..0000000 --- a/hsm-core/Hsm/Core/Message.hs +++ /dev/null @@ -1,25 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -module Hsm.Core.Message - ( message - , topic - , body - ) where - -import Data.Binary (Binary, decode, encode) -import Data.ByteString (ByteString, fromStrict, toStrict) -import Data.ByteString.Char8 qualified as B (breakSubstring, drop, length) -import Data.Text (Text) -import Data.Text.Encoding (decodeUtf8, encodeUtf8) - -sep :: ByteString -sep = "//" - -message :: Binary a => Text -> a -> ByteString -message t b = encodeUtf8 t <> sep <> toStrict (encode b) - -topic :: ByteString -> Text -topic = decodeUtf8 . fst . B.breakSubstring sep - -body :: Binary a => ByteString -> a -body = decode . fromStrict . B.drop (B.length sep) . snd . B.breakSubstring sep diff --git a/hsm-core/Hsm/Core/Options.hs b/hsm-core/Hsm/Core/Options.hs deleted file mode 100644 index 29e40a4..0000000 --- a/hsm-core/Hsm/Core/Options.hs +++ /dev/null @@ -1,40 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -module Hsm.Core.Options - ( Options(Options) - , options - ) where - -import Control.Applicative ((<**>)) -import Data.Text (Text, pack, unpack) -import Effectful.Log (LogLevel(LogInfo), readLogLevelEither, showLogLevel) -import Options.Applicative qualified as P -import Options.Applicative.Text qualified as P - -data Options = - Options Text LogLevel - -parser :: P.Parser Options -parser = - Options - <$> P.textOption - (P.help "Path to services config file" - <> P.short 'c' - <> P.long "config" - <> P.metavar "PATH" - <> P.value "servconf.yaml" - <> P.showDefault) - <*> P.option - (P.eitherReader $ readLogLevelEither . pack) - (P.help "Log level" - <> P.short 'l' - <> P.long "log-level" - <> P.metavar "LEVEL" - <> P.value LogInfo - <> P.showDefaultWith (unpack . showLogLevel)) - -options :: Text -> IO Options -options name = - P.customExecParser (P.prefs $ P.columns 100) - $ P.info (parser <**> P.helper) - $ P.fullDesc <> P.progDesc ("Launch " <> unpack name <> " service") diff --git a/hsm-core/Hsm/Core/Zmq.hs b/hsm-core/Hsm/Core/Zmq.hs deleted file mode 100644 index 2f70d48..0000000 --- a/hsm-core/Hsm/Core/Zmq.hs +++ /dev/null @@ -1,29 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -module Hsm.Core.Zmq - ( withSocket - ) where - -import Effectful (Eff, IOE, (:>)) -import Effectful.Log (Log, LogLevel(LogTrace)) -import Effectful.Resource (Resource, allocate) -import Hsm.Core.Log (withLogIO) -import System.ZMQ4 qualified as Z - -withSocket :: - (Z.SocketType t, IOE :> es, Log :> es, Resource :> es) - => t - -> Eff es (Z.Socket t) -withSocket stype = withLogIO >>= bracket - where - bracket logIO = snd . snd <$> allocate acquire release - where - acquire = - logIO LogTrace "Acquiring ZMQ context" >> do - cont <- Z.context - sock <- Z.socket cont stype - return (cont, sock) - release (cont, sock) = - logIO LogTrace "Releasing ZMQ context" >> do - Z.close sock - Z.shutdown cont diff --git a/hsm-core/Hsm/Core/Zmq/Client.hs b/hsm-core/Hsm/Core/Zmq/Client.hs deleted file mode 100644 index 6093e54..0000000 --- a/hsm-core/Hsm/Core/Zmq/Client.hs +++ /dev/null @@ -1,93 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} - -module Hsm.Core.Zmq.Client - ( Client - , receive - , poll - , runClient - ) where - -import Control.Monad (forM_) -import Control.Monad.Loops (whileM) -import Data.Binary (Binary) -import Data.Text (Text, pack, unpack) -import Data.Text.Encoding (encodeUtf8) -import Effectful (Dispatch(Static), DispatchOf, Eff, IOE, (:>)) -import Effectful.Dispatch.Static qualified as E -import Effectful.Log (Log, localDomain, logInfo_, logTrace_) -import Effectful.Reader.Static (Reader, ask) -import Effectful.Resource (Resource) -import GHC.Records (HasField) -import Hsm.Core.Message (body, topic) -import Hsm.Core.Zmq (withSocket) -import Streamly.Data.Stream (Stream, repeatM) -import System.ZMQ4 qualified as Z - -data Client a b - -type instance DispatchOf Client = Static E.WithSideEffects - -newtype instance E.StaticRep Client = - Client (Z.Socket Z.Sub) - -domain :: Text -domain = "client" - -receiver :: - forall es a. (Log :> es, Client :> es, Binary a, Show a) - => Eff es a -receiver = do - Client sock <- E.getStaticRep - message <- E.unsafeEff_ $ Z.receive sock - localDomain domain - $ logTrace_ - $ "Message received [" - <> topic message - <> "]: " - <> pack (show $ body @a message) - return $ body message - -receive :: (Log :> es, Client :> es, Binary a, Show a) => Stream (Eff es) a -receive = repeatM receiver - -poll :: (Log :> es, Client :> es, Binary a, Show a) => Stream (Eff es) [a] -poll = - repeatM $ do - ms <- whileM newMsg receiver - localDomain domain - $ localDomain "poller" - $ logTrace_ - $ pack (show $ length ms) <> " new message(s) on queue" - return ms - where - newMsg = do - Client sock <- E.getStaticRep - peek <- E.unsafeEff_ $ Z.poll 0 [Z.Sock sock [Z.In] Nothing] - return $ peek /= [[]] - -runClient :: - forall env es a. - ( HasField "subEps" env [Text] - , HasField "topics" env [Text] - , IOE :> es - , Log :> es - , Reader env :> es - , Resource :> es - ) - => Eff (Client : es) a - -> Eff es a -runClient action = - withSocket Z.Sub >>= \sock -> - E.evalStaticRep (Client sock) $ do - localDomain domain $ do - logInfo_ "Initializing ZMQ client" - env <- ask @env - forM_ env.subEps $ E.unsafeEff_ . Z.connect sock . unpack - forM_ env.topics $ E.unsafeEff_ . Z.subscribe sock . encodeUtf8 - logTrace_ $ "Listening to " <> pack (show env.subEps) - logTrace_ $ "Subscribed to " <> pack (show env.topics) - action diff --git a/hsm-core/Hsm/Core/Zmq/Server.hs b/hsm-core/Hsm/Core/Zmq/Server.hs deleted file mode 100644 index 2e9217b..0000000 --- a/hsm-core/Hsm/Core/Zmq/Server.hs +++ /dev/null @@ -1,74 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeFamilies #-} - -module Hsm.Core.Zmq.Server - ( Server - , send - , runServer - ) where - -import Data.Binary (Binary) -import Data.Text (Text, pack, unpack) -import Effectful (Dispatch(Static), DispatchOf, Eff, IOE, (:>)) -import Effectful.Dispatch.Static qualified as E -import Effectful.Log (Log, localDomain, logInfo_, logTrace_) -import Effectful.Reader.Static (Reader, ask) -import Effectful.Resource (Resource) -import GHC.Records (HasField) -import Hsm.Core.Message (message) -import Hsm.Core.Zmq (withSocket) -import Streamly.Data.Fold qualified as S (drain) -import Streamly.Data.Stream qualified as S (Stream, fold, mapM) -import System.ZMQ4 qualified as Z - -data Server a b - -type instance DispatchOf Server = Static E.WithSideEffects - -newtype instance E.StaticRep Server = - Server (Z.Socket Z.Pub) - -domain :: Text -domain = "server" - -send :: - forall env es a. - ( HasField "name" env Text - , Log :> es - , Reader env :> es - , Server :> es - , Binary a - , Show a - ) - => S.Stream (Eff es) a - -> Eff es () -send = S.fold S.drain . S.mapM sender - where - sender payload = do - Server sock <- E.getStaticRep - env <- ask @env - E.unsafeEff_ $ Z.send sock [] $ message env.name payload - localDomain domain $ logTrace_ $ "Message sent: " <> pack (show payload) - -runServer :: - forall env es a. - ( HasField "pubEp" env Text - , IOE :> es - , Log :> es - , Reader env :> es - , Resource :> es - ) - => Eff (Server : es) a - -> Eff es a -runServer action = - withSocket Z.Pub >>= \sock -> - E.evalStaticRep (Server sock) $ do - localDomain domain $ do - logInfo_ "Initializing ZMQ server" - env <- ask @env - E.unsafeEff_ $ Z.bind sock $ unpack env.pubEp - logTrace_ $ "Publishing to " <> env.pubEp - action diff --git a/hsm-core/hsm-core.cabal b/hsm-core/hsm-core.cabal deleted file mode 100644 index bbdbbb3..0000000 --- a/hsm-core/hsm-core.cabal +++ /dev/null @@ -1,42 +0,0 @@ -cabal-version: 3.4 -author: Paul Oliver -build-type: Simple -maintainer: contact@pauloliver.dev -name: hsm-core -version: 0.1.0.0 - -library - build-depends: - , aeson - , base - , binary - , bytestring - , effectful-core - , log-base - , log-effectful - , monad-loops - , optparse-applicative - , optparse-text - , resourcet-effectful - , streamly-core - , template-haskell - , text - , time - , yaml - , zeromq4-haskell - - exposed-modules: - Hsm.Core.App - Hsm.Core.Env - Hsm.Core.Fsm - Hsm.Core.Log - Hsm.Core.Message - Hsm.Core.Zmq.Client - Hsm.Core.Zmq.Server - - other-modules: - Hsm.Core.Options - Hsm.Core.Zmq - - ghc-options: -Wall -Wunused-packages - default-language: GHC2021 |