-- | Types and subroutines related to constructing transformations on
-- HTTP responses.
module Hasura.RQL.DDL.Webhook.Transform.Response
  ( -- ** Request Transformation Context
    ResponseTransformCtx (..),
    runResponseTemplateTransform,

    -- * Unescaped
    runUnescapedResponseTemplateTransform,
    runUnescapedResponseTemplateTransform',
  )
where

-------------------------------------------------------------------------------

import Control.Arrow (left)
import Data.Aeson qualified as J
import Data.Aeson.Kriti.Functions as KFunc
import Data.ByteString (ByteString)
import Data.Validation (Validation, fromEither)
import Hasura.Prelude
import Hasura.RQL.DDL.Webhook.Transform.Class
  ( Template (..),
    TemplatingEngine (Kriti),
    TransformErrorBundle (..),
    UnescapedTemplate,
    encodeScalar,
    wrapUnescapedTemplate,
  )
import Hasura.Session (SessionVariables)

-------------------------------------------------------------------------------

-- | Common context that is made available to all response transformations.
data ResponseTransformCtx = ResponseTransformCtx
  { ResponseTransformCtx -> Value
responseTransformBody :: J.Value,
    -- NOTE: This is a @Nothing@ if you have a Response Transform but no Request Transform:
    ResponseTransformCtx -> Value
responseTransformReqCtx :: J.Value,
    ResponseTransformCtx -> Maybe SessionVariables
responseSessionVariables :: Maybe SessionVariables,
    ResponseTransformCtx -> TemplatingEngine
responseTransformEngine :: TemplatingEngine,
    ResponseTransformCtx -> Int
responseStatusCode :: Int
  }

-------------------------------------------------------------------------------

-- | 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
runResponseTemplateTransform :: Template
-> ResponseTransformCtx -> Either TransformErrorBundle Value
runResponseTemplateTransform Template
template ResponseTransformCtx {responseTransformEngine :: ResponseTransformCtx -> TemplatingEngine
responseTransformEngine = TemplatingEngine
Kriti, Int
Maybe SessionVariables
Value
responseTransformBody :: ResponseTransformCtx -> Value
responseTransformReqCtx :: ResponseTransformCtx -> Value
responseSessionVariables :: ResponseTransformCtx -> Maybe SessionVariables
responseStatusCode :: ResponseTransformCtx -> Int
responseTransformBody :: Value
responseTransformReqCtx :: Value
responseSessionVariables :: Maybe SessionVariables
responseStatusCode :: Int
..} =
  let context :: [(Text, Value)]
context = [(Text
"$body", Value
responseTransformBody), (Text
"$request", Value -> Value
forall a. ToJSON a => a -> Value
J.toJSON Value
responseTransformReqCtx), (Text
"$response", [Pair] -> Value
J.object ([Key
"status" Key -> Int -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
forall v. ToJSON v => Key -> v -> Pair
J..= Int
responseStatusCode]))]
      customFunctions :: HashMap Text KritiFunc
customFunctions = Maybe SessionVariables -> HashMap Text KritiFunc
KFunc.sessionFunctions Maybe SessionVariables
responseSessionVariables
      eResult :: Either SerializedError Value
eResult = Text
-> [(Text, Value)]
-> HashMap Text KritiFunc
-> Either SerializedError Value
KFunc.runKritiWith (Template -> Text
unTemplate (Template -> Text) -> Template -> Text
forall a b. (a -> b) -> a -> b
$ Template
template) [(Text, Value)]
context HashMap Text KritiFunc
customFunctions
   in Either SerializedError Value
eResult Either SerializedError Value
-> (Either SerializedError Value
    -> Either TransformErrorBundle Value)
-> Either TransformErrorBundle Value
forall a b. a -> (a -> b) -> b
& (SerializedError -> TransformErrorBundle)
-> Either SerializedError Value
-> Either TransformErrorBundle Value
forall b c d. (b -> c) -> Either b d -> Either c d
forall (a :: * -> * -> *) b c d.
ArrowChoice a =>
a b c -> a (Either b d) (Either c d)
left \SerializedError
kritiErr ->
        let renderedErr :: Value
renderedErr = SerializedError -> Value
forall a. ToJSON a => a -> Value
J.toJSON SerializedError
kritiErr
         in [Value] -> TransformErrorBundle
TransformErrorBundle [Value
renderedErr]

-- | Run an 'UnescapedTemplate' with a 'ResponseTransformCtx'.
runUnescapedResponseTemplateTransform ::
  ResponseTransformCtx ->
  UnescapedTemplate ->
  Either TransformErrorBundle ByteString
runUnescapedResponseTemplateTransform :: ResponseTransformCtx
-> UnescapedTemplate -> Either TransformErrorBundle ByteString
runUnescapedResponseTemplateTransform ResponseTransformCtx
context UnescapedTemplate
unescapedTemplate = do
  Value
result <- Template
-> ResponseTransformCtx -> Either TransformErrorBundle Value
runResponseTemplateTransform (UnescapedTemplate -> Template
wrapUnescapedTemplate UnescapedTemplate
unescapedTemplate) ResponseTransformCtx
context
  Value -> Either TransformErrorBundle ByteString
forall (m :: * -> *).
MonadError TransformErrorBundle m =>
Value -> m ByteString
encodeScalar Value
result

-- | Run an 'UnescapedTemplate' with a 'ResponseTransformCtx' in 'Validation'.
runUnescapedResponseTemplateTransform' ::
  ResponseTransformCtx ->
  UnescapedTemplate ->
  Validation TransformErrorBundle ByteString
runUnescapedResponseTemplateTransform' :: ResponseTransformCtx
-> UnescapedTemplate -> Validation TransformErrorBundle ByteString
runUnescapedResponseTemplateTransform' ResponseTransformCtx
context UnescapedTemplate
unescapedTemplate =
  Either TransformErrorBundle ByteString
-> Validation TransformErrorBundle ByteString
forall e a. Either e a -> Validation e a
fromEither
    (Either TransformErrorBundle ByteString
 -> Validation TransformErrorBundle ByteString)
-> Either TransformErrorBundle ByteString
-> Validation TransformErrorBundle ByteString
forall a b. (a -> b) -> a -> b
$ ResponseTransformCtx
-> UnescapedTemplate -> Either TransformErrorBundle ByteString
runUnescapedResponseTemplateTransform ResponseTransformCtx
context UnescapedTemplate
unescapedTemplate