mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 20:41:49 +03:00
d96203f602
This is mainly about removing `StandaloneKindSignatures`. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7428 GitOrigin-RevId: 9b28c9b119f50c49a1b5c48391d537f1575700b4
195 lines
5.8 KiB
Haskell
195 lines
5.8 KiB
Haskell
{-# LANGUAGE DeriveAnyClass #-}
|
|
{-# LANGUAGE OverloadedLists #-}
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
-- | The 'Transform' typeclass with various types and helper functions
|
|
-- for evaluating transformations.
|
|
module Hasura.RQL.DDL.Webhook.Transform.Class
|
|
( -- * Transformation Interface and Utilities
|
|
Transform (..),
|
|
|
|
-- ** Error Context
|
|
TransformErrorBundle (..),
|
|
throwErrorBundle,
|
|
|
|
-- * Templating
|
|
TemplatingEngine (..),
|
|
Template (..),
|
|
|
|
-- * Unescaped
|
|
UnescapedTemplate (..),
|
|
wrapUnescapedTemplate,
|
|
encodeScalar,
|
|
)
|
|
where
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
import Autodocodec (HasCodec (codec), dimapCodec, stringConstCodec)
|
|
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
|
|
import Data.Aeson qualified as J
|
|
import Data.ByteString (ByteString)
|
|
import Data.ByteString.Builder.Extra (toLazyByteStringWith, untrimmedStrategy)
|
|
import Data.ByteString.Builder.Scientific (scientificBuilder)
|
|
import Data.ByteString.Lazy qualified as LBS
|
|
import Data.Kind (Type)
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
import Data.Validation (Validation)
|
|
import Hasura.Prelude
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- | 'Transform' describes how to reify a defunctionalized transformation for
|
|
-- a particular request field.
|
|
class Transform a where
|
|
-- | The associated type 'TransformFn a' is the defunctionalized version
|
|
-- of some transformation that should be applied to a given request field.
|
|
--
|
|
-- In most cases it is some variation on a piece of template text describing
|
|
-- the transformation.
|
|
data TransformFn a :: Type
|
|
|
|
data TransformCtx a :: Type
|
|
|
|
-- | 'transform' is a function which takes 'TransformFn' of @a@ and reifies
|
|
-- it into a function of the form:
|
|
--
|
|
-- @
|
|
-- ReqTransformCtx -> a -> m a
|
|
-- @
|
|
transform ::
|
|
MonadError TransformErrorBundle m =>
|
|
TransformFn a ->
|
|
TransformCtx a ->
|
|
a ->
|
|
m a
|
|
|
|
-- | Validate a 'TransformFn' of @a@.
|
|
validate ::
|
|
TemplatingEngine ->
|
|
TransformFn a ->
|
|
Validation TransformErrorBundle ()
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- | We use collect all transformation failures as a '[J.Value]'.
|
|
newtype TransformErrorBundle = TransformErrorBundle
|
|
{ tebMessages :: [J.Value]
|
|
}
|
|
deriving stock (Eq, Generic, Show)
|
|
deriving newtype (Monoid, Semigroup, FromJSON, ToJSON)
|
|
deriving anyclass (NFData)
|
|
|
|
-- | A helper function for serializing transformation errors to JSON.
|
|
throwErrorBundle ::
|
|
MonadError TransformErrorBundle m =>
|
|
Text ->
|
|
Maybe J.Value ->
|
|
m a
|
|
throwErrorBundle msg val = do
|
|
let requiredCtx =
|
|
[ "error_code" J..= ("TransformationError" :: Text),
|
|
"message" J..= msg
|
|
]
|
|
optionalCtx =
|
|
[ ("value" J..=) <$> val
|
|
]
|
|
err = J.object (requiredCtx <> catMaybes optionalCtx)
|
|
throwError $ TransformErrorBundle [err]
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- | Available templating engines.
|
|
data TemplatingEngine
|
|
= Kriti
|
|
deriving stock (Bounded, Enum, Eq, Generic, Show)
|
|
deriving anyclass (NFData)
|
|
|
|
instance HasCodec TemplatingEngine where
|
|
codec = stringConstCodec [(Kriti, "Kriti")]
|
|
|
|
-- XXX(jkachmar): We need roundtrip tests for these instances.
|
|
instance FromJSON TemplatingEngine where
|
|
parseJSON =
|
|
J.genericParseJSON
|
|
J.defaultOptions
|
|
{ J.tagSingleConstructors = True
|
|
}
|
|
|
|
-- XXX(jkachmar): We need roundtrip tests for these instances.
|
|
instance ToJSON TemplatingEngine where
|
|
toJSON =
|
|
J.genericToJSON
|
|
J.defaultOptions
|
|
{ J.tagSingleConstructors = True
|
|
}
|
|
|
|
toEncoding =
|
|
J.genericToEncoding
|
|
J.defaultOptions
|
|
{ J.tagSingleConstructors = True
|
|
}
|
|
|
|
-- | Textual transformation template.
|
|
newtype Template = Template
|
|
{ unTemplate :: Text
|
|
}
|
|
deriving stock (Eq, Generic, Ord, Show)
|
|
deriving newtype (Hashable, FromJSONKey, ToJSONKey)
|
|
deriving anyclass (NFData)
|
|
|
|
instance HasCodec Template where
|
|
codec = dimapCodec Template unTemplate codec
|
|
|
|
instance J.FromJSON Template where
|
|
parseJSON = J.withText "Template" (pure . Template)
|
|
|
|
instance J.ToJSON Template where
|
|
toJSON = J.String . coerce
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- | Validated textual transformation template /for string
|
|
-- interpolation only/.
|
|
--
|
|
-- This is necessary due to Kriti not distinguishing between string
|
|
-- literals and string templates.
|
|
newtype UnescapedTemplate = UnescapedTemplate
|
|
{ getUnescapedTemplate :: Text
|
|
}
|
|
deriving stock (Eq, Generic, Ord, Show)
|
|
deriving newtype (Hashable, FromJSONKey, ToJSONKey)
|
|
deriving anyclass (NFData)
|
|
|
|
instance HasCodec UnescapedTemplate where
|
|
codec = dimapCodec UnescapedTemplate getUnescapedTemplate codec
|
|
|
|
instance J.FromJSON UnescapedTemplate where
|
|
parseJSON = J.withText "Template" (pure . UnescapedTemplate)
|
|
|
|
instance J.ToJSON UnescapedTemplate where
|
|
toJSON = J.String . coerce
|
|
|
|
-- | Wrap an 'UnescapedTemplate' with escaped double quotes.
|
|
wrapUnescapedTemplate :: UnescapedTemplate -> Template
|
|
wrapUnescapedTemplate (UnescapedTemplate txt) = Template $ "\"" <> txt <> "\""
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Utility functions.
|
|
|
|
-- | Encode a JSON Scalar Value as a 'ByteString'.
|
|
-- If a non-Scalar value is provided, will return a 'TrnasformErrorBundle'
|
|
encodeScalar ::
|
|
MonadError TransformErrorBundle m =>
|
|
J.Value ->
|
|
m ByteString
|
|
encodeScalar = \case
|
|
J.String str -> pure $ encodeUtf8 str
|
|
J.Number num ->
|
|
-- like toLazyByteString, but tuned for output and for common small size:
|
|
pure . LBS.toStrict . toLazyByteStringWith (untrimmedStrategy 24 1024) "" $ scientificBuilder num
|
|
J.Bool True -> pure "true"
|
|
J.Bool False -> pure "false"
|
|
val ->
|
|
throwErrorBundle "Template must produce a String, Number, or Boolean value" (Just val)
|