2022-06-27 19:32:25 +03:00
|
|
|
-- | Utility functions for use defining autodocodec codecs.
|
2022-10-12 19:28:51 +03:00
|
|
|
module Hasura.Metadata.DTO.Utils
|
2022-12-15 23:37:00 +03:00
|
|
|
( boolConstCodec,
|
2023-02-20 20:08:49 +03:00
|
|
|
boundedEnumCodec,
|
2022-12-15 23:37:00 +03:00
|
|
|
codecNamePrefix,
|
|
|
|
discriminatorField,
|
2022-10-12 19:28:51 +03:00
|
|
|
fromEnvCodec,
|
|
|
|
optionalVersionField,
|
2022-10-13 20:56:03 +03:00
|
|
|
typeableName,
|
2022-12-15 23:37:00 +03:00
|
|
|
versionField,
|
2022-10-12 19:28:51 +03:00
|
|
|
)
|
|
|
|
where
|
2022-06-27 19:32:25 +03:00
|
|
|
|
|
|
|
import Autodocodec
|
2022-10-13 20:56:03 +03:00
|
|
|
import Data.Char (isAlphaNum)
|
2023-02-20 20:08:49 +03:00
|
|
|
import Data.List.NonEmpty qualified as NE
|
2022-06-27 19:32:25 +03:00
|
|
|
import Data.Scientific (Scientific)
|
2022-09-12 23:29:51 +03:00
|
|
|
import Data.Text qualified as T
|
|
|
|
import Data.Text.Extended qualified as T
|
2022-10-13 20:56:03 +03:00
|
|
|
import Data.Typeable (Proxy (Proxy), Typeable, typeRep)
|
2022-06-27 19:32:25 +03:00
|
|
|
import Hasura.Prelude
|
2022-09-12 23:29:51 +03:00
|
|
|
import Hasura.SQL.Tag (HasTag (backendTag), reify)
|
2022-06-27 19:32:25 +03:00
|
|
|
|
2022-12-15 23:37:00 +03:00
|
|
|
-- | Map a fixed set of two values to boolean values when serializing. The first
|
|
|
|
-- argument is the value to map to @True@, the second is the value to map to
|
|
|
|
-- @False@.
|
|
|
|
boolConstCodec :: Eq a => a -> a -> JSONCodec a
|
|
|
|
boolConstCodec trueCase falseCase =
|
|
|
|
dimapCodec
|
|
|
|
(bool trueCase falseCase)
|
|
|
|
(== trueCase)
|
|
|
|
$ codec @Bool
|
|
|
|
|
2023-02-20 20:08:49 +03:00
|
|
|
-- | A codec for a 'Bounded' 'Enum' that maps to literal strings using
|
|
|
|
-- a provided function.
|
|
|
|
--
|
|
|
|
--
|
|
|
|
-- === Example usage
|
|
|
|
--
|
|
|
|
-- >>> data Fruit = FruitApple | FruitOrange deriving (Show, Eq, Enum, Bounded)
|
|
|
|
-- >>> let c = boundedEnumCodec (snakeCase . drop 5)
|
|
|
|
-- >>> toJSONVia c Apple
|
|
|
|
-- String "apple"
|
|
|
|
-- >>> JSON.parseMaybe (parseJSONVia c) (String "orange") :: Maybe Fruit
|
|
|
|
-- Just Orange
|
|
|
|
boundedEnumCodec ::
|
|
|
|
forall enum.
|
|
|
|
(Eq enum, Enum enum, Bounded enum) =>
|
|
|
|
(enum -> String) ->
|
|
|
|
JSONCodec enum
|
|
|
|
boundedEnumCodec display =
|
|
|
|
let ls = [minBound .. maxBound]
|
|
|
|
in case NE.nonEmpty ls of
|
|
|
|
Nothing -> error "0 enum values ?!"
|
|
|
|
Just ne -> stringConstCodec (NE.map (\v -> (v, T.pack (display v))) ne)
|
|
|
|
|
2022-06-27 19:32:25 +03:00
|
|
|
-- | Defines a required object field named @version@ that must have the given
|
|
|
|
-- integer value. On serialization the field will have the given value
|
|
|
|
-- automatically. On deserialization parsing will fail unless the field has the
|
|
|
|
-- exact given value.
|
|
|
|
versionField :: Integer -> ObjectCodec a Scientific
|
|
|
|
versionField v = requiredFieldWith' "version" (EqCodec n scientificCodec) .= const n
|
|
|
|
where
|
|
|
|
n = fromInteger v
|
|
|
|
|
|
|
|
-- | Defines an optional object field named @version@ that must have the given
|
|
|
|
-- integer value if the field is present. On serialization the field will have
|
|
|
|
-- the given value automatically. On deserialization parsing will fail unless
|
|
|
|
-- the field has the exact given value, or is absent.
|
|
|
|
optionalVersionField :: Integer -> ObjectCodec a (Maybe Scientific)
|
|
|
|
optionalVersionField v =
|
|
|
|
optionalFieldWith' "version" (EqCodec n scientificCodec) .= const (Just n)
|
|
|
|
where
|
|
|
|
n = fromInteger v
|
2022-09-12 23:29:51 +03:00
|
|
|
|
2022-12-15 23:37:00 +03:00
|
|
|
-- | Useful in an object codec for a field that indicates the type of the
|
|
|
|
-- object within a union.
|
2023-02-20 20:08:49 +03:00
|
|
|
discriminatorField :: Text -> Text -> ObjectCodec a ()
|
2022-12-15 23:37:00 +03:00
|
|
|
discriminatorField name value =
|
|
|
|
dimapCodec (const ()) (const value) $
|
|
|
|
requiredFieldWith' name (literalTextCodec value)
|
|
|
|
|
2022-09-12 23:29:51 +03:00
|
|
|
-- | Provides a title-cased name for a database kind, inferring the appropriate
|
|
|
|
-- database kind from type context.
|
|
|
|
codecNamePrefix :: forall b. (HasTag b) => Text
|
|
|
|
codecNamePrefix = T.toTitle $ T.toTxt $ reify $ backendTag @b
|
2022-10-12 19:28:51 +03:00
|
|
|
|
2022-10-13 20:56:03 +03:00
|
|
|
-- | Provides a string based on the given type to use to uniquely name
|
|
|
|
-- instantiations of polymorphic codecs.
|
|
|
|
typeableName :: forall a. (Typeable a) => Text
|
|
|
|
typeableName = T.map toValidChar $ tshow $ typeRep (Proxy @a)
|
|
|
|
where
|
|
|
|
toValidChar c = if isAlphaNum c then c else '_'
|
|
|
|
|
2022-10-12 19:28:51 +03:00
|
|
|
-- | Represents a text field wrapped in an object with a single property
|
|
|
|
-- named @from_env@.
|
|
|
|
--
|
|
|
|
-- Objects of this form appear in many places in the Metadata API. If we
|
|
|
|
-- reproduced this codec in each use case the OpenAPI document would have many
|
|
|
|
-- identical object definitions. Using a shared codec allows a single shared
|
|
|
|
-- reference.
|
|
|
|
fromEnvCodec :: JSONCodec Text
|
|
|
|
fromEnvCodec = object "FromEnv" $ requiredField' "from_env"
|