Feature/improved webhook debug endpoint errors

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3782
Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com>
GitOrigin-RevId: 404197e766efa94a1814e8a0287cd55d9175f2a7
This commit is contained in:
Solomon 2022-03-10 15:22:54 -08:00 committed by hasura-bot
parent 2e04bd5ac2
commit ca85acbfe3
24 changed files with 464 additions and 196 deletions

View File

@ -121,6 +121,7 @@ function:
- server: add metadata inconsistency information in `reload_metadata` API call
- server: add custom function for case insensitive lookup in session variable in request transformation
- server: Improved error messaging for `test_webhook_transform` metadata API endpoint
- server: Webhook Tranforms can now produce `x-www-url-formencoded` bodies.
- server: Webhook Transforms can now delete request/response bodies explicitly.
- server: Fix truncation of session variables with variable length column types in MSSQL (#8158)

View File

@ -156,8 +156,7 @@ const getErrorFromCode = (data: Record<string, any>) => {
return `${errorCode}: ${errorMsg}`;
};
const getErrorFromBody = (data: Record<string, any>) => {
const errorObj = data.body[0];
const getErrorFromBody = (errorObj: Record<string, any>) => {
const errorCode = errorObj?.error_code;
const errorMsg = errorObj?.message;
const stPos = errorObj?.source_position?.start_line
@ -170,16 +169,16 @@ const getErrorFromBody = (data: Record<string, any>) => {
};
export const parseValidateApiData = (
requestData: Record<string, any>,
requestData: Record<string, any> | Record<string, any>[],
setError: (error: string) => void,
setUrl?: (data: string) => void,
setBody?: (data: string) => void
) => {
if (requestData?.code) {
const errorMessage = getErrorFromCode(requestData);
if (Array.isArray(requestData)) {
const errorMessage = getErrorFromBody(requestData[0]);
setError(errorMessage);
} else if (requestData?.body?.[0]?.error_code) {
const errorMessage = getErrorFromBody(requestData);
} else if (requestData?.code) {
const errorMessage = getErrorFromCode(requestData);
setError(errorMessage);
} else if (requestData?.webhook_url || requestData?.body) {
setError('');

View File

@ -617,6 +617,7 @@ library
, Hasura.RQL.DDL.Webhook.Transform.Headers
, Hasura.RQL.DDL.Webhook.Transform.Method
, Hasura.RQL.DDL.Webhook.Transform.QueryParams
, Hasura.RQL.DDL.Webhook.Transform.Validation
, Hasura.RQL.DDL.Webhook.Transform.Url
, Hasura.RQL.DDL.Schema
, Hasura.RQL.DDL.Schema.Cache

View File

@ -12,10 +12,16 @@ module Hasura.RQL.DDL.Action
DropActionPermission,
runDropActionPermission,
dropActionPermissionInMetadata,
caName,
caDefinition,
caComment,
uaName,
uaDefinition,
uaComment,
)
where
import Control.Lens ((.~), (^.))
import Control.Lens (makeLenses, (.~), (^.))
import Data.Aeson qualified as J
import Data.Aeson.TH qualified as J
import Data.Dependent.Map qualified as DMap
@ -49,6 +55,8 @@ data CreateAction = CreateAction
_caComment :: !(Maybe Text)
}
$(makeLenses ''CreateAction)
$(J.deriveJSON hasuraJSON ''CreateAction)
runCreateAction ::
@ -197,6 +205,7 @@ data UpdateAction = UpdateAction
_uaComment :: !(Maybe Text)
}
$(makeLenses ''UpdateAction)
$(J.deriveFromJSON hasuraJSON ''UpdateAction)
runUpdateAction ::

View File

@ -12,10 +12,24 @@ module Hasura.RQL.DDL.EventTrigger
getHeaderInfosFromConf,
getWebhookInfoFromConf,
buildEventTriggerInfo,
cetqSource,
cetqName,
cetqTable,
cetqInsert,
cetqUpdate,
cetqDelete,
cetqEnableManual,
cetqRetryConf,
cetqWebhook,
cetqWebhookFromEnv,
cetqHeaders,
cetqReplace,
cetqRequestTransform,
cetqResponseTrasnform,
)
where
import Control.Lens ((.~))
import Control.Lens (makeLenses, (.~))
import Data.Aeson
import Data.ByteString.Lazy qualified as LBS
import Data.Environment qualified as Env
@ -52,6 +66,8 @@ data CreateEventTriggerQuery (b :: BackendType) = CreateEventTriggerQuery
_cetqResponseTrasnform :: !(Maybe MetadataResponseTransform)
}
$(makeLenses ''CreateEventTriggerQuery)
instance Backend b => FromJSON (CreateEventTriggerQuery b) where
parseJSON = withObject "CreateEventTriggerQuery" \o -> do
sourceName <- o .:? "source" .!= defaultSource

View File

@ -21,7 +21,7 @@ import Control.Lens ((.~), (^.), (^?))
import Data.Aeson qualified as J
import Data.Aeson.Ordered qualified as AO
import Data.Attoparsec.Text qualified as AT
import Data.Bifunctor (bimap, first)
import Data.Bifunctor (first)
import Data.Bitraversable
import Data.ByteString.Lazy qualified as BL
import Data.CaseInsensitive qualified as CI
@ -59,8 +59,6 @@ import Hasura.RQL.DDL.Webhook.Transform.Class (mkReqTransformCtx)
import Hasura.RQL.Types
import Hasura.RQL.Types.Eventing.Backend (BackendEventTrigger (..))
import Hasura.SQL.AnyBackend qualified as AB
import Kriti qualified as K
import Kriti.Error qualified as K
import Network.HTTP.Client.Transformable qualified as HTTP
runClearMetadata ::
@ -523,8 +521,7 @@ runRemoveMetricsConfig = do
pure successMsg
data TestTransformError
= UrlInterpError K.SerializedError
| RequestInitializationError HTTP.HttpException
= RequestInitializationError HTTP.HttpException
| RequestTransformationError HTTP.Request TransformErrorBundle
runTestWebhookTransform ::
@ -541,28 +538,25 @@ runTestWebhookTransform (TestWebhookTransform env headers urlE payload rt _ sv)
headers' <- traverse (traverse (fmap TE.encodeUtf8 . interpolateFromEnv env . TE.decodeUtf8)) headers
result <- runExceptT $ do
let env' = bimap T.pack (J.String . T.pack) <$> Env.toList env
decodeKritiResult = TE.decodeUtf8 . BL.toStrict . J.encode
kritiUrlResult <- hoistEither $ first (UrlInterpError . K.serialize) $ decodeKritiResult <$> K.runKriti ("\"" <> url <> "\"") env'
let unwrappedUrl = T.drop 1 $ T.dropEnd 1 kritiUrlResult
initReq <- hoistEither $ first RequestInitializationError $ HTTP.mkRequestEither unwrappedUrl
initReq <- hoistEither $ first RequestInitializationError $ HTTP.mkRequestEither url
let req = initReq & HTTP.body .~ pure (J.encode payload) & HTTP.headers .~ headers'
reqTransform = requestFields rt
engine = templateEngine rt
reqTransformCtx = mkReqTransformCtx unwrappedUrl sv engine
reqTransformCtx = mkReqTransformCtx url sv engine
hoistEither $ first (RequestTransformationError req) $ applyRequestTransform reqTransformCtx reqTransform req
case result of
Right transformed ->
let body = decodeBody (transformed ^. HTTP.body)
in pure $ packTransformResult transformed body
Left (RequestTransformationError req err) -> pure $ packTransformResult req (J.toJSON err)
-- NOTE: In the following two cases we have failed before producing a valid request.
Left (UrlInterpError err) -> pure $ encJFromJValue $ J.toJSON err
Left (RequestInitializationError err) -> pure $ encJFromJValue $ J.String $ "Error: " <> tshow err
pure $ packTransformResult $ Right transformed
Left (RequestTransformationError _ err) -> pure $ packTransformResult (Left err)
-- NOTE: In the following case we have failed before producing a valid request.
Left (RequestInitializationError err) ->
let errorBundle =
TransformErrorBundle $
pure $
J.object ["error_code" J..= J.String "Request Initialization Error", "message" J..= J.String (tshow err)]
in pure $ encJFromJValue $ J.toJSON errorBundle
interpolateFromEnv :: MonadError QErr m => Env.Environment -> Text -> m Text
interpolateFromEnv env url =
@ -574,6 +568,8 @@ interpolateFromEnv env url =
err e = throwError $ err400 NotFound $ "Missing Env Var: " <> e
in either err (pure . fold) result
-- | Deserialize a JSON or X-WWW-URL-FORMENCODED body from an
-- 'HTTP.Request' as 'J.Value'.
decodeBody :: Maybe BL.ByteString -> J.Value
decodeBody Nothing = J.Null
decodeBody (Just bs) = fromMaybe J.Null $ jsonToValue bs <|> formUrlEncodedToValue bs
@ -598,12 +594,14 @@ parseEnvTemplate = AT.many1 $ pEnv <|> pLit <|> fmap Right "{"
indistinct :: Either a a -> a
indistinct = either id id
packTransformResult :: HTTP.Request -> J.Value -> EncJSON
packTransformResult req body =
encJFromJValue $
J.object
[ "webhook_url" J..= (req ^. HTTP.url),
"method" J..= (req ^. HTTP.method),
"headers" J..= (first CI.foldedCase <$> (req ^. HTTP.headers)),
"body" J..= body
]
packTransformResult :: Either TransformErrorBundle HTTP.Request -> EncJSON
packTransformResult = \case
Right req ->
encJFromJValue $
J.object
[ "webhook_url" J..= (req ^. HTTP.url),
"method" J..= (req ^. HTTP.method),
"headers" J..= (first CI.foldedCase <$> (req ^. HTTP.headers)),
"body" J..= decodeBody (req ^. HTTP.body)
]
Left err -> encJFromJValue $ J.toJSON err

View File

@ -15,9 +15,17 @@ module Hasura.RQL.DDL.Metadata.Types
AllowInconsistentMetadata (..),
WebHookUrl (..),
TestWebhookTransform (..),
twtEnv,
twtHeaders,
twtWebhookUrl,
twtPayload,
twtTransformer,
twtResponseTransformer,
twtSessionVariables,
)
where
import Control.Lens (makeLenses)
import Data.Aeson
import Data.Aeson.TH
import Data.CaseInsensitive qualified as CI
@ -214,6 +222,8 @@ data TestWebhookTransform = TestWebhookTransform
}
deriving (Eq)
$(makeLenses ''TestWebhookTransform)
instance FromJSON TestWebhookTransform where
parseJSON = withObject "TestWebhookTransform" $ \o -> do
env <- fmap (fromMaybe mempty) $ o .:? "env"

