-- | 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 :: Text -> [(Text, Value)] -> Either SerializedError Value
runKriti Text
t [(Text, Value)]
m = (KritiError -> SerializedError)
-> Either KritiError Value -> Either SerializedError Value
forall (a :: * -> * -> *) b c d.
ArrowChoice a =>
a b c -> a (Either b d) (Either c d)
left KritiError -> SerializedError
forall e. SerializeError e => e -> SerializedError
serialize (Either KritiError Value -> Either SerializedError Value)
-> Either KritiError Value -> Either SerializedError Value
forall a b. (a -> b) -> a -> b
$ Text
-> [(Text, Value)]
-> HashMap Text (Value -> Either CustomFunctionError Value)
-> Either KritiError Value
Kriti.runKritiWith Text
t [(Text, Value)]
m HashMap Text (Value -> Either CustomFunctionError Value)
basicFunctions

-- | `Data.Aeson.Kriti.Functions.runKritiWith` attaches the basicFunctions by default.
runKritiWith :: Text -> [(Text, J.Value)] -> HashMap Text KritiFunc -> Either SerializedError J.Value
runKritiWith :: Text
-> [(Text, Value)]
-> HashMap Text (Value -> Either CustomFunctionError Value)
-> Either SerializedError Value
runKritiWith Text
t [(Text, Value)]
m HashMap Text (Value -> Either CustomFunctionError Value)
f = (KritiError -> SerializedError)
-> Either KritiError Value -> Either SerializedError Value
forall (a :: * -> * -> *) b c d.
ArrowChoice a =>
a b c -> a (Either b d) (Either c d)
left KritiError -> SerializedError
forall e. SerializeError e => e -> SerializedError
serialize (Either KritiError Value -> Either SerializedError Value)
-> Either KritiError Value -> Either SerializedError Value
forall a b. (a -> b) -> a -> b
$ Text
-> [(Text, Value)]
-> HashMap Text (Value -> Either CustomFunctionError Value)
-> Either KritiError Value
Kriti.runKritiWith Text
t [(Text, Value)]
m (HashMap Text (Value -> Either CustomFunctionError Value)
basicFunctions HashMap Text (Value -> Either CustomFunctionError Value)
-> HashMap Text (Value -> Either CustomFunctionError Value)
-> HashMap Text (Value -> Either CustomFunctionError Value)
forall a. Semigroup a => a -> a -> a
<> HashMap Text (Value -> Either CustomFunctionError Value)
f)

-- | Re-Export of the Kriti 'stdlib'
basicFunctions :: M.HashMap Text KritiFunc
basicFunctions :: HashMap Text (Value -> Either CustomFunctionError Value)
basicFunctions = HashMap Text (Value -> Either CustomFunctionError Value)
Kriti.basicFuncMap

-- | Functions that interact with environment variables
environmentFunctions :: Env.Environment -> M.HashMap Text KritiFunc
environmentFunctions :: Environment
-> HashMap Text (Value -> Either CustomFunctionError Value)
environmentFunctions Environment
env =
  [(Text, Value -> Either CustomFunctionError Value)]
-> HashMap Text (Value -> Either CustomFunctionError Value)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
M.fromList
    [ (Text
"getEnvironmentVariable", Value -> Either CustomFunctionError Value
getEnvVar)
    ]
  where
    getEnvVar :: J.Value -> Either Kriti.CustomFunctionError J.Value
    getEnvVar :: Value -> Either CustomFunctionError Value
getEnvVar = \case
      Value
J.Null -> Value -> Either CustomFunctionError Value
forall a b. b -> Either a b
Right (Value -> Either CustomFunctionError Value)
-> Value -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Value
J.Null
      J.String Text
k -> Value -> Either CustomFunctionError Value
forall a b. b -> Either a b
Right (Value -> Either CustomFunctionError Value)
-> Value -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Maybe String -> Value
forall a. ToJSON a => a -> Value
J.toJSON (Maybe String -> Value) -> Maybe String -> Value
forall a b. (a -> b) -> a -> b
$ Environment -> String -> Maybe String
Env.lookupEnv Environment
env (Text -> String
T.unpack Text
k)
      Value
_ -> CustomFunctionError -> Either CustomFunctionError Value
forall a b. a -> Either a b
Left (CustomFunctionError -> Either CustomFunctionError Value)
-> CustomFunctionError -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Text -> CustomFunctionError
Kriti.CustomFunctionError Text
"Environment variable name should be a string"

-- | Functions that interact with HGE session during requests
sessionFunctions :: Maybe SessionVariables -> M.HashMap Text KritiFunc
sessionFunctions :: Maybe SessionVariables
-> HashMap Text (Value -> Either CustomFunctionError Value)
sessionFunctions Maybe SessionVariables
sessionVars = Text
-> (Value -> Either CustomFunctionError Value)
-> HashMap Text (Value -> Either CustomFunctionError Value)
forall k v. Hashable k => k -> v -> HashMap k v
M.singleton Text
"getSessionVariable" Value -> Either CustomFunctionError Value
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 :: Value -> Either CustomFunctionError Value
getSessionVar = \case
      Value
J.Null -> Value -> Either CustomFunctionError Value
forall a b. b -> Either a b
Right (Value -> Either CustomFunctionError Value)
-> Value -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Value
J.Null
      J.String Text
txt ->
        case Maybe SessionVariables
sessionVars Maybe SessionVariables
-> (SessionVariables -> Maybe Text) -> Maybe Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= SessionVariable -> SessionVariables -> Maybe Text
getSessionVariableValue (Text -> SessionVariable
mkSessionVariable Text
txt) of
          Just Text
x -> Value -> Either CustomFunctionError Value
forall a b. b -> Either a b
Right (Value -> Either CustomFunctionError Value)
-> Value -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Text -> Value
J.String Text
x
          Maybe Text
Nothing -> CustomFunctionError -> Either CustomFunctionError Value
forall a b. a -> Either a b
Left (CustomFunctionError -> Either CustomFunctionError Value)
-> (Text -> CustomFunctionError)
-> Text
-> Either CustomFunctionError Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> CustomFunctionError
Kriti.CustomFunctionError (Text -> Either CustomFunctionError Value)
-> Text -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Text
"Session variable \"" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
txt Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\" not found"
      Value
_ -> CustomFunctionError -> Either CustomFunctionError Value
forall a b. a -> Either a b
Left (CustomFunctionError -> Either CustomFunctionError Value)
-> CustomFunctionError -> Either CustomFunctionError Value
forall a b. (a -> b) -> a -> b
$ Text -> CustomFunctionError
Kriti.CustomFunctionError Text
"Session variable name should be a string"