{-# LANGUAGE OverloadedStrings #-}
module Ecluse.Config.Resolve (
deepMerge,
buildEnvAst,
secretLeafKeys,
secretEnvSpellings,
envSpellingOf,
mountEnvKey,
) where
import Data.Aeson (Value (..), eitherDecodeStrict)
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Char (isUpper)
import Data.Text qualified as T
deepMerge :: Value -> Value -> Value
deepMerge :: Value -> Value -> Value
deepMerge (Object Object
l) (Object Object
r) = Object -> Value
Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ (Value -> Value -> Value) -> Object -> Object -> Object
forall v. (v -> v -> v) -> KeyMap v -> KeyMap v -> KeyMap v
KeyMap.unionWith Value -> Value -> Value
deepMerge Object
l Object
r
deepMerge Value
_ Value
r = Value
r
buildEnvAst :: [(String, String)] -> Value
buildEnvAst :: [(String, String)] -> Value
buildEnvAst [(String, String)]
env =
(Value -> Value -> Value) -> Value -> [Value] -> Value
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Value -> Value -> Value
deepMerge (Object -> Value
Object Object
forall v. KeyMap v
KeyMap.empty) (((Text, String) -> Value) -> [(Text, String)] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map (Text, String) -> Value
envVarValue [(Text, String)]
configVars)
where
configVars :: [(Text, String)]
configVars = [(Text
key, String
v) | (String
name, String
v) <- [(String, String)]
env, Just Text
key <- [Text -> Maybe Text
configEnvKey (String -> Text
T.pack String
name)]]
configEnvKey :: Text -> Maybe Text
configEnvKey :: Text -> Maybe Text
configEnvKey Text
name
| Text
name Text -> [Text] -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`elem` [Text]
reservedProcessKeys = Maybe Text
forall a. Maybe a
Nothing
| Bool
otherwise = Text -> Text -> Maybe Text
T.stripPrefix Text
"ECLUSE_" Text
name
reservedProcessKeys :: [Text]
reservedProcessKeys :: [Text]
reservedProcessKeys = [Text
"ECLUSE_CONFIG"]
secretLeafKeys :: [Text]
secretLeafKeys :: [Text]
secretLeafKeys = [Text
"authToken", Text
"mirrorTargetToken", Text
"publicationTargetToken"]
secretEnvSpellings :: [Text]
secretEnvSpellings :: [Text]
secretEnvSpellings = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
envSpellingOf [Text]
secretLeafKeys
envSpellingOf :: Text -> Text
envSpellingOf :: Text -> Text
envSpellingOf = Text -> Text
T.toUpper (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
underscoreUpper
where
underscoreUpper :: Char -> Text
underscoreUpper Char
c
| Char -> Bool
isUpper Char
c = Text
"_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Char -> Text
T.singleton Char
c
| Bool
otherwise = Char -> Text
T.singleton Char
c
mountEnvKey :: Text -> Text -> Text
mountEnvKey :: Text -> Text -> Text
mountEnvKey Text
ecosystem Text
key = Text
"ECLUSE_MOUNTS__" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
T.toUpper Text
ecosystem Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"__" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
key
envVarValue :: (Text, String) -> Value
envVarValue :: (Text, String) -> Value
envVarValue (Text
key, String
value) =
[Key] -> Value -> Value
nest [Key]
segments (Text -> Value
leafValue (String -> Text
T.pack String
value))
where
segments :: [Key]
segments = (Text -> Key) -> [Text] -> [Key]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Key
toCamelCase (HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"__" Text
key)
leafValue :: Text -> Value
leafValue
| Bool -> (Key -> Bool) -> Maybe Key -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False ((Text -> [Text] -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`elem` [Text]
secretLeafKeys) (Text -> Bool) -> (Key -> Text) -> Key -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Key -> Text
Key.toText) ([Key] -> Maybe Key
forall a. [a] -> Maybe a
listToMaybe ([Key] -> [Key]
forall a. [a] -> [a]
reverse [Key]
segments)) = Text -> Value
String
| Bool
otherwise = Text -> Value
parseEnvValue
toCamelCase :: Text -> Key.Key
toCamelCase :: Text -> Key
toCamelCase Text
t =
let words' :: [Text]
words' = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null) (HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"_" Text
t)
in Text -> Key
Key.fromText (Text -> Key) -> Text -> Key
forall a b. (a -> b) -> a -> b
$ case [Text]
words' of
[] -> Text
""
(Text
w : [Text]
ws) -> Text -> Text
T.toLower Text
w Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Text
T.concat ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
T.toTitle [Text]
ws)
nest :: [Key.Key] -> Value -> Value
nest :: [Key] -> Value -> Value
nest [] Value
v = Value
v
nest (Key
p : [Key]
ps) Value
v = Object -> Value
Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ Key -> Value -> Object
forall v. Key -> v -> KeyMap v
KeyMap.singleton Key
p ([Key] -> Value -> Value
nest [Key]
ps Value
v)
parseEnvValue :: Text -> Value
parseEnvValue :: Text -> Value
parseEnvValue Text
txt = case ByteString -> Either String Value
forall a. FromJSON a => ByteString -> Either String a
eitherDecodeStrict (Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 Text
txt) of
Right Value
v -> Value
v
Left String
_ -> Text -> Value
String Text
txt