2022-03-11 02:22:54 +03:00
|
|
|
{-# 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
|
2022-11-14 18:18:05 +03:00
|
|
|
import Hasura.RQL.DDL.Webhook.Transform.Body (validateBodyTransformFn)
|
2022-03-11 02:22:54 +03:00
|
|
|
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 ->
|
2022-11-14 18:18:05 +03:00
|
|
|
LensLike (Either TransformErrorBundle) api api MetadataResponseTransform MetadataResponseTransform ->
|
2022-03-11 02:22:54 +03:00
|
|
|
(api -> m EncJSON) ->
|
|
|
|
api ->
|
|
|
|
m EncJSON
|
2022-11-14 18:18:05 +03:00
|
|
|
validateTransforms focusRequestTransform focusResponseTransform f q =
|
|
|
|
let validationRes = do
|
|
|
|
q' <- traverseOf focusRequestTransform validateRequestTransform q
|
|
|
|
traverseOf focusResponseTransform validateResponseTransform q'
|
|
|
|
in case validationRes of
|
|
|
|
Left error' -> pure $ encJFromJValue $ J.toJSON error'
|
|
|
|
Right q' -> f q'
|
|
|
|
|
|
|
|
validateResponseTransform :: MetadataResponseTransform -> Either TransformErrorBundle MetadataResponseTransform
|
|
|
|
validateResponseTransform mrt@MetadataResponseTransform {..} =
|
|
|
|
case mrtBodyTransform of
|
|
|
|
Just bodyTransform -> toEither $ mrt <$ validateBodyTransformFn mrtTemplatingEngine bodyTransform
|
|
|
|
Nothing -> pure mrt
|