slackblocks-3.1.0: src/Slackblocks/Internal/Wire.hs
module Slackblocks.Internal.Wire
( optional,
extensionPairs,
checkExtensions,
checkedParser,
wireObject,
wireTag,
required,
optionalField,
remaining,
coerceField,
stripTag,
restoreTask,
restoreTasks,
)
where
import Data.Aeson (ToJSON (..), Value (..), object, (.=))
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KM
import Data.Aeson.Types (Pair, Parser)
import Data.Bifunctor (first)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Slackblocks.Json
import Slackblocks.Validation
optional :: (ToJSON a) => Text -> Maybe a -> [Pair]
optional key = maybe [] (\value -> [Key.fromText key .= value])
extensionPairs :: Map Text Value -> [Pair]
extensionPairs = map (first Key.fromText) . Map.toList
checkExtensions :: [Text] -> Map Text Value -> Result ()
checkExtensions fields = mapM_ check . Map.keys
where
check key
| key == "" || key == "type" || key `elem` fields =
failure InvalidUsage [Field key] "extensions.reserved" "extension collides with a reserved field"
| otherwise = Right ()
checkedParser :: (FromSlackJSON a) => Value -> Parser a
checkedParser = either (fail . show) pure . fromSlackJSON
wireObject :: Text -> Value -> Result (KM.KeyMap Value)
wireObject tag (Object fields)
| tag == "" = Right fields
| KM.lookup "type" fields == Just (String tag) = Right (KM.delete "type" fields)
| otherwise = failure TypeMismatch [Field "type"] "type" "unexpected wire discriminator"
wireObject _ _ = failure TypeMismatch [] "type" "expected an object"
wireTag :: Value -> Result Text
wireTag (Object fields) = required "type" fields
wireTag _ = failure TypeMismatch [] "type" "expected an object with a discriminator"
required :: (FromSlackJSON a) => Text -> KM.KeyMap Value -> Result a
required key fields = case KM.lookup (Key.fromText key) fields of
Nothing -> absent
Just Null -> absent
Just value -> atField key (fromSlackJSON value)
where
absent = failure MissingRequired [Field key] "required" "required field is missing"
optionalField :: (FromSlackJSON a) => Text -> KM.KeyMap Value -> Result (Maybe a)
optionalField key fields = case KM.lookup (Key.fromText key) fields of
Nothing -> Right Nothing
Just Null -> Right Nothing
Just value -> Just <$> atField key (fromSlackJSON value)
remaining :: [Text] -> KM.KeyMap Value -> Map Text Value
remaining names =
Map.fromList
. map (first Key.toText)
. KM.toList
. KM.filterWithKey (\key _ -> Key.toText key `notElem` names)
coerceField :: Text -> Text -> KM.KeyMap Value -> KM.KeyMap Value
coerceField field tag = KM.mapWithKey (\key value -> if Key.toText key == field then coerce value else value)
where
coerce (Number value) | tag == "decimal_string" = String (decimalText value)
coerce value | tag == "decimal_string" = value
coerce (String value) = object ["type" .= tag, "text" .= value]
coerce (Array values) = Array (fmap coerce values)
coerce value = value
stripTag :: Value -> Value
stripTag (Object fields) = Object (KM.delete "type" fields)
stripTag value = value
restoreTasks :: KM.KeyMap Value -> KM.KeyMap Value
restoreTasks = KM.mapWithKey (\key value -> if key == "tasks" then restore value else value)
where
restore (Array tasks) = Array (fmap restoreTask tasks)
restore value = value
restoreTask :: Value -> Value
restoreTask (Object fields) = Object (if KM.member "type" fields then fields else KM.insert "type" (String "task_card") fields)
restoreTask value = value