2022-05-26 17:05:13 +03:00
|
|
|
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
|
|
|
|
|
|
|
|
-- | This module defines the monads required to run parser tests.
|
|
|
|
--
|
|
|
|
-- Warning: a lot of the implementations are currently 'undefined'. As we write
|
|
|
|
-- more advanced tests, they might require implementations.
|
|
|
|
module Test.Parser.Monad
|
|
|
|
( ParserTestT (..),
|
|
|
|
SchemaEnvironment,
|
|
|
|
SchemaTestT (..),
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Data.Aeson.Internal (JSONPath)
|
|
|
|
import Data.Has (Has (..))
|
|
|
|
import Data.Text qualified as T
|
|
|
|
import Hasura.Base.Error (Code, QErr)
|
|
|
|
import Hasura.GraphQL.Execute.Types (GraphQLQueryType (..))
|
|
|
|
import Hasura.GraphQL.Parser.Class (MonadParse (..), MonadSchema (..))
|
|
|
|
import Hasura.GraphQL.Parser.Schema (MkTypename (..))
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
import Hasura.GraphQL.Schema.Common (SchemaContext (..), SchemaOptions (..), ignoreRemoteRelationship)
|
2022-05-26 17:05:13 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.Types.Common (StringifyNumbers (LeaveNumbersAlone))
|
|
|
|
import Hasura.RQL.Types.Function (FunctionPermissionsCtx (..))
|
|
|
|
import Hasura.RQL.Types.RemoteSchema (RemoteSchemaPermsCtx (..))
|
|
|
|
import Hasura.RQL.Types.SourceCustomization (CustomizeRemoteFieldName, MkRootFieldName, NamingCase (..))
|
|
|
|
import Hasura.Session (RoleName, adminRoleName)
|
|
|
|
import Language.Haskell.TH.Syntax qualified as TH
|
|
|
|
import Test.Hspec
|
|
|
|
|
|
|
|
notImplemented :: String -> a
|
|
|
|
notImplemented location =
|
|
|
|
error $ "Not implemented: Test.Parser.Monad." <> location
|
|
|
|
|
|
|
|
-- | Monad builder environment.
|
|
|
|
--
|
|
|
|
-- Parser functions generally have a return type of @m (Parser n)@. The @m@
|
|
|
|
-- parameter is mocked through 'SchemaTestT', which requires a bunch of 'Has'
|
|
|
|
-- instances, as well as a 'ReaderT' instance for environment
|
|
|
|
-- settings/configurations. This type repesents these settings.
|
|
|
|
--
|
|
|
|
-- SchemaEnvironment: currently void. This is subject to change if we require
|
|
|
|
-- more complex setup.
|
|
|
|
data SchemaEnvironment
|
|
|
|
|
|
|
|
instance Has NamingCase SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> NamingCase
|
|
|
|
getter = const HasuraCase
|
|
|
|
|
|
|
|
modifier :: (NamingCase -> NamingCase) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has NamingCase SchemaEnvironment>"
|
|
|
|
|
|
|
|
instance Has RoleName SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> RoleName
|
|
|
|
getter = const adminRoleName
|
|
|
|
|
|
|
|
modifier :: (RoleName -> RoleName) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has RoleName SchemaEnvironment>"
|
|
|
|
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
instance Has SchemaOptions SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> SchemaOptions
|
|
|
|
getter =
|
|
|
|
const
|
|
|
|
SchemaOptions
|
|
|
|
{ soStringifyNum = LeaveNumbersAlone,
|
|
|
|
soDangerousBooleanCollapse = False,
|
|
|
|
soQueryType = QueryHasura,
|
|
|
|
soFunctionPermsContext = FunctionPermissionsInferred,
|
|
|
|
soRemoteSchemaPermsCtx = RemoteSchemaPermsDisabled,
|
|
|
|
soOptimizePermissionFilters = False
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier :: (SchemaOptions -> SchemaOptions) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has SchemaOptions SchemaEnvironment>"
|
|
|
|
|
|
|
|
instance Has SchemaContext SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> SchemaContext
|
|
|
|
getter =
|
|
|
|
const
|
|
|
|
SchemaContext
|
|
|
|
{ scSourceCache = notImplemented "scSourceCache",
|
|
|
|
scRemoteRelationshipParserBuilder = ignoreRemoteRelationship
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier :: (SchemaContext -> SchemaContext) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has SchemaContext SchemaEnvironment>"
|
2022-05-26 17:05:13 +03:00
|
|
|
|
|
|
|
instance Has MkTypename SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> MkTypename
|
|
|
|
getter = const (MkTypename id)
|
|
|
|
|
|
|
|
modifier :: (MkTypename -> MkTypename) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has MkTypeName SchemaEnvironment>"
|
|
|
|
|
|
|
|
instance Has MkRootFieldName SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> MkRootFieldName
|
|
|
|
getter = const mempty
|
|
|
|
|
|
|
|
modifier :: (MkRootFieldName -> MkRootFieldName) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has MkRootFieldName SchemaEnvironment>"
|
|
|
|
|
|
|
|
instance Has CustomizeRemoteFieldName SchemaEnvironment where
|
|
|
|
getter :: SchemaEnvironment -> CustomizeRemoteFieldName
|
|
|
|
getter = notImplemented "getter<Has CustomizeRemoteFieldName SchemaEnvironment>"
|
|
|
|
|
|
|
|
modifier :: (CustomizeRemoteFieldName -> CustomizeRemoteFieldName) -> SchemaEnvironment -> SchemaEnvironment
|
|
|
|
modifier = notImplemented "modifier<Has CustomizeRemoteFieldName SchemaEnvironment>"
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | SchemaTestT
|
|
|
|
newtype SchemaTestT a = SchemaTestT a
|
|
|
|
deriving stock (Functor)
|
|
|
|
deriving (Applicative, Monad) via Identity
|
|
|
|
|
|
|
|
instance MonadError QErr SchemaTestT where
|
|
|
|
throwError :: forall a. QErr -> SchemaTestT a
|
|
|
|
throwError = notImplemented "throwError<MonadError QErr SchemaTestT>"
|
|
|
|
|
|
|
|
catchError :: forall a. SchemaTestT a -> (QErr -> SchemaTestT a) -> SchemaTestT a
|
|
|
|
catchError = notImplemented "catchError<MonadError QErr SchemaTestT>"
|
|
|
|
|
|
|
|
-- | Note this is not used because all the actual getters/setters for
|
|
|
|
-- SchemaEnvironment are @const X@, so these bottoms never actually get
|
|
|
|
-- evaluated.
|
|
|
|
instance MonadReader SchemaEnvironment SchemaTestT where
|
|
|
|
ask :: SchemaTestT SchemaEnvironment
|
|
|
|
ask = notImplemented "ask<MonadReader SchemaEnvironment SchemaTestT>"
|
|
|
|
|
|
|
|
local :: (SchemaEnvironment -> SchemaEnvironment) -> SchemaTestT a -> SchemaTestT a
|
|
|
|
local = notImplemented "local<MonadReader SchemaEnvironment SchemaTestT>"
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | ParserTestT
|
|
|
|
--
|
|
|
|
-- Encodes an assertion error (as `Left`) or a value as `Right`.
|
|
|
|
newtype ParserTestT a = ParserTestT (Either (IO ()) a)
|
|
|
|
deriving stock (Functor)
|
|
|
|
deriving (Applicative, Monad) via (Either (IO ()))
|
|
|
|
|
|
|
|
instance MonadSchema ParserTestT SchemaTestT where
|
|
|
|
memoizeOn :: TH.Name -> a -> SchemaTestT (p ParserTestT b) -> SchemaTestT (p ParserTestT b)
|
|
|
|
memoizeOn _ _ = id
|
|
|
|
|
|
|
|
instance MonadParse ParserTestT where
|
|
|
|
withPath :: (JSONPath -> JSONPath) -> ParserTestT a -> ParserTestT a
|
|
|
|
withPath = const id
|
|
|
|
|
|
|
|
parseErrorWith :: Code -> Text -> ParserTestT a
|
|
|
|
parseErrorWith code text =
|
|
|
|
ParserTestT
|
|
|
|
. Left
|
|
|
|
. expectationFailure
|
|
|
|
$ show code <> ": " <> T.unpack text
|