summaryrefslogtreecommitdiff
path: root/hsm-core/Hsm/Core/App.hs
blob: 51f6d9408e7b20d48e937863f32c304c8cc29870 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{-# LANGUAGE ImportQualifiedPost #-}

-- Module     : Hsm.Core.App
-- Maintainer : contact@pauloliver.dev
module Hsm.Core.App
  ( launch
  )
where

import Control.Applicative ((<**>))
import Data.Aeson (FromJSON, Result (Error, Success), Value, fromJSON)
import Data.Aeson.Key (fromString)
import Data.Aeson.KeyMap (KeyMap, (!?))
import Data.Maybe (fromMaybe)
import Data.Text (pack, unpack)
import Data.Yaml (decodeFileThrow)
import Effectful.Log (LogLevel (LogInfo), Logger, readLogLevelEither, showLogLevel)
import Log.Backend.StandardOutput (withStdOutLogger)
import Options.Applicative qualified as P
import System.IO.Echo (withoutInputEcho)

data Options = Options String LogLevel

parser :: P.Parser Options
parser =
  Options
    <$> P.strOption
      ( P.help "Path to services config file"
          <> P.short 'c'
          <> P.long "config"
          <> P.metavar "PATH"
          <> P.value "config.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)
      )

-- Bootstraps an application by reading its settings from a provided
-- configuration file. A configuration must exist in the config file for the name
-- provided. A logger object, log level and read-only configuration are passed
-- down to the provided application.
launch :: FromJSON e => String -> (Logger -> LogLevel -> e -> IO a) -> IO a
launch name app =
  P.execParser info >>= \(Options path level) ->
    returnEnv path >>= \env ->
      withoutInputEcho $ withStdOutLogger $ \logger -> app logger level env
 where
  title :: String
  title = "Launch " <> name <> " service"

  description :: P.InfoMod Options
  description = P.fullDesc <> P.progDesc title

  info :: P.ParserInfo Options
  info = P.info (parser <**> P.helper) description

  err :: String
  err = "Service configuration for " <> name <> " not found"

  load :: KeyMap Value -> Value
  load configs = fromMaybe (error err) $ configs !? fromString name

  check :: Result e -> e
  check (Success e) = e
  check (Error str) = error str

  returnEnv :: FromJSON e => String -> IO e
  returnEnv = fmap (check . fromJSON . load) . decodeFileThrow