View File

@ -53,17 +53,17 @@ runCreateCronTrigger ::
runCreateCronTrigger CreateCronTrigger {..} = do
let q =
CronTriggerMetadata
cctName
cctWebhook
cctCronSchedule
cctPayload
cctRetryConf
cctHeaders
cctIncludeInMetadata
cctComment
cctRequestTransform
cctResponseTransform
case cctReplace of
_cctName
_cctWebhook
_cctCronSchedule
_cctPayload
_cctRetryConf
_cctHeaders
_cctIncludeInMetadata
_cctComment
_cctRequestTransform
_cctResponseTransform
case _cctReplace of
True -> updateCronTrigger q
False -> do
cronTriggersMap <- scCronTriggers <$> askSchemaCache
@ -75,23 +75,23 @@ runCreateCronTrigger CreateCronTrigger {..} = do
<> triggerNameToTxt (ctName q)
<> " already exists"
let metadataObj = MOCronTrigger cctName
let metadataObj = MOCronTrigger _cctName
metadata =
CronTriggerMetadata
cctName
cctWebhook
cctCronSchedule
cctPayload
cctRetryConf
cctHeaders
cctIncludeInMetadata
cctComment
cctRequestTransform
cctResponseTransform
_cctName
_cctWebhook
_cctCronSchedule
_cctPayload
_cctRetryConf
_cctHeaders
_cctIncludeInMetadata
_cctComment
_cctRequestTransform
_cctResponseTransform
buildSchemaCacheFor metadataObj $
MetadataModifier $
metaCronTriggers %~ OMap.insert cctName metadata
populateInitialCronTriggerEvents cctCronSchedule cctName
metaCronTriggers %~ OMap.insert _cctName metadata
populateInitialCronTriggerEvents _cctCronSchedule _cctName
return successMsg
resolveCronTrigger ::

View File

