mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-20 06:58:39 +03:00
Feature/removable request transform body and modified request transform API
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3475 GitOrigin-RevId: bc847b18d491fe4957a190f5d0fe2ae6e6719791
This commit is contained in:
parent
972caf65f3
commit
d1ba271c3d
@ -3,7 +3,7 @@
|
||||
## Next release
|
||||
|
||||
### Bug fixes and improvements
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
- server: Webhook Transforms can now delete request/response bodies explicitly.
|
||||
|
||||
## v2.3.0-beta.1
|
||||
|
||||
|
@ -2042,6 +2042,10 @@ RequestTransformation
|
||||
- required
|
||||
- Schema
|
||||
- Description
|
||||
* - version
|
||||
- false
|
||||
- "1" | "2"
|
||||
- Sets the `RequestTransformation` schema version. Version `1` uses a `String` for the `body` field and Version `2` takes a :ref:`BodyTransform`. `Defaults to version `1`.
|
||||
* - method
|
||||
- false
|
||||
- String
|
||||
@ -2052,7 +2056,7 @@ RequestTransformation
|
||||
- Change the request URL to this value.
|
||||
* - body
|
||||
- false
|
||||
- String
|
||||
- :ref:`BodyTransform` | String
|
||||
- A template script for transforming the request body.
|
||||
* - content_type
|
||||
- false
|
||||
@ -2092,7 +2096,6 @@ TransformHeaders
|
||||
- Array of (:ref:`HeaderKey`)
|
||||
- Headers to be removed from the request. Content-Type cannot be removed.
|
||||
|
||||
|
||||
.. _HeaderKey:
|
||||
|
||||
HeaderKey
|
||||
@ -2113,6 +2116,29 @@ HeaderValue
|
||||
|
||||
String
|
||||
|
||||
|
||||
.. _BodyTransform:
|
||||
|
||||
BodyTransform
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- required
|
||||
- Schema
|
||||
- Description
|
||||
* - action
|
||||
- true
|
||||
- remove | transform
|
||||
- The action to perform on the request body.
|
||||
* - template
|
||||
- false
|
||||
- String
|
||||
- The transformation template to be applied to the body. This is
|
||||
required if the action is `transform`.
|
||||
|
||||
.. _TemplateEngine:
|
||||
|
||||
TemplateEngine
|
||||
@ -2137,9 +2163,13 @@ ResponseTransformation
|
||||
- required
|
||||
- Schema
|
||||
- Description
|
||||
* - version
|
||||
- false
|
||||
- "1" | "2"
|
||||
- Sets the `RequestTransformation` schema version. Version `1` uses a `String` for the `body` field and Version `2` takes a :ref:`BodyTransform`. `Defaults to version `1`.
|
||||
* - body
|
||||
- false
|
||||
- String
|
||||
- :ref:`BodyTransform` | String
|
||||
- A template script for transforming the response body.
|
||||
* - template_engine
|
||||
- false
|
||||
|
@ -1,3 +1,5 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
|
||||
module Hasura.RQL.DDL.WebhookTransforms
|
||||
( applyRequestTransform,
|
||||
applyResponseTransform,
|
||||
@ -6,17 +8,18 @@ module Hasura.RQL.DDL.WebhookTransforms
|
||||
mkRequestTransform,
|
||||
mkResponseTransform,
|
||||
RequestMethod (..),
|
||||
RemoveOrTransform (..),
|
||||
StringTemplateText (..),
|
||||
TemplatingEngine (..),
|
||||
TemplateText (..),
|
||||
ContentType (..),
|
||||
ReqTransformCtx (..),
|
||||
TransformHeaders (..),
|
||||
TransformErrorBundle (..),
|
||||
TransformHeaders (..),
|
||||
MetadataRequestTransform (..),
|
||||
MetadataResponseTransform (..),
|
||||
RequestTransform (..),
|
||||
ResponseTransform (..),
|
||||
Version (..),
|
||||
)
|
||||
where
|
||||
|
||||
@ -24,13 +27,14 @@ import Control.Lens (traverseOf, view)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Bifunctor (bimap, first)
|
||||
import Data.Bitraversable
|
||||
import Data.ByteString qualified as B
|
||||
import Data.ByteString.Lazy qualified as BL
|
||||
import Data.CaseInsensitive qualified as CI
|
||||
import Data.Either.Validation
|
||||
import Data.HashMap.Strict qualified as M
|
||||
import Data.List (nubBy)
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Encoding qualified as TE
|
||||
import Data.Validation qualified as V
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude hiding (first)
|
||||
import Hasura.Session (SessionVariables)
|
||||
@ -86,13 +90,11 @@ data RespTransformCtx = RespTransformCtx
|
||||
-- use the original request value, unless otherwise indicated.
|
||||
data RequestTransform = RequestTransform
|
||||
{ -- | Change the request method to one provided here. Nothing means POST.
|
||||
reqTransformRequestMethod :: Maybe RequestMethod,
|
||||
reqTransformRequestMethod :: Maybe (ReqTransformCtx -> Either TransformErrorBundle RequestMethod),
|
||||
-- | A function which contructs a new URL given the request context.
|
||||
reqTransformRequestURL :: Maybe (ReqTransformCtx -> Either TransformErrorBundle Text),
|
||||
-- | A function for transforming the request body.
|
||||
reqTransformBody :: Maybe (ReqTransformCtx -> Either TransformErrorBundle J.Value),
|
||||
-- | Change content type to one provided here.
|
||||
reqTransformContentType :: Maybe ContentType,
|
||||
reqTransformBody :: Maybe (RemoveOrTransform (ReqTransformCtx -> Either TransformErrorBundle J.Value)),
|
||||
-- | A function which contructs new query parameters given the request context.
|
||||
reqTransformQueryParams :: Maybe (ReqTransformCtx -> Either TransformErrorBundle HTTP.Query),
|
||||
-- | A function which contructs new Headers given the request context.
|
||||
@ -102,7 +104,7 @@ data RequestTransform = RequestTransform
|
||||
-- | A set of data transformation functions generated from a
|
||||
-- 'MetadataResponseTransform'. 'Nothing' means use the original
|
||||
-- response value.
|
||||
newtype ResponseTransform = ResponseTransform {respTransformBody :: Maybe (RespTransformCtx -> Either TransformErrorBundle J.Value)}
|
||||
newtype ResponseTransform = ResponseTransform {respTransformBody :: Maybe (RemoveOrTransform (RespTransformCtx -> Either TransformErrorBundle J.Value))}
|
||||
|
||||
-- | A de/serializable request transformation template which can be stored in
|
||||
-- the metadata associated with an action/event trigger/etc. and used to produce
|
||||
@ -119,114 +121,133 @@ newtype ResponseTransform = ResponseTransform {respTransformBody :: Maybe (RespT
|
||||
-- Nothing values mean use the original request value, unless
|
||||
-- otherwise indicated.
|
||||
data MetadataRequestTransform = MetadataRequestTransform
|
||||
{ -- | Change the request method to one provided here. Nothing means POST.
|
||||
mtRequestMethod :: Maybe RequestMethod,
|
||||
{ -- | The Schema Version
|
||||
mtVersion :: Version,
|
||||
-- | Change the request method to one provided here. Nothing means POST.
|
||||
mtRequestMethod :: Maybe StringTemplateText,
|
||||
-- | Template script for transforming the URL.
|
||||
mtRequestURL :: Maybe StringTemplateText,
|
||||
-- | Template script for transforming the request body.
|
||||
mtBodyTransform :: Maybe TemplateText,
|
||||
-- | Replace the Content-Type with this value. Only
|
||||
-- "application/json" and "application/x-www-form-urlencoded" are
|
||||
-- allowed.
|
||||
mtContentType :: Maybe ContentType,
|
||||
mtBodyTransform :: Maybe (RemoveOrTransform TemplateText),
|
||||
-- | A list of template scripts for constructing new Query Params.
|
||||
mtQueryParams :: Maybe [(StringTemplateText, Maybe StringTemplateText)],
|
||||
-- | Transform headers as defined here.
|
||||
-- | A list of template scripts for constructing headers.
|
||||
mtRequestHeaders :: Maybe TransformHeaders,
|
||||
-- | The template engine to use for transformations. Default: Kriti
|
||||
mtTemplatingEngine :: TemplatingEngine
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance NFData MetadataRequestTransform
|
||||
|
||||
instance Cacheable MetadataRequestTransform
|
||||
|
||||
infixr 8 .=?
|
||||
|
||||
(.=?) :: J.ToJSON v => k -> Maybe v -> Maybe (k, J.Value)
|
||||
(.=?) _ Nothing = Nothing
|
||||
(.=?) k (Just v) = Just (k, J.toJSON v)
|
||||
deriving stock (Show, Eq, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.ToJSON MetadataRequestTransform where
|
||||
toJSON MetadataRequestTransform {..} =
|
||||
J.object $
|
||||
["template_engine" J..= mtTemplatingEngine]
|
||||
<> catMaybes
|
||||
[ "method" .=? mtRequestMethod,
|
||||
"url" .=? mtRequestURL,
|
||||
"body" .=? mtBodyTransform,
|
||||
"content_type" .=? mtContentType,
|
||||
"query_params" .=? fmap M.fromList mtQueryParams,
|
||||
"request_headers" .=? mtRequestHeaders
|
||||
let body = case mtVersion of
|
||||
V1 -> case mtBodyTransform of
|
||||
Just (Transform template) -> Just ("body", J.toJSON template)
|
||||
_ -> Nothing
|
||||
V2 -> "body" .=? mtBodyTransform
|
||||
in J.object $
|
||||
[ "template_engine" J..= mtTemplatingEngine,
|
||||
"version" J..= mtVersion
|
||||
]
|
||||
<> catMaybes
|
||||
[ "method" .=? mtRequestMethod,
|
||||
"url" .=? mtRequestURL,
|
||||
"query_params" .=? fmap M.fromList mtQueryParams,
|
||||
"request_headers" .=? mtRequestHeaders,
|
||||
body
|
||||
]
|
||||
|
||||
instance J.FromJSON MetadataRequestTransform where
|
||||
parseJSON = J.withObject "Object" $ \o -> do
|
||||
version <- o J..:? "version" J..!= V1
|
||||
method <- o J..:? "method"
|
||||
url <- o J..:? "url"
|
||||
body <- o J..:? "body"
|
||||
contentType <- o J..:? "content_type"
|
||||
body <- case version of
|
||||
V1 -> do
|
||||
template :: (Maybe TemplateText) <- o J..:? "body"
|
||||
pure $ fmap Transform template
|
||||
V2 -> o J..:? "body"
|
||||
queryParams' <- o J..:? "query_params"
|
||||
let queryParams = fmap M.toList queryParams'
|
||||
headers <- o J..:? "request_headers"
|
||||
templateEngine <- o J..:? "template_engine"
|
||||
let templateEngine' = fromMaybe Kriti templateEngine
|
||||
pure $ MetadataRequestTransform method url body contentType queryParams headers templateEngine'
|
||||
templateEngine <- o J..:? "template_engine" J..!= Kriti
|
||||
pure $ MetadataRequestTransform version method url body queryParams headers templateEngine
|
||||
|
||||
data MetadataResponseTransform = MetadataResponseTransform
|
||||
{ mrtBodyTransform :: Maybe TemplateText,
|
||||
{ -- | The Schema Version
|
||||
mrtVersion :: Version,
|
||||
-- | Template script for transforming the response body.
|
||||
mrtBodyTransform :: Maybe (RemoveOrTransform TemplateText),
|
||||
-- | The template engine to use for transformations. Default: Kriti
|
||||
mrtTemplatingEngine :: TemplatingEngine
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance NFData MetadataResponseTransform
|
||||
|
||||
instance Cacheable MetadataResponseTransform
|
||||
deriving stock (Show, Eq, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.ToJSON MetadataResponseTransform where
|
||||
toJSON MetadataResponseTransform {..} =
|
||||
J.object $
|
||||
["template_engine" J..= mrtTemplatingEngine]
|
||||
<> catMaybes ["body" .=? mrtBodyTransform]
|
||||
let body = case mrtVersion of
|
||||
V1 -> case mrtBodyTransform of
|
||||
Just (Transform template) -> Just ("body", J.toJSON template)
|
||||
_ -> Nothing
|
||||
V2 -> "body" .=? mrtBodyTransform
|
||||
in J.object $
|
||||
[ "template_engine" J..= mrtTemplatingEngine,
|
||||
"version" J..= mrtVersion
|
||||
]
|
||||
<> catMaybes [body]
|
||||
|
||||
instance J.FromJSON MetadataResponseTransform where
|
||||
parseJSON = J.withObject "Object" $ \o -> do
|
||||
body <- o J..:? "body"
|
||||
version <- o J..:? "version" J..!= V1
|
||||
body <- case version of
|
||||
V1 -> do
|
||||
template :: (Maybe TemplateText) <- o J..:? "body"
|
||||
pure $ fmap Transform template
|
||||
V2 -> o J..:? "body"
|
||||
templateEngine <- o J..:? "template_engine"
|
||||
let templateEngine' = fromMaybe Kriti templateEngine
|
||||
pure $ MetadataResponseTransform body templateEngine'
|
||||
pure $ MetadataResponseTransform version body templateEngine'
|
||||
|
||||
data RequestMethod = GET | POST | PUT | PATCH | DELETE
|
||||
deriving (Show, Eq, Enum, Bounded, Generic)
|
||||
data RemoveOrTransform a = Remove | Transform a
|
||||
deriving stock (Show, Eq, Functor, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
renderRequestMethod :: RequestMethod -> Text
|
||||
renderRequestMethod = \case
|
||||
GET -> "GET"
|
||||
POST -> "POST"
|
||||
PUT -> "PUT"
|
||||
PATCH -> "PATCH"
|
||||
DELETE -> "DELETE"
|
||||
instance J.ToJSON a => J.ToJSON (RemoveOrTransform a) where
|
||||
toJSON = \case
|
||||
Remove -> J.object ["action" J..= ("remove" :: T.Text)]
|
||||
Transform a ->
|
||||
J.object
|
||||
[ "action" J..= ("transform" :: T.Text),
|
||||
"template" J..= J.toJSON a
|
||||
]
|
||||
|
||||
instance J.FromJSON a => J.FromJSON (RemoveOrTransform a) where
|
||||
parseJSON = J.withObject "RemoveOrTransform" $ \o -> do
|
||||
action :: T.Text <- o J..: "action"
|
||||
if action == "remove"
|
||||
then pure Remove
|
||||
else do
|
||||
"transform" :: T.Text <- o J..: "action"
|
||||
template <- o J..: "template"
|
||||
pure $ Transform template
|
||||
|
||||
newtype RequestMethod = RequestMethod (CI.CI T.Text)
|
||||
deriving stock (Generic)
|
||||
deriving newtype (Show, Eq)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.ToJSON RequestMethod where
|
||||
toJSON = J.String . renderRequestMethod
|
||||
toJSON = J.String . CI.original . coerce
|
||||
|
||||
instance J.FromJSON RequestMethod where
|
||||
parseJSON = J.withText "RequestMethod" \case
|
||||
"GET" -> pure GET
|
||||
"POST" -> pure POST
|
||||
"PUT" -> pure PUT
|
||||
"PATCH" -> pure PATCH
|
||||
"DELETE" -> pure DELETE
|
||||
_ -> fail "Invalid Request Method"
|
||||
|
||||
instance NFData RequestMethod
|
||||
|
||||
instance Cacheable RequestMethod
|
||||
parseJSON = J.withText "RequestMethod" (pure . coerce . CI.mk)
|
||||
|
||||
-- | Available Template Languages
|
||||
data TemplatingEngine = Kriti
|
||||
deriving (Show, Eq, Enum, Bounded, Generic)
|
||||
deriving stock (Show, Eq, Enum, Bounded, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
renderTemplatingEngine :: TemplatingEngine -> Text
|
||||
renderTemplatingEngine _ = "Kriti"
|
||||
@ -239,18 +260,28 @@ instance J.FromJSON TemplatingEngine where
|
||||
instance J.ToJSON TemplatingEngine where
|
||||
toJSON = J.String . renderTemplatingEngine
|
||||
|
||||
instance NFData TemplatingEngine
|
||||
data Version = V1 | V2
|
||||
deriving stock (Show, Eq, Generic)
|
||||
deriving anyclass (NFData, Cacheable, Hashable)
|
||||
|
||||
instance Cacheable TemplatingEngine
|
||||
instance J.FromJSON Version where
|
||||
parseJSON v = do
|
||||
version :: Int <- J.parseJSON v
|
||||
case version of
|
||||
1 -> pure V1
|
||||
2 -> pure V2
|
||||
i -> fail $ "expected 1 or 3, encountered " ++ show i
|
||||
|
||||
instance J.ToJSON Version where
|
||||
toJSON = \case
|
||||
V1 -> J.toJSON @Int 1
|
||||
V2 -> J.toJSON @Int 2
|
||||
|
||||
-- | Unparsed Kriti templating code
|
||||
newtype TemplateText = TemplateText {unTemplateText :: T.Text}
|
||||
deriving stock (Show, Eq, Ord, Generic)
|
||||
deriving newtype (Hashable, J.ToJSONKey, J.FromJSONKey)
|
||||
|
||||
instance NFData TemplateText
|
||||
|
||||
instance Cacheable TemplateText
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.FromJSON TemplateText where
|
||||
parseJSON = J.withText "TemplateText" \t ->
|
||||
@ -297,54 +328,22 @@ instance J.FromJSON StringTemplateText where
|
||||
instance J.ToJSON StringTemplateText where
|
||||
toJSON = J.String . coerce
|
||||
|
||||
-- | Wrap a template with escaped double quotes.
|
||||
wrapTemplate :: StringTemplateText -> StringTemplateText
|
||||
wrapTemplate (StringTemplateText t) = StringTemplateText $ "\"" <> t <> "\""
|
||||
|
||||
data ContentType = JSON | XWWWFORM
|
||||
deriving (Show, Eq, Enum, Bounded, Generic)
|
||||
|
||||
renderContentType :: ContentType -> Text
|
||||
renderContentType = \case
|
||||
JSON -> "application/json"
|
||||
XWWWFORM -> "application/x-www-form-urlencoded"
|
||||
|
||||
instance J.ToJSON ContentType where
|
||||
toJSON = J.String . renderContentType
|
||||
|
||||
instance J.FromJSON ContentType where
|
||||
parseJSON = J.withText "ContentType" \case
|
||||
"application/json" -> pure JSON
|
||||
"application/x-www-form-urlencoded" -> pure XWWWFORM
|
||||
_ -> fail "Invalid ContentType"
|
||||
|
||||
instance NFData ContentType
|
||||
|
||||
instance Cacheable ContentType
|
||||
|
||||
-- | This newtype exists solely to anchor a `FromJSON` instance and is
|
||||
-- eliminated in the `TransformHeaders` `FromJSON` instance.
|
||||
newtype HeaderKey = HeaderKey {unHeaderKey :: CI.CI Text}
|
||||
deriving (Show, Eq, Ord, Generic)
|
||||
|
||||
instance NFData HeaderKey
|
||||
|
||||
instance Cacheable HeaderKey
|
||||
deriving stock (Show, Eq, Ord, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.FromJSON HeaderKey where
|
||||
parseJSON = J.withText "HeaderKey" \txt -> case CI.mk txt of
|
||||
"Content-Type" -> fail "Restricted Header: Content-Type"
|
||||
key -> pure $ HeaderKey key
|
||||
|
||||
data TransformHeaders = TransformHeaders
|
||||
{ addHeaders :: [(CI.CI Text, StringTemplateText)],
|
||||
removeHeaders :: [CI.CI Text]
|
||||
}
|
||||
deriving (Show, Eq, Ord, Generic)
|
||||
|
||||
instance NFData TransformHeaders
|
||||
|
||||
instance Cacheable TransformHeaders
|
||||
deriving stock (Show, Eq, Ord, Generic)
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
instance J.ToJSON TransformHeaders where
|
||||
toJSON TransformHeaders {..} =
|
||||
@ -364,10 +363,7 @@ instance J.FromJSON TransformHeaders where
|
||||
newtype TransformErrorBundle = TransformErrorBundle {teMessages :: [J.Value]}
|
||||
deriving stock (Show, Eq, Generic)
|
||||
deriving newtype (Semigroup, Monoid, J.ToJSON)
|
||||
|
||||
instance NFData TransformErrorBundle
|
||||
|
||||
instance Cacheable TransformErrorBundle
|
||||
deriving anyclass (NFData, Cacheable)
|
||||
|
||||
-------------------------------
|
||||
--- Constructing Transforms ---
|
||||
@ -376,84 +372,42 @@ instance Cacheable TransformErrorBundle
|
||||
-- | Construct a `RequestTransform` from its metadata representation.
|
||||
mkRequestTransform :: MetadataRequestTransform -> RequestTransform
|
||||
mkRequestTransform MetadataRequestTransform {..} =
|
||||
let urlTransform = mkUrlTransform mtTemplatingEngine <$> mtRequestURL
|
||||
let methodTransform = mkMethodTransform mtTemplatingEngine <$> mtRequestMethod
|
||||
urlTransform = mkUrlTransform mtTemplatingEngine <$> mtRequestURL
|
||||
queryTransform = mkQueryParamsTransform mtTemplatingEngine <$> mtQueryParams
|
||||
headerTransform = mkHeaderTransform mtTemplatingEngine <$> mtRequestHeaders
|
||||
bodyTransform = mkReqTemplateTransform mtTemplatingEngine <$> mtBodyTransform
|
||||
in RequestTransform mtRequestMethod urlTransform bodyTransform mtContentType queryTransform headerTransform
|
||||
bodyTransform = fmap (mkReqTemplateTransform mtTemplatingEngine) <$> mtBodyTransform
|
||||
in RequestTransform methodTransform urlTransform bodyTransform queryTransform headerTransform
|
||||
|
||||
mkResponseTransform :: MetadataResponseTransform -> ResponseTransform
|
||||
mkResponseTransform MetadataResponseTransform {..} =
|
||||
let bodyTransform = mkRespTemplateTransform mrtTemplatingEngine <$> mrtBodyTransform
|
||||
let bodyTransform = fmap (mkRespTemplateTransform mrtTemplatingEngine) <$> mrtBodyTransform
|
||||
in ResponseTransform bodyTransform
|
||||
|
||||
-- | Transform a String Template
|
||||
transformST :: TemplatingEngine -> ReqTransformCtx -> StringTemplateText -> Either TransformErrorBundle B.ByteString
|
||||
transformST engine transformCtx t =
|
||||
valueToString =<< mkReqTemplateTransform engine (coerce wrapTemplate t) transformCtx
|
||||
|
||||
mkMethodTransform :: TemplatingEngine -> StringTemplateText -> ReqTransformCtx -> Either TransformErrorBundle RequestMethod
|
||||
mkMethodTransform engine template transformCtx =
|
||||
fmap (coerce . CI.mk . TE.decodeUtf8) $ transformST engine transformCtx template
|
||||
|
||||
mkQueryParamsTransform :: TemplatingEngine -> [(StringTemplateText, Maybe StringTemplateText)] -> ReqTransformCtx -> Either TransformErrorBundle HTTP.Query
|
||||
mkQueryParamsTransform engine templates transformCtx =
|
||||
let transform t =
|
||||
case mkReqTemplateTransform engine (coerce wrapTemplate t) transformCtx of
|
||||
Left err -> Left err
|
||||
Right (J.String str) -> Right $ str
|
||||
Right (J.Number num) -> Right $ tshow num
|
||||
Right (J.Bool True) -> Right $ "true"
|
||||
Right (J.Bool False) -> Right $ "false"
|
||||
Right val ->
|
||||
Left $
|
||||
TransformErrorBundle $
|
||||
pure $
|
||||
J.object
|
||||
[ "error_code" J..= J.String "TransformationError",
|
||||
"message" J..= J.String "Query Param Transforms must produce a String, Number, or Boolean value",
|
||||
"value" J..= val
|
||||
]
|
||||
toValidation :: [(Either TransformErrorBundle T.Text, Maybe (Either TransformErrorBundle T.Text))] -> [(Validation TransformErrorBundle T.Text, Maybe (Validation TransformErrorBundle T.Text))]
|
||||
toValidation = fmap (bimap eitherToValidation (fmap eitherToValidation))
|
||||
let transform = eitherToValidation . transformST engine transformCtx
|
||||
|
||||
collectErrors :: [(Either TransformErrorBundle T.Text, Maybe (Either TransformErrorBundle T.Text))] -> Validation TransformErrorBundle [(T.Text, Maybe T.Text)]
|
||||
collectErrors xs = traverse (bitraverse id sequenceA) (toValidation xs)
|
||||
|
||||
results = fmap (bimap transform (fmap transform)) templates
|
||||
collectedResults = validationToEither $ collectErrors results
|
||||
in (fmap . fmap) (bimap TE.encodeUtf8 (fmap TE.encodeUtf8)) collectedResults
|
||||
transformationResults :: [(Validation TransformErrorBundle B.ByteString, Maybe (Validation TransformErrorBundle B.ByteString))]
|
||||
transformationResults = fmap (bimap transform (fmap transform)) templates
|
||||
in validationToEither $ traverse (bitraverse id sequenceA) transformationResults
|
||||
|
||||
-- | Given a `TransformHeaders` and the `ReqTransformCtx`, Construct a
|
||||
-- function to transform the existing headers.
|
||||
mkHeaderTransform :: TemplatingEngine -> TransformHeaders -> ReqTransformCtx -> Either TransformErrorBundle ([HTTP.Header] -> [HTTP.Header])
|
||||
mkHeaderTransform engine TransformHeaders {..} transformCtx =
|
||||
let transform t =
|
||||
case mkReqTemplateTransform engine (coerce $ wrapTemplate t) transformCtx of
|
||||
Left err -> Failure err
|
||||
Right (J.String str) -> Success $ TE.encodeUtf8 str
|
||||
Right (J.Number num) -> Success $ TE.encodeUtf8 $ tshow num
|
||||
Right (J.Bool True) -> Success "true"
|
||||
Right (J.Bool False) -> Success "false"
|
||||
Right val ->
|
||||
Failure $
|
||||
TransformErrorBundle $
|
||||
pure $
|
||||
J.object
|
||||
[ "error_code" J..= J.String "TransformationError",
|
||||
"message" J..= J.String ("Header Transforms must produce a String, Number, or Boolean value: " <> tshow val)
|
||||
]
|
||||
|
||||
failIfCT key =
|
||||
if CI.foldedCase key == "content-type"
|
||||
then
|
||||
Failure $
|
||||
TransformErrorBundle $
|
||||
pure $
|
||||
J.object
|
||||
[ "error_code" J..= J.String "TransformationError",
|
||||
"message" J..= J.String ("Header Transforms cannot add Content-Type" <> CI.original key)
|
||||
]
|
||||
else Success $ CI.map TE.encodeUtf8 key
|
||||
|
||||
toBeAdded = traverse (bitraverse failIfCT transform) addHeaders
|
||||
toBeRemoved = (fmap . CI.map) TE.encodeUtf8 removeHeaders
|
||||
|
||||
filterHeaders h = filter ((`notElem` toBeRemoved) . fst) h
|
||||
in case toBeAdded of
|
||||
Failure err -> throwError err
|
||||
Success toBeAdded' -> pure (filterHeaders . (toBeAdded' <>))
|
||||
mkHeaderTransform engine TransformHeaders {..} ctx = do
|
||||
add <- V.toEither $ fmap mappend $ traverse (bitraverse (pure . CI.map TE.encodeUtf8) (V.fromEither . transformST engine ctx)) addHeaders
|
||||
let filter' xs = filter (flip elem (fmap (CI.map TE.encodeUtf8) removeHeaders) . fst) xs
|
||||
pure $ add . filter'
|
||||
|
||||
mkUrlTransform :: TemplatingEngine -> StringTemplateText -> ReqTransformCtx -> Either TransformErrorBundle Text
|
||||
mkUrlTransform engine template transformCtx =
|
||||
@ -532,13 +486,18 @@ buildRespTransformCtx reqCtx respBody =
|
||||
applyRequestTransform :: (HTTP.Request -> ReqTransformCtx) -> RequestTransform -> HTTP.Request -> Either TransformErrorBundle HTTP.Request
|
||||
applyRequestTransform transformCtx' RequestTransform {..} reqData =
|
||||
let transformCtx = transformCtx' reqData
|
||||
method = fmap (TE.encodeUtf8 . renderRequestMethod) reqTransformRequestMethod
|
||||
methodFunc :: B.ByteString -> Either TransformErrorBundle B.ByteString
|
||||
methodFunc method =
|
||||
case reqTransformRequestMethod of
|
||||
Nothing -> pure method
|
||||
Just f -> TE.encodeUtf8 . CI.original . coerce <$> f transformCtx
|
||||
|
||||
bodyFunc :: Maybe BL.ByteString -> Either TransformErrorBundle (Maybe BL.ByteString)
|
||||
bodyFunc body =
|
||||
case reqTransformBody of
|
||||
Nothing -> pure body
|
||||
Just f -> pure . J.encode <$> f transformCtx
|
||||
Just Remove -> pure Nothing
|
||||
Just (Transform f) -> pure . J.encode <$> f transformCtx
|
||||
|
||||
urlFunc :: Text -> Either TransformErrorBundle Text
|
||||
urlFunc url =
|
||||
@ -556,18 +515,12 @@ applyRequestTransform transformCtx' RequestTransform {..} reqData =
|
||||
headerFunc headers =
|
||||
case reqTransformRequestHeaders of
|
||||
Nothing -> pure headers
|
||||
Just f -> f transformCtx >>= \g -> pure $ g headers
|
||||
|
||||
contentTypeFunc :: [HTTP.Header] -> Either TransformErrorBundle [HTTP.Header]
|
||||
contentTypeFunc = pure . nubBy (\a b -> fst a == fst b) . (:) ("Content-Type", contentType)
|
||||
where
|
||||
contentType = maybe "application/json" (TE.encodeUtf8 . renderContentType) reqTransformContentType
|
||||
Just f -> f transformCtx <*> pure headers
|
||||
in reqData & traverseOf HTTP.url urlFunc
|
||||
>>= traverseOf HTTP.body bodyFunc
|
||||
>>= traverseOf HTTP.queryParams queryFunc
|
||||
>>= traverseOf HTTP.method (pure . (`fromMaybe` method))
|
||||
>>= traverseOf HTTP.method methodFunc
|
||||
>>= traverseOf HTTP.headers headerFunc
|
||||
>>= traverseOf HTTP.headers contentTypeFunc
|
||||
|
||||
-- | At the moment we only transform the body of
|
||||
-- Responses. 'http-client' does not export the constructors for
|
||||
@ -579,5 +532,39 @@ applyResponseTransform ResponseTransform {..} ctx@RespTransformCtx {..} =
|
||||
bodyFunc body =
|
||||
case respTransformBody of
|
||||
Nothing -> pure body
|
||||
Just f -> J.encode <$> f ctx
|
||||
Just Remove -> pure mempty
|
||||
Just (Transform f) -> J.encode <$> f ctx
|
||||
in bodyFunc (J.encode rtcBody)
|
||||
|
||||
-----------------
|
||||
--- Utilities ---
|
||||
-----------------
|
||||
|
||||
valueToString :: MonadError TransformErrorBundle m => J.Value -> m B.ByteString
|
||||
valueToString = \case
|
||||
J.String str -> pure $ TE.encodeUtf8 str
|
||||
J.Number num -> pure $ TE.encodeUtf8 (tshow num)
|
||||
J.Bool True -> pure "true"
|
||||
J.Bool False -> pure "false"
|
||||
val -> throwErrorBundle "Template must produce a String, Number, or Boolean value" (Just val)
|
||||
|
||||
throwErrorBundle :: MonadError TransformErrorBundle m => T.Text -> Maybe J.Value -> m a
|
||||
throwErrorBundle msg val =
|
||||
throwError $
|
||||
TransformErrorBundle $
|
||||
pure $
|
||||
J.object $
|
||||
[ "error_code" J..= J.String "TransformationError",
|
||||
"message" J..= J.String msg
|
||||
]
|
||||
<> catMaybes [("value" J..=) <$> val]
|
||||
|
||||
infixr 8 .=?
|
||||
|
||||
(.=?) :: J.ToJSON v => k -> Maybe v -> Maybe (k, J.Value)
|
||||
(.=?) _ Nothing = Nothing
|
||||
(.=?) k (Just v) = Just (k, J.toJSON v)
|
||||
|
||||
-- | Wrap a template with escaped double quotes.
|
||||
wrapTemplate :: StringTemplateText -> StringTemplateText
|
||||
wrapTemplate (StringTemplateText t) = StringTemplateText $ "\"" <> t <> "\""
|
||||
|
@ -13,8 +13,8 @@ import Test.Hspec.Hedgehog
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
it "RequstMethod RoundTrip" $
|
||||
hedgehog $ forAll genRequestMethod >>= trippingJSON
|
||||
it "StringTemplateText RoundTrip" $
|
||||
hedgehog $ forAll genStringTemplateText >>= trippingJSON
|
||||
|
||||
it "TemplateEngine RoundTrip" $
|
||||
hedgehog $ forAll genTemplatingEngine >>= trippingJSON
|
||||
@ -22,9 +22,6 @@ spec = do
|
||||
it "TemplateText RoundTrip" $
|
||||
hedgehog $ forAll genTemplateText >>= trippingJSON
|
||||
|
||||
it "ContentType RoundTrip" $
|
||||
hedgehog $ forAll genContentType >>= trippingJSON
|
||||
|
||||
it "TransformHeaders" $
|
||||
hedgehog $ do
|
||||
headers <- forAll genTransformHeaders
|
||||
@ -36,16 +33,13 @@ spec = do
|
||||
hedgehog $ do
|
||||
transform <- forAll genMetadataRequestTransform
|
||||
let sortH TransformHeaders {..} = TransformHeaders (sort addHeaders) (sort removeHeaders)
|
||||
sortMT mt@MetadataRequestTransform {mtRequestHeaders, mtQueryParams} = mt {mtRequestHeaders = sortH <$> mtRequestHeaders, mtQueryParams = sort <$> mtQueryParams}
|
||||
let sortMT mt@MetadataRequestTransform {mtRequestHeaders, mtQueryParams} = mt {mtRequestHeaders = sortH <$> mtRequestHeaders, mtQueryParams = sort <$> mtQueryParams}
|
||||
transformMaybe = eitherDecode $ encode transform
|
||||
Right (sortMT transform) === fmap sortMT transformMaybe
|
||||
|
||||
trippingJSON :: (Show a, Eq a, ToJSON a, FromJSON a, MonadTest m) => a -> m ()
|
||||
trippingJSON val = tripping val (toJSON) (fromJSON)
|
||||
|
||||
genRequestMethod :: Gen RequestMethod
|
||||
genRequestMethod = Gen.enumBounded @_ @RequestMethod
|
||||
|
||||
genTemplatingEngine :: Gen TemplatingEngine
|
||||
genTemplatingEngine = Gen.enumBounded @_ @TemplatingEngine
|
||||
|
||||
@ -59,9 +53,6 @@ genTemplateText = TemplateText . wrap <$> Gen.text (Range.constant 3 20) Gen.alp
|
||||
genStringTemplateText :: Gen StringTemplateText
|
||||
genStringTemplateText = StringTemplateText <$> Gen.text (Range.constant 3 20) Gen.alphaNum
|
||||
|
||||
genContentType :: Gen ContentType
|
||||
genContentType = Gen.enumBounded @_ @ContentType
|
||||
|
||||
genTransformHeaders :: Gen TransformHeaders
|
||||
genTransformHeaders = do
|
||||
numHeaders <- Gen.integral $ Range.constant 1 20
|
||||
@ -93,19 +84,18 @@ genUrl = do
|
||||
|
||||
genMetadataRequestTransform :: Gen MetadataRequestTransform
|
||||
genMetadataRequestTransform = do
|
||||
method <- Gen.maybe genRequestMethod
|
||||
method <- Gen.maybe genStringTemplateText
|
||||
-- NOTE: At the moment no need to generate valid urls or templates
|
||||
-- but such instances maybe useful in the future.
|
||||
url <- Gen.maybe $ genUrl
|
||||
bodyTransform <- Gen.maybe $ genTemplateText
|
||||
contentType <- Gen.maybe $ genContentType
|
||||
bodyTransform <- Gen.maybe $ fmap Transform $ genTemplateText
|
||||
queryParams <- Gen.maybe $ genQueryParams
|
||||
reqHeaders <- Gen.maybe $ genTransformHeaders
|
||||
MetadataRequestTransform
|
||||
V2
|
||||
method
|
||||
url
|
||||
bodyTransform
|
||||
contentType
|
||||
queryParams
|
||||
reqHeaders
|
||||
<$> genTemplatingEngine
|
||||
|
@ -72,14 +72,17 @@ args:
|
||||
output_type: UserId
|
||||
handler: http://127.0.0.1:5593/create-user
|
||||
request_transform:
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: |
|
||||
{
|
||||
"input": {
|
||||
"email": "foo@bar.com",
|
||||
"name": "notClarke"
|
||||
body:
|
||||
action: transform
|
||||
template: |
|
||||
{
|
||||
"input": {
|
||||
"email": "foo@bar.com",
|
||||
"name": "notClarke"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- type: create_action
|
||||
args:
|
||||
|
@ -252,16 +252,19 @@ args:
|
||||
output_type: OutObject
|
||||
handler: http://127.0.0.1:5593/mirror-action
|
||||
request_transform:
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: |
|
||||
{
|
||||
"input": {
|
||||
"arg": {
|
||||
"id": {{ $body.input.arg.name }},
|
||||
"name": {{ $body.input.arg.id }}
|
||||
body:
|
||||
action: transform
|
||||
template: |
|
||||
{
|
||||
"input": {
|
||||
"arg": {
|
||||
"id": {{ $body.input.arg.name }},
|
||||
"name": {{ $body.input.arg.id }}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- type: create_action
|
||||
args:
|
||||
@ -285,12 +288,15 @@ args:
|
||||
output_type: OutObjectTransformed
|
||||
handler: http://127.0.0.1:5593/mirror-action
|
||||
response_transform:
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: |
|
||||
{
|
||||
"foo": {{ $body.id }},
|
||||
"bar": {{ $body.name }}
|
||||
}
|
||||
body:
|
||||
action: transform
|
||||
template: |
|
||||
{
|
||||
"foo": {{ $body.id }},
|
||||
"bar": {{ $body.name }}
|
||||
}
|
||||
|
||||
- type: create_action
|
||||
args:
|
||||
@ -333,24 +339,27 @@ args:
|
||||
output_type: NestedOutObjectTransformed!
|
||||
handler: http://127.0.0.1:5593/get-user-by-email-nested
|
||||
response_transform:
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: |
|
||||
{
|
||||
"uid": {{ $body.user_id.id }},
|
||||
"city0": {{ $body.addresses[0].city }},
|
||||
"country0": {{ $body.addresses[0].country }},
|
||||
"other_addresses":
|
||||
{{ range i, x := $body.addresses }}
|
||||
{{ if i > 0 }}
|
||||
{
|
||||
"city": {{ x.city }},
|
||||
"country": {{ x.country }}
|
||||
}
|
||||
{{ else }}
|
||||
null
|
||||
body:
|
||||
action: transform
|
||||
template: |
|
||||
{
|
||||
"uid": {{ $body.user_id.id }},
|
||||
"city0": {{ $body.addresses[0].city }},
|
||||
"country0": {{ $body.addresses[0].country }},
|
||||
"other_addresses":
|
||||
{{ range i, x := $body.addresses }}
|
||||
{{ if i > 0 }}
|
||||
{
|
||||
"city": {{ x.city }},
|
||||
"country": {{ x.country }}
|
||||
}
|
||||
{{ else }}
|
||||
null
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
}
|
||||
}
|
||||
|
||||
- type: create_action
|
||||
args:
|
||||
@ -445,10 +454,13 @@ args:
|
||||
output_type: '[Result]'
|
||||
handler: http://127.0.0.1:5593/get-results
|
||||
response_transform:
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: |
|
||||
{{ range i, x := $body.result_ids }}
|
||||
{
|
||||
"id": {{ x }}
|
||||
}
|
||||
{{ end }}
|
||||
body:
|
||||
action: transform
|
||||
template: |
|
||||
{{ range i, x := $body.result_ids }}
|
||||
{
|
||||
"id": {{ x }}
|
||||
}
|
||||
{{ end }}
|
||||
|
@ -1,26 +0,0 @@
|
||||
- description: PG create event trigger
|
||||
url: /v1/metadata
|
||||
status: 400
|
||||
response:
|
||||
path: $.args.request_transform.content_type
|
||||
error: "Error when parsing command create_event_trigger.\nSee our documentation at https://hasura.io/docs/latest/graphql/core/api-reference/metadata-api/index.html#metadata-apis.\nInternal error message: Invalid ContentType"
|
||||
code: parse-failed
|
||||
query:
|
||||
type: pg_create_event_trigger
|
||||
args:
|
||||
name: sample_trigger
|
||||
table:
|
||||
name: test_t1
|
||||
schema: hge_tests
|
||||
source: default
|
||||
webhook: http://127.0.0.1:5592
|
||||
insert:
|
||||
columns: '*'
|
||||
payload:
|
||||
- id
|
||||
- first_name
|
||||
- last_name
|
||||
replace: false
|
||||
request_transform:
|
||||
template_engine: Kriti
|
||||
content_type: multipart/form-data
|
@ -20,13 +20,13 @@
|
||||
- last_name
|
||||
replace: false
|
||||
request_transform:
|
||||
content_type: application/json
|
||||
version: 2
|
||||
template_engine: Kriti
|
||||
body: "{{ $body.event.data.new }}"
|
||||
body:
|
||||
action: transform
|
||||
template: "{{ $body.event.data.new }}"
|
||||
query_params:
|
||||
"foo": "bar"
|
||||
request_headers:
|
||||
add_headers:
|
||||
foo: "bar"
|
||||
remove_headers:
|
||||
- User-Agent
|
||||
|
@ -1,23 +0,0 @@
|
||||
- description: PG create event trigger
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_create_event_trigger
|
||||
args:
|
||||
name: sample_trigger
|
||||
table:
|
||||
name: test_t1
|
||||
schema: hge_tests
|
||||
source: default
|
||||
webhook: http://127.0.0.1:5592
|
||||
insert:
|
||||
columns: '*'
|
||||
payload:
|
||||
- id
|
||||
- first_name
|
||||
- last_name
|
||||
replace: false
|
||||
request_transform:
|
||||
content_type: application/json
|
@ -1,24 +0,0 @@
|
||||
- description: PG create event trigger
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_create_event_trigger
|
||||
args:
|
||||
name: sample_trigger
|
||||
table:
|
||||
name: test_t1
|
||||
schema: hge_tests
|
||||
source: default
|
||||
webhook: http://127.0.0.1:5592
|
||||
insert:
|
||||
columns: '*'
|
||||
payload:
|
||||
- id
|
||||
- first_name
|
||||
- last_name
|
||||
replace: false
|
||||
request_transform:
|
||||
template_engine: Kriti
|
||||
content_type: application/x-www-form-urlencoded
|
@ -22,5 +22,8 @@
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
body: "{{ $body.world }}"
|
||||
version: 2
|
||||
body:
|
||||
action: transform
|
||||
template: "{{ $body.world }}"
|
||||
template_engine: Kriti
|
||||
|
@ -4,7 +4,7 @@
|
||||
X-Hasura-Role: admin
|
||||
status: 400
|
||||
response:
|
||||
path: "$.args.request_transform.body"
|
||||
path: "$.args.request_transform.body.template"
|
||||
error: "Unexpected token '$body'."
|
||||
code: "parse-failed"
|
||||
query:
|
||||
@ -14,5 +14,8 @@
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
body: "$body.hello }}"
|
||||
version: 2
|
||||
body:
|
||||
action: transform
|
||||
template: "$body.hello }}"
|
||||
template_engine: Kriti
|
||||
|
@ -22,12 +22,16 @@
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
version: 2
|
||||
url: "http://www.google.com"
|
||||
template_engine: Kriti
|
||||
body: "\"hello_{{ $body.hello }}\""
|
||||
body:
|
||||
action: transform
|
||||
template: "\"hello_{{ $body.hello }}\""
|
||||
method: POST
|
||||
query_params:
|
||||
"foo": "bar"
|
||||
request_headers:
|
||||
add_headers:
|
||||
foo: "bar"
|
||||
content-type: "application/json"
|
||||
|
@ -19,12 +19,16 @@
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
version: 2
|
||||
url: "http://www.google.com"
|
||||
template_engine: Kriti
|
||||
body: "\"hello_{{ $body.hello }}\""
|
||||
body:
|
||||
action: transform
|
||||
template: "\"hello_{{ $body.hello }}\""
|
||||
method: POST
|
||||
query_params:
|
||||
"foo": "bar"
|
||||
request_headers:
|
||||
add_headers:
|
||||
foo: "bar"
|
||||
content-type: "application/json"
|
||||
|
@ -0,0 +1,31 @@
|
||||
- description: Test Webhook Transform Old Body Schema
|
||||
url: /v1/metadata
|
||||
headers:
|
||||
X-Hasura-Role: admin
|
||||
status: 200
|
||||
response:
|
||||
body: hello_world
|
||||
headers:
|
||||
- - content-type
|
||||
- application/json
|
||||
- - foo
|
||||
- bar
|
||||
method: POST
|
||||
webhook_url: http://www.google.com?foo=bar
|
||||
query:
|
||||
type: test_webhook_transform
|
||||
args:
|
||||
webhook_url: http://localhost:1234
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
url: "http://www.google.com"
|
||||
template_engine: Kriti
|
||||
body: "\"hello_{{ $body.hello }}\""
|
||||
method: POST
|
||||
query_params:
|
||||
"foo": "bar"
|
||||
request_headers:
|
||||
add_headers:
|
||||
foo: "bar"
|
||||
content-type: "application/json"
|
@ -0,0 +1,30 @@
|
||||
- description: Test Webhook Transform
|
||||
url: /v1/metadata
|
||||
headers:
|
||||
X-Hasura-Role: admin
|
||||
status: 200
|
||||
response:
|
||||
headers:
|
||||
- - foo
|
||||
- bar
|
||||
body:
|
||||
method: GET
|
||||
webhook_url: http://www.google.com?foo=bar
|
||||
query:
|
||||
type: test_webhook_transform
|
||||
args:
|
||||
webhook_url: http://localhost:1234
|
||||
body:
|
||||
hello: world
|
||||
request_transform:
|
||||
version: 2
|
||||
url: "http://www.google.com"
|
||||
template_engine: Kriti
|
||||
body:
|
||||
action: remove
|
||||
method: GET
|
||||
query_params:
|
||||
"foo": "bar"
|
||||
request_headers:
|
||||
add_headers:
|
||||
foo: "bar"
|
@ -870,57 +870,3 @@ class TestEventTransform(object):
|
||||
removedHeaders=["user-agent"],
|
||||
webhook_path=expectedPath)
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_content_type_allows_json(self, hge_ctx, evts_webhook):
|
||||
# GIVEN
|
||||
check_query_f(hge_ctx, self.dir() + '/content_type_transform.yaml')
|
||||
|
||||
# WHEN
|
||||
table = {"schema": "hge_tests", "name": "test_t1"}
|
||||
insert_row = {"id": 0, "first_name": "Simon", "last_name": "Marlow"}
|
||||
st_code, resp = insert(hge_ctx, table, insert_row)
|
||||
|
||||
# THEN
|
||||
expected_event_data = {
|
||||
"old": None,
|
||||
"new": insert_row
|
||||
}
|
||||
|
||||
check_event(hge_ctx,
|
||||
evts_webhook,
|
||||
"sample_trigger",
|
||||
table,
|
||||
"INSERT",
|
||||
expected_event_data,
|
||||
{"Content-Type": "application/json"})
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_content_type_allows_urlencoded(self, hge_ctx, evts_webhook):
|
||||
# GIVEN
|
||||
check_query_f(hge_ctx, self.dir() + '/url_encoded_transform.yaml')
|
||||
|
||||
# WHEN
|
||||
table = {"schema": "hge_tests", "name": "test_t1"}
|
||||
insert_row = {"id": 0, "first_name": "Simon", "last_name": "Marlow"}
|
||||
st_code, resp = insert(hge_ctx, table, insert_row)
|
||||
|
||||
# THEN
|
||||
expected_event_data = {
|
||||
"old": None,
|
||||
"new": insert_row
|
||||
}
|
||||
|
||||
check_event(hge_ctx,
|
||||
evts_webhook,
|
||||
"sample_trigger",
|
||||
table,
|
||||
"INSERT",
|
||||
expected_event_data,
|
||||
{"Content-Type": "application/x-www-form-urlencoded"})
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_content_type_disallows_bad_content_types(self, hge_ctx, evts_webhook):
|
||||
check_query_f(hge_ctx, self.dir() + '/bad_content_type_transform.yaml')
|
||||
|
||||
def test_transform_headers_disallows_bad_content_types(self, hge_ctx, evts_webhook):
|
||||
check_query_f(hge_ctx, self.dir() + '/bad_header_transform.yaml')
|
||||
|
@ -223,6 +223,12 @@ class TestMetadata:
|
||||
def test_webhook_transform_success(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/test_webhook_transform_success.yaml')
|
||||
|
||||
def test_webhook_transform_success_remove_body(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/test_webhook_transform_success_remove_body.yaml')
|
||||
|
||||
def test_webhook_transform_success_old_body_schema(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/test_webhook_transform_success_old_body_schema.yaml')
|
||||
|
||||
def test_webhook_transform_with_url_env_reference_success(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/test_webhook_transform_env_reference_success.yaml')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user