Moving kriti function references into a single module for coordination of availability

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5095
GitOrigin-RevId: 8394c33f0182baad53306c6efc3ab720957b7339
This commit is contained in:
Lyndon Maydwell 2022-07-21 17:05:46 +10:00 committed by hasura-bot
parent b326fd978a
commit 6821b90910
6 changed files with 82 additions and 47 deletions

View File

@ -199,7 +199,7 @@ Please submit any feedback you may have for this feature at https://github.com/h
### Bug fixes and improvements
- server: fix bug where hasura SQL trigger was not dropped when MSSQL source is dropped
- server: Kriti `basicFunctions` now available for REST Connectors
- server: Kriti `basicFunctions` now available for REST Connectors and Webhook Transforms
- server: use `root_field_namespace` as prefix for remote schema (fixes #8438)
- server: allow all argument types for BigQuery routines
- server: fix prefix/suffix behaviour for `graphql-default` naming convention (fixes #8544)

View File

@ -395,6 +395,7 @@ library
, Control.Monad.Unique
, Data.Aeson.Extended
, Data.Aeson.KeyMap.Extended
, Data.Aeson.Kriti.Functions
, Data.Environment
, Data.HashMap.Strict.Extended
, Data.HashMap.Strict.Multi

View File

@ -0,0 +1,65 @@
-- | Module of reusable functions for Kriti transforms.
--
-- NOTE: This defines an alternative `runKritiWith` that includes the basicFunctions by default.
-- You should probably invoke Kriti through this module rather than directly in order to
-- make updating the functions available only require touching this module.
--
-- TODO: This should be added to the documentation and referenced in (for-example) REST Connectors once
-- the documentation refactor project is complete.
module Data.Aeson.Kriti.Functions (runKriti, runKritiWith, basicFunctions, environmentFunctions, sessionFunctions) where
import Control.Arrow (left)
import Data.Aeson qualified as J
import Data.Environment qualified as Env
import Data.HashMap.Strict qualified as M
import Data.Text qualified as T
import Hasura.Prelude
import Hasura.Session (SessionVariables, getSessionVariableValue, mkSessionVariable)
import Kriti qualified
import Kriti.CustomFunctions qualified as Kriti
import Kriti.Error (SerializeError (serialize), SerializedError)
import Kriti.Error qualified as Kriti
type KritiFunc = J.Value -> Either Kriti.CustomFunctionError J.Value
-- | `Data.Aeson.Kriti.Functions.runKriti` attaches the basicFunctions by default
-- NOTE: The error type is SerializedError due to KritiError not currently being exported
runKriti :: Text -> [(Text, J.Value)] -> Either SerializedError J.Value
runKriti t m = left serialize $ Kriti.runKritiWith t m basicFunctions
-- | `Data.Aeson.Kriti.Functions.runKritiWith` attaches the basicFunctions by default.
runKritiWith :: Text -> [(Text, J.Value)] -> HashMap Text KritiFunc -> Either SerializedError J.Value
runKritiWith t m f = left serialize $ Kriti.runKritiWith t m (basicFunctions <> f)
-- | Re-Export of the Kriti 'stdlib'
basicFunctions :: M.HashMap Text KritiFunc
basicFunctions = Kriti.basicFuncMap
-- | Functions that interact with environment variables
environmentFunctions :: Env.Environment -> M.HashMap Text KritiFunc
environmentFunctions env =
M.fromList
[ ("getEnvironmentVariable", getEnvVar)
]
where
getEnvVar :: J.Value -> Either Kriti.CustomFunctionError J.Value
getEnvVar = \case
J.Null -> Right $ J.Null
J.String k -> Right $ J.toJSON $ Env.lookupEnv env (T.unpack k)
_ -> Left $ Kriti.CustomFunctionError "Environment variable name should be a string"
-- | Functions that interact with HGE session during requests
sessionFunctions :: Maybe SessionVariables -> M.HashMap Text KritiFunc
sessionFunctions sessionVars = M.singleton "getSessionVariable" getSessionVar
where
-- Returns Null if session-variables aren't passed in
-- Throws an error if session variable isn't found. Perhaps a version that returns null would also be useful.
-- Lookups are case-insensitive
getSessionVar :: J.Value -> Either Kriti.CustomFunctionError J.Value
getSessionVar = \case
J.Null -> Right $ J.Null
J.String txt ->
case sessionVars >>= getSessionVariableValue (mkSessionVariable txt) of
Just x -> Right $ J.String x
Nothing -> Left . Kriti.CustomFunctionError $ "Session variable \"" <> txt <> "\" not found"
_ -> Left $ Kriti.CustomFunctionError "Session variable name should be a string"

View File

@ -7,6 +7,7 @@ where
--------------------------------------------------------------------------------
import Data.Aeson qualified as J
import Data.Aeson.Kriti.Functions qualified as KFunc
import Data.Environment qualified as Env
import Data.HashMap.Strict qualified as M
import Data.Text qualified as T
@ -14,8 +15,6 @@ import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Backends.DataConnector.Adapter.Types (ConnSourceConfig (ConnSourceConfig, template, value), SourceConfig (..))
import Hasura.Base.Error (Code (NotSupported), QErr, throw400)
import Hasura.Prelude
import Kriti qualified
import Kriti.CustomFunctions qualified as Kriti
import Kriti.Error qualified as Kriti
transformConfig :: (MonadError QErr m) => API.Config -> Maybe Text -> [(T.Text, J.Value)] -> Env.Environment -> m API.Config
@ -23,8 +22,8 @@ transformConfig config maybeTemplate scope env = do
case maybeTemplate of
Nothing -> pure config
(Just t) ->
case Kriti.runKritiWith t (("$config", J.toJSON config) : scope) (additionalFunctions env) of
Left e -> throw400 NotSupported $ "transformConfig: Kriti template transform failed - " <> tshow (Kriti.serialize e)
case KFunc.runKritiWith t (("$config", J.toJSON config) : scope) (additionalFunctions env) of
Left e -> throw400 NotSupported $ "transformConfig: Kriti template transform failed - " <> tshow e
Right (J.Object r) -> pure $ API.Config r
Right o -> throw400 NotSupported $ "transformConfig: Kriti did not decode into Object - " <> tshow o
@ -37,10 +36,4 @@ transformConnSourceConfig :: (MonadError QErr m) => ConnSourceConfig -> [(T.Text
transformConnSourceConfig ConnSourceConfig {value, template} scope env = transformConfig value template scope env
additionalFunctions :: Env.Environment -> M.HashMap T.Text (J.Value -> Either Kriti.CustomFunctionError J.Value)
additionalFunctions env = M.singleton "env" getEnv <> Kriti.basicFuncMap
where
getEnv :: J.Value -> Either Kriti.CustomFunctionError J.Value
getEnv x = case x of
J.Null -> Right $ J.Null
J.String k -> Right $ J.toJSON $ Env.lookupEnv env (T.unpack k)
_ -> Left $ Kriti.CustomFunctionError "Environment variable name should be a string"
additionalFunctions env = KFunc.environmentFunctions env

View File

@ -55,13 +55,13 @@ where
import Control.Lens (Lens', lens, set, traverseOf, view)
import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson.Extended qualified as J
import Data.Aeson.Kriti.Functions qualified as KFunc
import Data.Bifunctor (first)
import Data.ByteString.Lazy qualified as BL
import Data.CaseInsensitive qualified as CI
import Data.Coerce (Coercible)
import Data.Functor.Barbie (AllBF, ApplicativeB, ConstraintsB, FunctorB, TraversableB)
import Data.Functor.Barbie qualified as B
import Data.HashMap.Strict qualified as M
import Data.Text.Encoding qualified as TE
import Data.Validation qualified as V
import Hasura.Incremental (Cacheable)
@ -73,9 +73,7 @@ import Hasura.RQL.DDL.Webhook.Transform.Headers (Headers (..), HeadersTransformF
import Hasura.RQL.DDL.Webhook.Transform.Method
import Hasura.RQL.DDL.Webhook.Transform.QueryParams
import Hasura.RQL.DDL.Webhook.Transform.Url
import Hasura.Session (SessionVariables, getSessionVariableValue, mkSessionVariable)
import Kriti qualified (runKriti)
import Kriti.Error qualified as Kriti (CustomFunctionError (CustomFunctionError), serialize)
import Hasura.Session (SessionVariables)
import Network.HTTP.Client.Transformable qualified as HTTP
-------------------------------------------------------------------------------
@ -376,18 +374,8 @@ buildRespTransformCtx reqCtx sessionVars engine respBody =
{ responseTransformBody = fromMaybe J.Null $ J.decode @J.Value respBody,
responseTransformReqCtx = J.toJSON reqCtx,
responseTransformEngine = engine,
responseTransformFunctions = M.singleton "getSessionVariable" getSessionVar
responseTransformFunctions = KFunc.sessionFunctions sessionVars
}
where
getSessionVar :: J.Value -> Either Kriti.CustomFunctionError J.Value
getSessionVar inp = case inp of
J.String txt ->
case sessionVarValue of
Just x -> Right $ J.String x
Nothing -> Left . Kriti.CustomFunctionError $ "Session variable \"" <> txt <> "\" not found"
where
sessionVarValue = sessionVars >>= getSessionVariableValue (mkSessionVariable txt)
_ -> Left $ Kriti.CustomFunctionError "Session variable name should be a string"
-- | Construct a Template Transformation function for Responses
--
@ -403,7 +391,7 @@ mkRespTemplateTransform _ Body.Remove _ = pure J.Null
mkRespTemplateTransform engine (Body.ModifyAsJSON (Template template)) ResponseTransformCtx {..} =
let context = [("$body", responseTransformBody), ("$request", responseTransformReqCtx)]
in case engine of
Kriti -> first (TransformErrorBundle . pure . J.toJSON . Kriti.serialize) $ Kriti.runKriti template context
Kriti -> first (TransformErrorBundle . pure . J.toJSON) $ KFunc.runKriti template context
mkRespTemplateTransform engine (Body.ModifyAsFormURLEncoded formTemplates) context =
case engine of
Kriti -> do

View File

@ -43,6 +43,7 @@ import Control.Arrow (left)
import Control.Lens (bimap, view)
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
import Data.Aeson qualified as J
import Data.Aeson.Kriti.Functions as KFunc
import Data.Binary.Builder (toLazyByteString)
import Data.ByteString (ByteString)
import Data.ByteString.Builder.Scientific (scientificBuilder)
@ -54,9 +55,7 @@ import Data.Text.Encoding qualified as TE
import Data.Validation (Validation, fromEither)
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
import Hasura.Session (SessionVariables, getSessionVariableValue, mkSessionVariable)
import Kriti (runKritiWith)
import Kriti.CustomFunctions qualified as Kriti (basicFuncMap)
import Hasura.Session (SessionVariables)
import Kriti.Error qualified as Kriti (CustomFunctionError (..), serialize)
import Kriti.Parser qualified as Kriti (parser)
import Network.HTTP.Client.Transformable qualified as HTTP
@ -166,25 +165,14 @@ mkReqTransformCtx url sessionVars rtcEngine reqData =
view HTTP.queryParams reqData & fmap \(key, val) ->
(TE.decodeUtf8 key, fmap TE.decodeUtf8 val)
in Just $ J.toJSON queryParams
rtcFunctions = M.singleton "getSessionVariable" getSessionVar
in RequestTransformCtx
{ rtcBaseUrl,
rtcBody,
rtcSessionVariables,
rtcQueryParams,
rtcEngine,
rtcFunctions = rtcFunctions <> Kriti.basicFuncMap
rtcFunctions = KFunc.sessionFunctions sessionVars
}
where
getSessionVar :: J.Value -> Either Kriti.CustomFunctionError J.Value
getSessionVar inp = case inp of
J.String txt ->
case sessionVarValue of
Just x -> Right $ J.String x
Nothing -> Left . Kriti.CustomFunctionError $ "Session variable \"" <> txt <> "\" not found"
where
sessionVarValue = sessionVars >>= getSessionVariableValue (mkSessionVariable txt)
_ -> Left $ Kriti.CustomFunctionError "Session variable name should be a string"
-- | Common context that is made available to all response transformations.
data ResponseTransformCtx = ResponseTransformCtx
@ -257,9 +245,9 @@ runRequestTemplateTransform template RequestTransformCtx {rtcEngine = Kriti, ..}
[ ("$query_params",) <$> rtcQueryParams,
("$base_url",) <$> rtcBaseUrl
]
eResult = runKritiWith (unTemplate $ template) context rtcFunctions
eResult = KFunc.runKritiWith (unTemplate $ template) context rtcFunctions
in eResult & left \kritiErr ->
let renderedErr = J.toJSON $ Kriti.serialize kritiErr
let renderedErr = J.toJSON kritiErr
in TransformErrorBundle [renderedErr]
-- TODO: Should this live in 'Hasura.RQL.DDL.Webhook.Transform.Validation'?
@ -291,9 +279,9 @@ runResponseTemplateTransform ::
Either TransformErrorBundle J.Value
runResponseTemplateTransform template ResponseTransformCtx {responseTransformEngine = Kriti, ..} =
let context = [("$body", responseTransformBody), ("$request", responseTransformReqCtx)]
eResult = runKritiWith (unTemplate $ template) context responseTransformFunctions
eResult = KFunc.runKritiWith (unTemplate $ template) context responseTransformFunctions
in eResult & left \kritiErr ->
let renderedErr = J.toJSON $ Kriti.serialize kritiErr
let renderedErr = J.toJSON kritiErr
in TransformErrorBundle [renderedErr]
-------------------------------------------------------------------------------