@ -272,7 +272,7 @@ infixr 8 .=?
newtype WithOptional fn result = WithOptional
{ getOptional :: Maybe (fn result)
}
deriving stock (Eq, Functor, Generic, Show)
deriving stock (Eq, Functor, Foldable, Generic, Show)
deriving newtype (FromJSON, ToJSON)
deriving newtype instance
@ -355,7 +355,7 @@ mkRespTemplateTransform engine (ModifyBody (Template template)) ResponseTransfor
mkRespTemplateTransform engine (FormUrlEncoded formTemplates) context =
case engine of
Kriti -> do
result <- liftEither . V.toEither $ traverse (validateUnescapedResponseTemplateTransform context) formTemplates
result <- liftEither . V.toEither $ traverse (runUnescapedResponseTemplateTransform' context) formTemplates
pure $ J.String $ TE.decodeUtf8 $ BL.toStrict $ foldFormEncoded result
mkResponseTransform :: MetadataResponseTransform -> ResponseTransform

View File

@ -22,17 +22,21 @@ import Data.HashMap.Internal.Strict qualified as M
import Data.List qualified as L
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Data.Validation (Validation)
import Data.Validation qualified as V
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
( RequestTransformCtx (..),
Template (..),
TemplatingEngine,
Transform (..),
TransformErrorBundle (..),
UnescapedTemplate,
mkRequestTemplateTransform,
validateUnescapedRequestTemplateTransform,
runRequestTemplateTransform,
runUnescapedRequestTemplateTransform',
validateRequestTemplateTransform',
validateRequestUnescapedTemplateTransform',
)
import Network.URI.Extended qualified as URI
@ -60,12 +64,21 @@ instance Transform Body where
case transformation of
RemoveBody -> pure $ JSONBody Nothing
ModifyBody template -> do
result <- liftEither $ mkRequestTemplateTransform template context
result <- liftEither $ runRequestTemplateTransform template context
pure . JSONBody . Just $ result
FormUrlEncoded formTemplates -> do
result <- liftEither . V.toEither $ traverse (validateUnescapedRequestTemplateTransform context) formTemplates
result <- liftEither . V.toEither $ traverse (runUnescapedRequestTemplateTransform' context) formTemplates
pure $ RawBody $ foldFormEncoded result
validate ::
TemplatingEngine ->
TransformFn Body ->
Validation TransformErrorBundle ()
validate engine = \case
BodyTransform (RemoveBody) -> pure ()
BodyTransform (ModifyBody template) -> validateRequestTemplateTransform' engine template
BodyTransform (FormUrlEncoded kv) -> traverse_ (validateRequestUnescapedTemplateTransform' engine) kv
-- | Helper function for URI escaping 'T.Text' values.
escapeURIText :: T.Text -> T.Text
escapeURIText =

View File

@ -2,7 +2,8 @@
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE UndecidableInstances #-}
-- TODO(SOLOMON): Module Description.
-- | The 'Transform' typeclass with various types and helper functions
-- for evaluating transformations.
module Hasura.RQL.DDL.Webhook.Transform.Class
( -- * Transformation Interface and Utilities
Transform (..),
@ -20,15 +21,19 @@ module Hasura.RQL.DDL.Webhook.Transform.Class
TemplatingEngine (..),
Template (..),
Version (..),
mkRequestTemplateTransform,
runRequestTemplateTransform,
validateRequestTemplateTransform,
validateRequestTemplateTransform',
-- * Unescaped
UnescapedTemplate (..),
wrapUnescapedTemplate,
mkUnescapedRequestTemplateTransform,
validateUnescapedRequestTemplateTransform,
mkUnescapedResponseTemplateTransform,
validateUnescapedResponseTemplateTransform,
runUnescapedRequestTemplateTransform,
runUnescapedRequestTemplateTransform',
runUnescapedResponseTemplateTransform,
runUnescapedResponseTemplateTransform',
validateRequestUnescapedTemplateTransform,
validateRequestUnescapedTemplateTransform',
)
where
@ -51,7 +56,7 @@ import Data.Validation (Validation, fromEither)
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.Session (SessionVariables, getSessionVariableValue, mkSessionVariable)
import Kriti (SerializedError (..), runKritiWith)
import Kriti (runKritiWith)
import Kriti.Error qualified as Kriti (CustomFunctionError (..), serialize)
import Kriti.Parser qualified as Kriti (parser)
import Network.HTTP.Client.Transformable qualified as HTTP
@ -82,6 +87,12 @@ class Transform a where
a ->
m a
-- | Validate a 'TransformFn' of @a@.
validate ::
TemplatingEngine ->
TransformFn a ->
Validation TransformErrorBundle ()
-------------------------------------------------------------------------------
-- | We use collect all transformation failures as a '[J.Value]'.
@ -193,7 +204,7 @@ instance ToJSON TemplatingEngine where
{ J.tagSingleConstructors = True
}
-- | Validated textual transformation template.
-- | Textual transformation template.
newtype Template = Template
{ unTemplate :: Text
}
@ -201,28 +212,23 @@ newtype Template = Template
deriving newtype (Hashable, FromJSONKey, ToJSONKey)
deriving anyclass (Cacheable, NFData)
-- XXX(jkachmar): We need roundtrip tests for these instances.
instance J.FromJSON Template where
parseJSON = J.withText "Template" \t -> do
case Kriti.parser (encodeUtf8 t) of
-- TODO(SOLOMON): Use the parsed ValueExt in 'RequestFields' so that we
-- don't have to parse at every request.
Right _ -> pure $ Template t
Left err ->
let SerializedError {_message} = Kriti.serialize err
in fail $ T.unpack _message
parseJSON = J.withText "Template" (pure . Template)
-- XXX(jkachmar): We need roundtrip tests for these instances.
instance J.ToJSON Template where
toJSON = J.String . coerce
-- | A helper function for executing transformations from a 'Template'
-- and a 'RequestTrasformCtx'.
mkRequestTemplateTransform ::
-- and a 'RequestTransformCtx'.
--
-- NOTE: This and all related funtions are hard-coded to Kriti at the
-- moment. When we add additional template engines this function will
-- need to take a 'TemplatingEngine' parameter.
runRequestTemplateTransform ::
Template ->
RequestTransformCtx ->
Either TransformErrorBundle J.Value
mkRequestTemplateTransform template RequestTransformCtx {rtcEngine = Kriti, ..} =
runRequestTemplateTransform template RequestTransformCtx {rtcEngine = Kriti, ..} =
let context =
[ ("$body", rtcBody),
("$session_variables", rtcSessionVariables)
@ -236,11 +242,34 @@ mkRequestTemplateTransform template RequestTransformCtx {rtcEngine = Kriti, ..}
let renderedErr = J.toJSON $ Kriti.serialize kritiErr
in TransformErrorBundle [renderedErr]
mkResponseTemplateTransform ::
-- TODO: Should this live in 'Hasura.RQL.DDL.Webhook.Transform.Validation'?
validateRequestTemplateTransform ::
TemplatingEngine ->
Template ->
Either TransformErrorBundle ()
validateRequestTemplateTransform Kriti (Template template) =
bimap packBundle (const ()) $ Kriti.parser $ TE.encodeUtf8 template
where
packBundle = TransformErrorBundle . pure . J.toJSON . Kriti.serialize
validateRequestTemplateTransform' ::
TemplatingEngine ->
Template ->
Validation TransformErrorBundle ()
validateRequestTemplateTransform' engine =
fromEither . validateRequestTemplateTransform engine
-- | A helper function for executing transformations from a 'Template'
-- and a 'ResponseTransformCtx'.
--
-- NOTE: This and all related funtions are hard-coded to Kriti at the
-- moment. When we add additional template engines this function will
-- need to take a 'TemplatingEngine' parameter.
runResponseTemplateTransform ::
Template ->
ResponseTransformCtx ->
Either TransformErrorBundle J.Value
mkResponseTemplateTransform template ResponseTransformCtx {responseTransformEngine = Kriti, ..} =
runResponseTemplateTransform template ResponseTransformCtx {responseTransformEngine = Kriti, ..} =
let context = [("$body", responseTransformBody), ("$request", responseTransformReqCtx)]
eResult = runKritiWith (unTemplate $ template) context responseTransformFunctions
in eResult & left \kritiErr ->
@ -283,26 +312,9 @@ newtype UnescapedTemplate = UnescapedTemplate
deriving newtype (Hashable, FromJSONKey, ToJSONKey)
deriving anyclass (Cacheable, NFData)
-- XXX(jkachmar): We need roundtrip tests for these instances.
instance J.FromJSON UnescapedTemplate where
parseJSON = J.withText "UnescapedTemplate" \t -> do
let wrapped = "\"" <> t <> "\""
-- TODO(SOLOMON): Use the parsed ValueExt in 'RequestFields' so that we
-- don't have to parse at every request.
case Kriti.parser (encodeUtf8 wrapped) of
-- NOTE: We can't simplfy use the wrapped value because the
-- 'FromJSONKey' instance uses coercion rather then this
-- instance to wrap the newtype. This means we would need to
-- handle QueryParam keys explicitly in 'mkQueryParamsTransform'
-- while values would be handled implicitliy via this aeson
-- instance. That is confusing, so we just handle everything
-- explicitly down the call stack.
Right _ -> pure $ UnescapedTemplate t
Left err ->
let SerializedError {_message} = Kriti.serialize err
in fail $ T.unpack _message
parseJSON = J.withText "Template" (pure . UnescapedTemplate)
-- XXX(jkachmar): We need roundtrip tests for these instances.
instance J.ToJSON UnescapedTemplate where
toJSON = J.String . coerce
@ -311,45 +323,64 @@ wrapUnescapedTemplate :: UnescapedTemplate -> Template
wrapUnescapedTemplate (UnescapedTemplate txt) = Template $ "\"" <> txt <> "\""
-- | A helper function for executing Kriti transformations from a
-- 'UnescapedTemplate' and a 'RequestTrasformCtx'. The differennce
-- from 'mkRequestTemplateTransform' is that this function will wrap
-- the template text in double quotes before running Kriti.
mkUnescapedRequestTemplateTransform ::
-- 'UnescapedTemplate' and a 'RequestTrasformCtx'.
--
-- The difference from 'runRequestTemplateTransform' is that this
-- function will wrap the template text in double quotes before
-- running Kriti.
runUnescapedRequestTemplateTransform ::
RequestTransformCtx ->
UnescapedTemplate ->
Either TransformErrorBundle ByteString
mkUnescapedRequestTemplateTransform context unescapedTemplate = do
runUnescapedRequestTemplateTransform context unescapedTemplate = do
result <-
mkRequestTemplateTransform
runRequestTemplateTransform
(wrapUnescapedTemplate unescapedTemplate)
context
encodeScalar result
-- | Run a Kriti transformation with an unescaped template in
-- 'Validation' instead of 'Either'.
validateUnescapedRequestTemplateTransform ::
runUnescapedRequestTemplateTransform' ::
RequestTransformCtx ->
UnescapedTemplate ->
Validation TransformErrorBundle ByteString
validateUnescapedRequestTemplateTransform context unescapedTemplate =
runUnescapedRequestTemplateTransform' context unescapedTemplate =
fromEither $
mkUnescapedRequestTemplateTransform context unescapedTemplate
runUnescapedRequestTemplateTransform context unescapedTemplate
mkUnescapedResponseTemplateTransform ::
-- TODO: Should this live in 'Hasura.RQL.DDL.Webhook.Transform.Validation'?
validateRequestUnescapedTemplateTransform ::
TemplatingEngine ->
UnescapedTemplate ->
Either TransformErrorBundle ()
validateRequestUnescapedTemplateTransform engine =
validateRequestTemplateTransform engine . wrapUnescapedTemplate
validateRequestUnescapedTemplateTransform' ::
TemplatingEngine ->
UnescapedTemplate ->
Validation TransformErrorBundle ()
validateRequestUnescapedTemplateTransform' engine =
fromEither . validateRequestUnescapedTemplateTransform engine
-- | Run an 'UnescapedTemplate' with a 'ResponseTransformCtx'.
runUnescapedResponseTemplateTransform ::
ResponseTransformCtx ->
UnescapedTemplate ->
Either TransformErrorBundle ByteString
mkUnescapedResponseTemplateTransform context unescapedTemplate = do
result <- mkResponseTemplateTransform (wrapUnescapedTemplate unescapedTemplate) context
runUnescapedResponseTemplateTransform context unescapedTemplate = do
result <- runResponseTemplateTransform (wrapUnescapedTemplate unescapedTemplate) context
encodeScalar result
validateUnescapedResponseTemplateTransform ::
-- | Run an 'UnescapedTemplate' with a 'ResponseTransformCtx' in 'Validation'.
runUnescapedResponseTemplateTransform' ::
ResponseTransformCtx ->
UnescapedTemplate ->
Validation TransformErrorBundle ByteString
validateUnescapedResponseTemplateTransform context unescapedTemplate =
runUnescapedResponseTemplateTransform' context unescapedTemplate =
fromEither $
mkUnescapedResponseTemplateTransform context unescapedTemplate
runUnescapedResponseTemplateTransform context unescapedTemplate
-------------------------------------------------------------------------------
-- Utility functions.

View File

@ -1,6 +1,7 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Hasura.RQL.DDL.Webhook.Transform.Headers
( -- * Header Transformations
@ -20,15 +21,18 @@ import Data.Aeson qualified as J
import Data.CaseInsensitive qualified as CI
import Data.HashMap.Strict qualified as M
import Data.Text.Encoding qualified as TE
import Data.Validation (Validation)
import Data.Validation qualified as V
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
( RequestTransformCtx (..),
TemplatingEngine,
Transform (..),
TransformErrorBundle (..),
UnescapedTemplate (..),
validateUnescapedRequestTemplateTransform,
runUnescapedRequestTemplateTransform',
validateRequestUnescapedTemplateTransform',
)
import Network.HTTP.Types qualified as HTTP.Types
@ -67,11 +71,18 @@ instance Transform Headers where
-- Validation's applicative sequencing.
newHeaders <- liftEither . V.toEither $ for rhf_addHeaders \(rawKey, rawValue) -> do
let key = CI.map TE.encodeUtf8 rawKey
value <- validateUnescapedRequestTemplateTransform context rawValue
value <- runUnescapedRequestTemplateTransform' context rawValue
pure (key, value)
pure . Headers $ filteredHeaders <> newHeaders
validate ::
TemplatingEngine ->
TransformFn Headers ->
Validation TransformErrorBundle ()
validate engine (HeadersTransform (ReplaceHeaders (fmap snd . rhf_addHeaders -> templates))) =
traverse_ (validateRequestUnescapedTemplateTransform' engine) templates
-- | The defunctionalized transformation on 'Headers'
newtype HeadersTransformAction
= ReplaceHeaders ReplaceHeaderFields

View File

@ -16,10 +16,12 @@ import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson qualified as J
import Data.CaseInsensitive qualified as CI
import Data.Text qualified as T
import Data.Validation
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
( RequestTransformCtx (..),
TemplatingEngine,
Transform (..),
TransformErrorBundle (..),
)
@ -48,6 +50,13 @@ instance Transform Method where
transform :: MonadError TransformErrorBundle m => TransformFn Method -> RequestTransformCtx -> Method -> m Method
transform (MethodTransform (ReplaceMethod method)) _ _ = pure method
-- NOTE: Do we want to validate the method verb?
validate ::
TemplatingEngine ->
TransformFn Method ->
Validation TransformErrorBundle ()
validate _ _ = pure ()
-- | The defunctionalized transformation on 'Method'.
--
-- In this case our transformation simply replaces the 'Method' with a

View File

@ -15,15 +15,18 @@ where
import Data.Aeson qualified as J
import Data.HashMap.Strict qualified as M
import Data.Validation (Validation)
import Data.Validation qualified as V
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
( RequestTransformCtx (..),
TemplatingEngine,
Transform (..),
TransformErrorBundle (..),
UnescapedTemplate (..),
validateUnescapedRequestTemplateTransform,
runUnescapedRequestTemplateTransform',
validateRequestUnescapedTemplateTransform',
)
import Network.HTTP.Client.Transformable qualified as HTTP
@ -47,11 +50,22 @@ instance Transform QueryParams where
-- NOTE: We use `ApplicativeDo` here to take advantage of
-- Validation's applicative sequencing.
queryParams <- liftEither . V.toEither $ for replacements \(rawKey, rawValue) -> do
key <- validateUnescapedRequestTemplateTransform context rawKey
value <- traverse (validateUnescapedRequestTemplateTransform context) rawValue
key <- runUnescapedRequestTemplateTransform' context rawKey
value <- traverse (runUnescapedRequestTemplateTransform' context) rawValue
pure (key, value)
pure $ QueryParams queryParams
validate ::
TemplatingEngine ->
TransformFn QueryParams ->
Validation TransformErrorBundle ()
validate engine (QueryParamsTransform (QueryParamsTransformAction params)) =
for_ params \(key, val) -> do
-- Note: We are using ApplicativeDo here:
validateRequestUnescapedTemplateTransform' engine key
traverse_ (validateRequestUnescapedTemplateTransform' engine) val
pure ()
-- | The defunctionalized transformation 'QueryParams'
newtype QueryParamsTransformAction = QueryParamsTransformAction [(UnescapedTemplate, Maybe UnescapedTemplate)]
deriving stock (Generic)

View File

@ -16,15 +16,18 @@ where
import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson qualified as J
import Data.Text qualified as T
import Data.Validation
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
( RequestTransformCtx (..),
TemplatingEngine,
Transform (..),
TransformErrorBundle (..),
UnescapedTemplate (..),
mkRequestTemplateTransform,
runRequestTemplateTransform,
throwErrorBundle,
validateRequestUnescapedTemplateTransform',
wrapUnescapedTemplate,
)
import Network.URI (parseURI)
@ -55,7 +58,7 @@ instance Transform Url where
case transformation of
ModifyUrl unescapedTemplate -> do
let template = wrapUnescapedTemplate unescapedTemplate
resultJson <- liftEither $ mkRequestTemplateTransform template context
resultJson <- liftEither $ runRequestTemplateTransform template context
templatedUrlTxt <- case resultJson of
J.String templatedUrlTxt -> pure templatedUrlTxt
val -> do
@ -65,6 +68,13 @@ instance Transform Url where
Nothing -> throwErrorBundle ("Invalid URL: " <> templatedUrlTxt) Nothing
Just _validatedUrl -> pure $ Url templatedUrlTxt
validate ::
TemplatingEngine ->
TransformFn Url ->
Validation TransformErrorBundle ()
validate engine (UrlTransform (ModifyUrl template)) =
validateRequestUnescapedTemplateTransform' engine template
-- | The defunctionalized transformation function on 'Url'
newtype UrlTransformAction
= ModifyUrl UnescapedTemplate

View File

@ -0,0 +1,112 @@
{-# LANGUAGE PolyKinds #-}
-- | We validate 'TransformFn' terms inside 'RequestTransform' before
-- dispatching Metadata actions in 'runMetadataQueryV1M'. Validation
-- follows the same HKD pattern from 'applyRequestTransform' but using
-- 'btraverseC' to call 'validate' from the 'Transform' class on all
-- the HKD fields.
module Hasura.RQL.DDL.Webhook.Transform.Validation
( Unvalidated (..),
Unvalidated1 (..),
unUnvalidate,
unUnvalidate1,
validateRequestTransform,
validateTransforms,
)
where
import Control.Lens (Lens', LensLike, lens, traverseOf)
import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson qualified as J
import Data.Functor.Barbie (FunctorB (bmap), btraverseC)
import Data.Functor.Compose (Compose (..))
import Data.Kind
import Data.Validation (Validation, toEither)
import Hasura.Base.Error (QErr)
import Hasura.EncJSON (EncJSON, encJFromJValue)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform
import Hasura.RQL.DDL.Webhook.Transform.Class
-------------------------------------------------------------------------------
type Tuple1 a b = Compose ((,) a) b
type OptionalTuple1 a b = WithOptional (Tuple1 a b)
-- | A variation on 'RequestTransformFn' where 'TransformFn' is tupled
-- with 'TemplatingEngine'. This is necessary to validate the 'TransformFn'.
--
-- TODO: In the future we most likely want to embed the
-- 'TemplatingEngine' in the 'TransformFn' or the
-- 'Template'/'UnwrappedTemplate', in which case we would not need
-- this alias for validation.
type ValidationFields = RequestFields (OptionalTuple1 TemplatingEngine TransformFn)
-- TODO(SOLOMON): Add lens law unit tests
-- | A lens for zipping our defunctionalized transform with the
-- 'TemplatingEngine' for validation.
transformFns :: Lens' RequestTransform ValidationFields
transformFns = lens getter setter
where
getter :: RequestTransform -> ValidationFields
getter RequestTransform {..} =
bmap (WithOptional . fmap (Compose . (templateEngine,)) . getOptional) requestFields
setter :: RequestTransform -> ValidationFields -> RequestTransform
setter rt requestFields' =
rt {requestFields = bmap (WithOptional . fmap (snd . getCompose) . getOptional) requestFields'}
-- | Validate all 'TransformFn a' fields in the 'RequestTransform'.
validateRequestTransform ::
MonadError TransformErrorBundle m =>
RequestTransform ->
m RequestTransform
validateRequestTransform reqTransform =
liftEither $ toEither $ transformFns (btraverseC @Transform validate') reqTransform
where
validate' ::
(Transform a) =>
OptionalTuple1 TemplatingEngine TransformFn a ->
Validation TransformErrorBundle (OptionalTuple1 TemplatingEngine TransformFn a)
validate' = \case
fn@(WithOptional (Just (Compose (engine, transformFn)))) ->
fn <$ validate engine transformFn
fn -> pure fn
-------------------------------------------------------------------------------
-- | Used to annotate that a 'RequestTransform', or some record
-- containing a 'RequestTransform' has not yet been validated.
newtype Unvalidated a = Unvalidated {_unUnvalidate :: a}
deriving newtype (FromJSON, ToJSON)
-- | A lens for focusing through 'Unvalidated' in 'validateTransforms'.
unUnvalidate :: Lens' (Unvalidated a) a
unUnvalidate = lens _unUnvalidate (\_ a -> Unvalidated a)
-- | Used to annotate that a higher kinded type containing a
-- 'RequestTransform' has not yet been validated.
--
-- This is needed specifically for 'CreateEventTriggerQuery' and any
-- other type that is paramterized by a 'BackendType'.
newtype Unvalidated1 (f :: k -> Type) (a :: k) = Unvalidated1 {_unUnvalidate1 :: f a}
deriving newtype (FromJSON, ToJSON)
-- | A lens for focusing through 'Unvalidated1' in 'validateTransforms'.
unUnvalidate1 :: Lens' (Unvalidated1 f a) (f a)
unUnvalidate1 = lens _unUnvalidate1 (\_ a -> Unvalidated1 a)
-- | Used to focus into a records in 'RQLMetadataV1' and validate any
-- 'RequestTransform' terms present.
validateTransforms ::
(MonadError QErr m) =>
LensLike (Either TransformErrorBundle) api api RequestTransform RequestTransform ->
(api -> m EncJSON) ->
api ->
m EncJSON
validateTransforms focus f q =
case traverseOf focus validateRequestTransform q of
Left error' -> pure $ encJFromJValue $ J.toJSON error'
Right q' -> f q'

View File

@ -27,9 +27,21 @@ module Hasura.RQL.Types.ScheduledTrigger
GetInvocationsBy (..),
GetEventInvocations (..),
ClearCronEvents (..),
cctName,
cctWebhook,
cctCronSchedule,
cctPayload,
cctRetryConf,
cctHeaders,
cctIncludeInMetadata,
cctComment,
cctReplace,
cctRequestTransform,
cctResponseTransform,
)
where
import Control.Lens (makeLenses)
import Data.Aeson
import Data.Aeson qualified as J
import Data.Aeson.Casing
@ -135,20 +147,22 @@ instance FromJSON CronTriggerMetadata where
$(deriveToJSON hasuraJSON {omitNothingFields = True} ''CronTriggerMetadata)
data CreateCronTrigger = CreateCronTrigger
{ cctName :: !TriggerName,
cctWebhook :: !InputWebhook,
cctCronSchedule :: !CronSchedule,
cctPayload :: !(Maybe J.Value),
cctRetryConf :: !STRetryConf,
cctHeaders :: ![HeaderConf],
cctIncludeInMetadata :: !Bool,
cctComment :: !(Maybe Text),
cctReplace :: !Bool,
cctRequestTransform :: !(Maybe RequestTransform),
cctResponseTransform :: !(Maybe MetadataResponseTransform)
{ _cctName :: !TriggerName,
_cctWebhook :: !InputWebhook,
_cctCronSchedule :: !CronSchedule,
_cctPayload :: !(Maybe J.Value),
_cctRetryConf :: !STRetryConf,
_cctHeaders :: ![HeaderConf],
_cctIncludeInMetadata :: !Bool,
_cctComment :: !(Maybe Text),
_cctReplace :: !Bool,
_cctRequestTransform :: !(Maybe RequestTransform),
_cctResponseTransform :: !(Maybe MetadataResponseTransform)
}
deriving (Show, Eq, Generic)
$(makeLenses ''CreateCronTrigger)
instance NFData CreateCronTrigger
instance Cacheable CreateCronTrigger
@ -156,17 +170,17 @@ instance Cacheable CreateCronTrigger
instance FromJSON CreateCronTrigger where
parseJSON =
withObject "CreateCronTrigger" $ \o -> do
cctName <- o .: "name"
cctWebhook <- o .: "webhook"
cctPayload <- o .:? "payload"
cctCronSchedule <- o .: "schedule"
cctRetryConf <- o .:? "retry_conf" .!= defaultSTRetryConf
cctHeaders <- o .:? "headers" .!= []
cctIncludeInMetadata <- o .: "include_in_metadata"
cctComment <- o .:? "comment"
cctReplace <- o .:? "replace" .!= False
cctRequestTransform <- o .:? "request_transform"
cctResponseTransform <- o .:? "response_transform"
_cctName <- o .: "name"
_cctWebhook <- o .: "webhook"
_cctPayload <- o .:? "payload"
_cctCronSchedule <- o .: "schedule"
_cctRetryConf <- o .:? "retry_conf" .!= defaultSTRetryConf
_cctHeaders <- o .:? "headers" .!= []
_cctIncludeInMetadata <- o .: "include_in_metadata"
_cctComment <- o .:? "comment"
_cctReplace <- o .:? "replace" .!= False
_cctRequestTransform <- o .:? "request_transform"
_cctResponseTransform <- o .:? "response_transform"
pure CreateCronTrigger {..}
$(deriveToJSON hasuraJSON {omitNothingFields = True} ''CreateCronTrigger)

View File

@ -45,9 +45,12 @@ import Language.Haskell.TH hiding (Type)
-- backend. This declaration generates the following type:
--
-- data AnyBackend (i :: BackendType -> Type)
-- = PostgresValue (i 'Postgres)
-- | MSSQLValue (i 'MSSQL)
-- | ...
-- = PostgresVanillaValue (i '(Postgres Vanilla))
-- | PostgresCitusValue (i '(Postgres Citus))
-- | BigQueryValue (i 'BigQuery)
-- | MySQLValue (i 'MySQL)
-- | MSSQLValue (i 'MSSQL)
-- | ExperimentalValue (i 'Experimental)
$( do
-- the kind of the type variable, expressed with a quote
varKind <- [t|BackendType -> Type|]

View File

@ -8,6 +8,7 @@ module Hasura.Server.API.Metadata
)
where
import Control.Lens (_Just)
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.Aeson
import Data.Aeson.Casing
@ -20,7 +21,7 @@ import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Logging qualified as L
import Hasura.Metadata.Class
import Hasura.Prelude
import Hasura.Prelude hiding (first)
import Hasura.RQL.DDL.Action
import Hasura.RQL.DDL.ApiLimit
import Hasura.RQL.DDL.ComputedField
@ -41,6 +42,7 @@ import Hasura.RQL.DDL.RemoteSchema
import Hasura.RQL.DDL.ScheduledTrigger
import Hasura.RQL.DDL.Schema
import Hasura.RQL.DDL.Schema.Source
import Hasura.RQL.DDL.Webhook.Transform.Validation
import Hasura.RQL.Types
import Hasura.RQL.Types.Eventing.Backend
import Hasura.RQL.Types.Run
@ -96,7 +98,7 @@ data RQLMetadataV1
RMAddComputedField !(AddComputedField ('Postgres 'Vanilla))
| RMDropComputedField !(DropComputedField ('Postgres 'Vanilla))
| -- Tables event triggers
RMCreateEventTrigger !(AnyBackend CreateEventTriggerQuery)
RMCreateEventTrigger !(AnyBackend (Unvalidated1 CreateEventTriggerQuery))
| RMDeleteEventTrigger !(AnyBackend DeleteEventTriggerQuery)
| RMRedeliverEvent !(AnyBackend RedeliverEventQuery)
| RMInvokeEventTrigger !(AnyBackend InvokeEventTriggerQuery)
@ -110,7 +112,7 @@ data RQLMetadataV1
RMAddRemoteSchemaPermissions !AddRemoteSchemaPermission
| RMDropRemoteSchemaPermissions !DropRemoteSchemaPermissions
| -- Scheduled triggers
RMCreateCronTrigger !CreateCronTrigger
RMCreateCronTrigger !(Unvalidated CreateCronTrigger)
| RMDeleteCronTrigger !ScheduledTriggerName
| RMCreateScheduledEvent !CreateScheduledEvent
| RMDeleteScheduledEvent !DeleteScheduledEvent
@ -118,9 +120,9 @@ data RQLMetadataV1
| RMGetEventInvocations !GetEventInvocations
| RMGetCronTriggers
| -- Actions
RMCreateAction !CreateAction
RMCreateAction !(Unvalidated CreateAction)
| RMDropAction !DropAction
| RMUpdateAction !UpdateAction
| RMUpdateAction !(Unvalidated UpdateAction)
| RMCreateActionPermission !CreateActionPermission
| RMDropActionPermission !DropActionPermission
| -- Query collections, allow list related
@ -163,7 +165,7 @@ data RQLMetadataV1
RMDumpInternalState !DumpInternalState
| RMGetCatalogState !GetCatalogState
| RMSetCatalogState !SetCatalogState
| RMTestWebhookTransform !TestWebhookTransform
| RMTestWebhookTransform !(Unvalidated TestWebhookTransform)
| -- Bulk metadata queries
RMBulk [RQLMetadataRequest]
@ -429,7 +431,13 @@ runMetadataQueryV1M env currentResourceVersion = \case
RMDropFunctionPermission q -> dispatchMetadata runDropFunctionPermission q
RMAddComputedField q -> runAddComputedField q
RMDropComputedField q -> runDropComputedField q
RMCreateEventTrigger q -> dispatchMetadata runCreateEventTriggerQuery q
RMCreateEventTrigger q ->
dispatchMetadata
( validateTransforms
(unUnvalidate1 . cetqRequestTransform . _Just)
(runCreateEventTriggerQuery . _unUnvalidate1)
)
q
RMDeleteEventTrigger q -> dispatchMetadataAndEventTrigger runDeleteEventTriggerQuery q
RMRedeliverEvent q -> dispatchEventTrigger runRedeliverEvent q
RMInvokeEventTrigger q -> dispatchEventTrigger runInvokeEventTrigger q
@ -440,16 +448,28 @@ runMetadataQueryV1M env currentResourceVersion = \case
RMIntrospectRemoteSchema q -> runIntrospectRemoteSchema q
RMAddRemoteSchemaPermissions q -> runAddRemoteSchemaPermissions q
RMDropRemoteSchemaPermissions q -> runDropRemoteSchemaPermissions q
RMCreateCronTrigger q -> runCreateCronTrigger q
RMCreateCronTrigger q ->
validateTransforms
(unUnvalidate . cctRequestTransform . _Just)
(runCreateCronTrigger . _unUnvalidate)
q
RMDeleteCronTrigger q -> runDeleteCronTrigger q
RMCreateScheduledEvent q -> runCreateScheduledEvent q
RMDeleteScheduledEvent q -> runDeleteScheduledEvent q
RMGetScheduledEvents q -> runGetScheduledEvents q
RMGetEventInvocations q -> runGetEventInvocations q
RMGetCronTriggers -> runGetCronTriggers
RMCreateAction q -> runCreateAction q
RMCreateAction q ->
validateTransforms
(unUnvalidate . caDefinition . adRequestTransform . _Just)
(runCreateAction . _unUnvalidate)
q
RMDropAction q -> runDropAction q
RMUpdateAction q -> runUpdateAction q
RMUpdateAction q ->
validateTransforms
(unUnvalidate . uaDefinition . adRequestTransform . _Just)
(runUpdateAction . _unUnvalidate)
q
RMCreateActionPermission q -> runCreateActionPermission q
RMDropActionPermission q -> runDropActionPermission q
RMCreateQueryCollection q -> runCreateCollection q
@ -480,7 +500,11 @@ runMetadataQueryV1M env currentResourceVersion = \case
RMDumpInternalState q -> runDumpInternalState q
RMGetCatalogState q -> runGetCatalogState q
RMSetCatalogState q -> runSetCatalogState q
RMTestWebhookTransform q -> runTestWebhookTransform q
RMTestWebhookTransform q ->
validateTransforms
(unUnvalidate . twtTransformer)
(runTestWebhookTransform . _unUnvalidate)
q
RMSetQueryTagsConfig q -> runSetQueryTagsConfig q
RMBulk q -> encJFromList <$> indexedMapM (runMetadataQueryM env currentResourceVersion) q
where

View File

@ -14,6 +14,7 @@ import Hasura.RQL.DDL.Relationship.Rename
import Hasura.RQL.DDL.RemoteRelationship
import Hasura.RQL.DDL.Schema
import Hasura.RQL.DDL.Schema.Source
import Hasura.RQL.DDL.Webhook.Transform.Validation
import Hasura.RQL.Types
import Hasura.SQL.AnyBackend
@ -59,7 +60,7 @@ data RQLMetadataV1
RMAddComputedField !(AddComputedField ('Postgres 'Vanilla))
| RMDropComputedField !(DropComputedField ('Postgres 'Vanilla))
| -- Tables event triggers
RMCreateEventTrigger !(AnyBackend CreateEventTriggerQuery)
RMCreateEventTrigger !(AnyBackend (Unvalidated1 CreateEventTriggerQuery))
| RMDeleteEventTrigger !(AnyBackend DeleteEventTriggerQuery)
| RMRedeliverEvent !(AnyBackend RedeliverEventQuery)
| RMInvokeEventTrigger !(AnyBackend InvokeEventTriggerQuery)
@ -73,7 +74,7 @@ data RQLMetadataV1
RMAddRemoteSchemaPermissions !AddRemoteSchemaPermission
| RMDropRemoteSchemaPermissions !DropRemoteSchemaPermissions
| -- Scheduled triggers
RMCreateCronTrigger !CreateCronTrigger
RMCreateCronTrigger !(Unvalidated CreateCronTrigger)
| RMDeleteCronTrigger !ScheduledTriggerName
| RMCreateScheduledEvent !CreateScheduledEvent
| RMDeleteScheduledEvent !DeleteScheduledEvent
@ -81,9 +82,9 @@ data RQLMetadataV1
| RMGetEventInvocations !GetEventInvocations
| RMGetCronTriggers
| -- Actions
RMCreateAction !CreateAction
RMCreateAction !(Unvalidated CreateAction)
| RMDropAction !DropAction
| RMUpdateAction !UpdateAction
| RMUpdateAction !(Unvalidated UpdateAction)
| RMCreateActionPermission !CreateActionPermission
| RMDropActionPermission !DropActionPermission
| -- Query collections, allow list related
@ -126,7 +127,7 @@ data RQLMetadataV1
RMDumpInternalState !DumpInternalState
| RMGetCatalogState !GetCatalogState
| RMSetCatalogState !SetCatalogState
| RMTestWebhookTransform !TestWebhookTransform
| RMTestWebhookTransform !(Unvalidated TestWebhookTransform)
| -- Bulk metadata queries
RMBulk [RQLMetadataRequest]

View File

@ -4,17 +4,13 @@
X-Hasura-Role: admin
status: 200
response:
body:
- error_code: Invalid Path
source_position:
end_column: 15
start_line: 0
end_line: 0
start_column: 4
message: '"$body.world"'
headers: []
method: GET
webhook_url: http://localhost:1234/
- error_code: Invalid Path
source_position:
end_column: 15
start_line: 0
end_line: 0
start_column: 4
message: '"$body.world"'
query:
type: test_webhook_transform
args:

View File

@ -2,11 +2,15 @@
url: /v1/metadata
headers:
X-Hasura-Role: admin
status: 400
status: 200
response:
path: "$.args.request_transform.body.template"
error: "Unexpected token '$body'."
code: "parse-failed"
- error_code: Parse Error
source_position:
end_column: 15
start_line: 0
end_line: 0
start_column: 14
message: Unexpected token '$body'.
query:
type: test_webhook_transform
args:

View File

@ -26,7 +26,6 @@
X-Hasura-Role: admin
status: 200
response:
body:
- error_code: Function Error
source_position:
end_column: 51
@ -34,9 +33,6 @@
end_line: 0
start_column: 23
message: Session variable "this_variable_doesnt_exist" not found
headers: []
method: GET
webhook_url: http://localhost:1234/
query:
type: test_webhook_transform
args:
@ -55,7 +51,6 @@
X-Hasura-Role: admin
status: 200
response:
body:
- error_code: Function Error
source_position:
end_column: 15
@ -63,9 +58,6 @@
end_line: 0
start_column: 4
message: Function fooFunction is not defined.
headers: []
method: GET
webhook_url: http://localhost:1234/
query:
type: test_webhook_transform
args:

View File

@ -7,7 +7,7 @@
body: "foo=bar&baz=world"
headers:
- - content-type
- application/json
- application/x-www-form-urlencoded
- - foo
- bar
method: POST
@ -33,4 +33,4 @@
request_headers:
add_headers:
foo: "bar"
content-type: "application/json"
content-type: "application/x-www-form-urlencoded"