module Hercules.Formats.Secret where
import Data.Aeson
import qualified Data.Aeson.Key as AK
import qualified Data.Aeson.KeyMap as AM
import qualified Data.Aeson.Types as A
import Data.Foldable (toList)
import qualified Data.HashMap.Strict as HM
import Hercules.API.Prelude
import Hercules.Formats.Common
( noVersion,
withKind,
withVersions,
)
data Condition
= Or [Condition]
| And [Condition]
| IsDefaultBranch
| IsBranch Text
| IsTag
| IsRepo Text
| IsOwner Text
deriving (Generic, Eq, Read, Show)
instance ToJSON Condition where
toJSON (Or a) = object ["or" .= a]
toJSON (And a) = object ["and" .= a]
toJSON IsDefaultBranch = String "isDefaultBranch"
toJSON IsTag = String "isTag"
toJSON (IsBranch a) = object ["isBranch" .= a]
toJSON (IsRepo a) = object ["isRepo" .= a]
toJSON (IsOwner a) = object ["isOwner" .= a]
instance FromJSON Condition where
parseJSON (String "isTag") = pure IsTag
parseJSON (String "isDefaultBranch") = pure IsDefaultBranch
parseJSON (Object o) =
case AM.toList o of
[] -> fail "The empty object does not represent a Condition."
[(k, v)] -> case HM.lookup (AK.toText k) taggedConditionParsers of
Nothing -> fail $ "The field name in a Condition object must be one of " <> show (map fst (HM.toList taggedConditionParsers))
Just p -> p v
_ -> fail "A Condition object must contain a single field."
parseJSON _ = fail "Expected Object or String."
taggedConditionParsers :: HM.HashMap Text (Value -> A.Parser Condition)
taggedConditionParsers =
HM.fromList
[ ( "or",
\v -> do
params <- parseJSON v
Or <$> traverse parseJSON params
),
( "and",
\v -> do
params <- parseJSON v
And <$> traverse parseJSON params
),
("isBranch", fmap IsBranch . parseJSON),
("isRepo", fmap IsRepo . parseJSON),
("isOwner", fmap IsOwner . parseJSON)
]
{-
This was awful:
instance FromJSON Condition where
parseJSON (String "isTag") = pure IsTag
parseJSON (String "isDefaultBranch") = pure IsDefaultBranch
parseJSON j = do
l <- parseJSON j
case l of
[] -> fail "The empty list does not represent a Condition."
(jTag : args) -> do
tag <- parseJSON jTag
case tag :: Text of
"or" -> do
Or <$> traverse parseJSON args
"and" -> do
And <$> traverse parseJSON args
"isDefaultBranch" -> do
IsDefaultBranch <$ noParams tag args
"isTag" -> do
IsTag <$ noParams tag args
"isBranch" -> do
IsBranch <$> traverse parseJSON args
"isRepo" -> do
IsRepo <$> traverse parseJSON args
"isOwner" -> do
IsOwner <$> traverse parseJSON args
t -> do
fail $ "Unknown tag " <> show t <> " in Condition."
where
noParams _ [] = pure ()
noParams tag _ = fail $ "Condition with tag " <> show tag <> " does not take any parameters."
instance ToJSON Condition where
toJSON (Or a) = toJSON (String "or" : map toJSON a)
toJSON (And a) = toJSON (String "and" : map toJSON a)
toJSON IsDefaultBranch = String "isDefaultBranch"
toJSON IsTag = String "isTag"
toJSON (IsBranch a) = toJSON (String "isBranch" : map toJSON a)
toJSON (IsRepo a) = toJSON (String "isRepo" : map toJSON a)
toJSON (IsOwner a) = toJSON (String "isOwner" : map toJSON a)
-}
-- | Arbitrary secret like keys, tokens, passwords etc.
data Secret = Secret
{ data_ :: Map Text Value,
condition :: Maybe Condition
}
instance ToJSON Secret where
toJSON a =
object
(["kind" .= String "Secret", "data" .= data_ a] ++ ["condition" .= x | x <- toList (condition a)])
toEncoding a =
pairs
("kind" .= String "Secret" <> "data" .= data_ a <> foldMap ("condition" .=) (condition a))
instance FromJSON Secret where
parseJSON =
withKind "Secret" $
withVersions
[ noVersion $ \o ->
Secret
<$> o .: "data"
<*> o .:? "condition"
]