2021-07-30 14:33:06 +03:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
module Hasura.QuickCheck.Instances () where
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
import Data.Aeson.Types qualified as Aeson.Types
|
2022-02-22 18:32:36 +03:00
|
|
|
import Data.HashMap.Strict.Extended qualified as HashMap
|
|
|
|
import Data.HashMap.Strict.InsOrd qualified as InsOrd.HashMap
|
2022-03-01 19:03:23 +03:00
|
|
|
import Data.HashMap.Strict.Multi qualified as MMap
|
2022-02-28 10:49:13 +03:00
|
|
|
import Data.HashSet qualified as HashSet
|
2021-08-06 13:17:43 +03:00
|
|
|
import Data.Ratio ((%))
|
2022-03-01 19:03:23 +03:00
|
|
|
import Data.Set qualified as Set
|
2021-07-30 14:33:06 +03:00
|
|
|
import Data.Text qualified as T
|
2022-03-01 19:03:23 +03:00
|
|
|
import Data.Trie qualified as Trie
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
import Hasura.Base.Error (QErr (..), QErrExtra (..))
|
|
|
|
import Hasura.Base.Error qualified as Error
|
2022-02-22 18:32:36 +03:00
|
|
|
import Hasura.GraphQL.Namespace (NamespacedField (..), namespacedField)
|
2021-07-30 14:33:06 +03:00
|
|
|
import Hasura.Prelude
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Common
|
2022-03-01 19:03:23 +03:00
|
|
|
import Hasura.RQL.Types.Endpoint.Trie
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
import Hasura.RQL.Types.Metadata.Object
|
|
|
|
( MetadataObjId (..),
|
|
|
|
MetadataObject (..),
|
|
|
|
)
|
scaffolding for remote-schemas module
The main aim of the PR is:
1. To set up a module structure for 'remote-schemas' package.
2. Move parts by the remote schema codebase into the new module structure to validate it.
## Notes to the reviewer
Why a PR with large-ish diff?
1. We've been making progress on the MM project but we don't yet know long it is going to take us to get to the first milestone. To understand this better, we need to figure out the unknowns as soon as possible. Hence I've taken a stab at the first two items in the [end-state](https://gist.github.com/0x777/ca2bdc4284d21c3eec153b51dea255c9) document to figure out the unknowns. Unsurprisingly, there are a bunch of issues that we haven't discussed earlier. These are documented in the 'open questions' section.
1. The diff is large but that is only code moved around and I've added a section that documents how things are moved. In addition, there are fair number of PR comments to help with the review process.
## Changes in the PR
### Module structure
Sets up the module structure as follows:
```
Hasura/
RemoteSchema/
Metadata/
Types.hs
SchemaCache/
Types.hs
Permission.hs
RemoteRelationship.hs
Build.hs
MetadataAPI/
Types.hs
Execute.hs
```
### 1. Types representing metadata are moved
Types that capture metadata information (currently scattered across several RQL modules) are moved into `Hasura.RemoteSchema.Metadata.Types`.
- This new module only depends on very 'core' modules such as
`Hasura.Session` for the notion of roles and `Hasura.Incremental` for `Cacheable` typeclass.
- The requirement on database modules is avoided by generalizing the remote schemas metadata to accept an arbitrary 'r' for a remote relationship
definition.
### 2. SchemaCache related types and build logic have been moved
Types that represent remote schemas information in SchemaCache are moved into `Hasura.RemoteSchema.SchemaCache.Types`.
Similar to `H.RS.Metadata.Types`, this module depends on 'core' modules except for `Hasura.GraphQL.Parser.Variable`. It has something to do with remote relationships but I haven't spent time looking into it. The validation of 'remote relationships to remote schema' is also something that needs to be looked at.
Rips out the logic that builds remote schema's SchemaCache information from the monolithic `buildSchemaCacheRule` and moves it into `Hasura.RemoteSchema.SchemaCache.Build`. Further, the `.SchemaCache.Permission` and `.SchemaCache.RemoteRelationship` have been created from existing modules that capture schema cache building logic for those two components.
This was a fair amount of work. On main, currently remote schema's SchemaCache information is built in two phases - in the first phase, 'permissions' and 'remote relationships' are ignored and in the second phase they are filled in.
While remote relationships can only be resolved after partially resolving sources and other remote schemas, the same isn't true for permissions. Further, most of the work that is done to resolve remote relationships can be moved to the first phase so that the second phase can be a very simple traversal.
This is the approach that was taken - resolve permissions and as much as remote relationships information in the first phase.
### 3. Metadata APIs related types and build logic have been moved
The types that represent remote schema related metadata APIs and the execution logic have been moved to `Hasura.RemoteSchema.MetadataAPI.Types` and `.Execute` modules respectively.
## Open questions:
1. `Hasura.RemoteSchema.Metadata.Types` is so called because I was hoping that all of the metadata related APIs of remote schema can be brought in at `Hasura.RemoteSchema.Metadata.API`. However, as metadata APIs depended on functions from `SchemaCache` module (see [1](https://github.com/hasura/graphql-engine-mono/blob/ceba6d62264603ee5d279814677b29bcc43ecaea/server/src-lib/Hasura/RQL/DDL/RemoteSchema.hs#L55) and [2](https://github.com/hasura/graphql-engine-mono/blob/ceba6d62264603ee5d279814677b29bcc43ecaea/server/src-lib/Hasura/RQL/DDL/RemoteSchema.hs#L91), it made more sense to create a separate top-level module for `MetadataAPI`s.
Maybe we can just have `Hasura.RemoteSchema.Metadata` and get rid of the extra nesting or have `Hasura.RemoteSchema.Metadata.{Core,Permission,RemoteRelationship}` if we want to break them down further.
1. `buildRemoteSchemas` in `H.RS.SchemaCache.Build` has the following type:
```haskell
buildRemoteSchemas ::
( ArrowChoice arr,
Inc.ArrowDistribute arr,
ArrowWriter (Seq CollectedInfo) arr,
Inc.ArrowCache m arr,
MonadIO m,
HasHttpManagerM m,
Inc.Cacheable remoteRelationshipDefinition,
ToJSON remoteRelationshipDefinition,
MonadError QErr m
) =>
Env.Environment ->
( (Inc.Dependency (HashMap RemoteSchemaName Inc.InvalidationKey), OrderedRoles),
[RemoteSchemaMetadataG remoteRelationshipDefinition]
)
`arr` HashMap RemoteSchemaName (PartiallyResolvedRemoteSchemaCtxG remoteRelationshipDefinition, MetadataObject)
```
Note the dependence on `CollectedInfo` which is defined as
```haskell
data CollectedInfo
= CIInconsistency InconsistentMetadata
| CIDependency
MetadataObject
-- ^ for error reporting on missing dependencies
SchemaObjId
SchemaDependency
deriving (Eq)
```
this pretty much means that remote schemas is dependent on types from databases, actions, ....
How do we fix this? Maybe introduce a typeclass such as `ArrowCollectRemoteSchemaDependencies` which is defined in `Hasura.RemoteSchema` and then implemented in graphql-engine?
1. The dependency on `buildSchemaCacheFor` in `.MetadataAPI.Execute` which has the following signature:
```haskell
buildSchemaCacheFor ::
(QErrM m, CacheRWM m, MetadataM m) =>
MetadataObjId ->
MetadataModifier ->
```
This can be easily resolved if we restrict what the metadata APIs are allowed to do. Currently, they operate in an unfettered access to modify SchemaCache (the `CacheRWM` constraint):
```haskell
runAddRemoteSchema ::
( QErrM m,
CacheRWM m,
MonadIO m,
HasHttpManagerM m,
MetadataM m,
Tracing.MonadTrace m
) =>
Env.Environment ->
AddRemoteSchemaQuery ->
m EncJSON
```
This should instead be changed to restrict remote schema APIs to only modify remote schema metadata (but has access to the remote schemas part of the schema cache), this dependency is completely removed.
```haskell
runAddRemoteSchema ::
( QErrM m,
MonadIO m,
HasHttpManagerM m,
MonadReader RemoteSchemasSchemaCache m,
MonadState RemoteSchemaMetadata m,
Tracing.MonadTrace m
) =>
Env.Environment ->
AddRemoteSchemaQuery ->
m RemoteSchemeMetadataObjId
```
The idea is that the core graphql-engine would call these functions and then call
`buildSchemaCacheFor`.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6291
GitOrigin-RevId: 51357148c6404afe70219afa71bd1d59bdf4ffc6
2022-10-21 06:13:07 +03:00
|
|
|
import Hasura.RQL.Types.SchemaCache
|
|
|
|
import Hasura.RQL.Types.Table
|
|
|
|
import Hasura.RemoteSchema.Metadata (RemoteSchemaName (..))
|
|
|
|
import Hasura.RemoteSchema.SchemaCache
|
2022-02-22 18:32:36 +03:00
|
|
|
( RemoteSchemaInputValueDefinition (..),
|
|
|
|
RemoteSchemaIntrospection (..),
|
|
|
|
getTypeName,
|
|
|
|
)
|
|
|
|
import Hasura.Server.Utils qualified as Utils
|
|
|
|
import Hasura.Session (SessionVariable, mkSessionVariable)
|
|
|
|
import Language.GraphQL.Draft.Syntax qualified as GraphQL
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
import Network.HTTP.Types qualified as HTTP.Types
|
2022-02-22 18:32:36 +03:00
|
|
|
import Test.QuickCheck.Extended
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Orphan instances for third-party libraries types
|
2021-07-30 14:33:06 +03:00
|
|
|
|
|
|
|
instance Arbitrary Text where
|
Update GraphQL Parser version to fix text encoding issue (fix #1965)
### A long tale about encoding
GraphQL has an [introspection system](http://spec.graphql.org/June2018/#sec-Introspection), which allows its schema to be introspected. This is what we use to introspect [remote schemas](https://github.com/hasura/graphql-engine-mono/blob/41383e1f88c709c6cae4059a1b4fb8f2a58259e6/server/src-rsr/introspection.json). There is one place in the introspection where we might find GraphQL values: the default value of an argument.
```json
{
"fields": [
{
"name": "echo",
"args": [
{
"name": "msg",
"defaultValue": "\"Hello\\nWorld!\""
}
]
}
]
}
```
Note that GraphQL's introspection is transport agnostic: the default value isn't returned as a JSON value, but as a _string-encoded GraphQL Value_. In this case, the value is the GraphQL String `"Hello\nWorld!"`. Embedded into a string, it is encoded as: `"\"Hello\\nWorld!\""`.
When we [parse that value](https://github.com/hasura/graphql-engine-mono/blob/41383e1f88c709c6cae4059a1b4fb8f2a58259e6/server/src-lib/Hasura/GraphQL/RemoteServer.hs#L351), we first extract that JSON string, to get its content, `"Hello\nWorld!"`, then use our [GraphQL Parser library](https://github.com/hasura/graphql-parser-hs/blob/21c1ddfb41791578b66633a2e51f9deb43761108/src/Language/GraphQL/Draft/Parser.hs#L200) to interpret this: we find the double quote, understand that the content is a String, unescape the backslashes, and end up with the desired string value: `['H', 'e', 'l', 'l', 'o', '\n', 'W', 'o', 'r', 'l', 'd', '!']`. This all works fine.
However, there was a bug in the _printer_ part of our parser library: when printing back a String value, we would not re-escape characters properly. In practice, this meant that the GraphQL String `"Hello\nWorld"` would be encoded in JSON as `"\"Hello\nWorld!\""`. Note how the `\n` is not properly double-escaped. This led to a variety of problems, as described in #1965:
- we would successfully parse a remote schema containing such characters in its default values, but then would print those erroneous JSON values in our introspection, which would _crash the console_
- we would inject those default values in queries sent to remote schemas, and print them wrong doing so, sending invalid values to remote schemas and getting errors in result
It turns out that this bug had been lurking in the code for a long time: I combed through the history of [the parser library](https://github.com/hasura/graphql-parser-hs), and as far as I can tell, this bug has always been there. So why was it never caught? After all, we do have [round trip tests](https://github.com/hasura/graphql-parser-hs/blob/21c1ddfb41791578b66633a2e51f9deb43761108/test/Spec.hs#L52) that print + parse arbitrary values and check that we get the same value as a result. They do use any arbitrary unicode character in their generated strings. So... that should have covered it, right?
Well... it turns out that [the tests were ignoring errors](https://github.com/hasura/graphql-parser-hs/blob/7678066c49b61acf0c102a0ffe48e86897e2e22d/test/Spec.hs#L45), and would always return "SUCCESS" in CI, even if they failed... Furthermore, the sample size was small enough that, most of the time, _they would not hit such characters_. Running the tests locally on a loop, I only got errors ~10% of the time...
This was all fixed in hasura/graphql-parser-hs#44. This was probably one of Hasura's longest standing bugs? ^^'
### Description
This PR bumps the version of graphql-parser-hs in the engine, and switches some of our own arbitrary tests to use unicode characters in text rather than alphanumeric values. It turns out those tests were much better at hitting "bad" values, and that they consistently failed when generating arbitrary unicode characters.
https://github.com/hasura/graphql-engine-mono/pull/2031
GitOrigin-RevId: 54fa48270386a67336e5544351691619e0684559
2021-08-06 14:53:52 +03:00
|
|
|
arbitrary = T.pack <$> listOf arbitraryUnicodeChar
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance
|
2022-11-15 14:25:04 +03:00
|
|
|
(Arbitrary k, Hashable k, Arbitrary v) =>
|
2022-02-22 18:32:36 +03:00
|
|
|
Arbitrary (HashMap k v)
|
|
|
|
where
|
|
|
|
arbitrary = HashMap.fromList <$> arbitrary
|
|
|
|
shrink = fmap HashMap.fromList . shrink . HashMap.toList
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance
|
2022-11-15 14:25:04 +03:00
|
|
|
(Arbitrary k, Hashable k, Arbitrary v) =>
|
2022-02-22 18:32:36 +03:00
|
|
|
Arbitrary (InsOrdHashMap k v)
|
|
|
|
where
|
|
|
|
arbitrary = InsOrd.HashMap.fromList <$> arbitrary
|
|
|
|
shrink = fmap InsOrd.HashMap.fromList . shrink . InsOrd.HashMap.toList
|
|
|
|
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
instance Arbitrary Aeson.Types.JSONPathElement where
|
|
|
|
arbitrary = Aeson.Types.Index <$> arbitrary
|
|
|
|
|
|
|
|
instance Arbitrary HTTP.Types.Status where
|
|
|
|
arbitrary = HTTP.Types.Status <$> arbitrary <*> pure mempty
|
|
|
|
|
2022-03-01 19:03:23 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Orphan instances for types defined by us, but which are not coupled to
|
|
|
|
-- GraphQL Engine.
|
|
|
|
|
2022-11-15 14:25:04 +03:00
|
|
|
instance (Hashable k, Arbitrary k, Eq v, Arbitrary v) => Arbitrary (Trie.Trie k v) where
|
2022-03-01 19:03:23 +03:00
|
|
|
arbitrary = Trie.Trie <$> scale (`div` 2) arbitrary <*> arbitrary
|
|
|
|
shrink (Trie.Trie m v) =
|
|
|
|
[Trie.Trie m v' | v' <- shrink v]
|
|
|
|
++ [Trie.Trie m' v | m' <- shrink m]
|
|
|
|
|
2022-11-15 14:25:04 +03:00
|
|
|
instance (Hashable k, Arbitrary k, Ord v, Arbitrary v) => Arbitrary (MMap.MultiMap k v) where
|
2022-03-01 19:03:23 +03:00
|
|
|
arbitrary = MMap.fromMap . fmap (Set.fromList . take 5) <$> arbitrary
|
|
|
|
shrink m = map MMap.fromMap $ shrink $ MMap.toMap m
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Orphan instances for Language.GraphQL.Draft.Syntax types
|
|
|
|
--
|
|
|
|
-- TODO: We control `graphql-parser-hs`; we should upstream these orphan
|
|
|
|
-- instances as either a separate package (e.g. `graphql-parser-hs-quickcheck`)
|
|
|
|
-- or via flag (disabled by default) which enables QuickCheck as a dependency
|
|
|
|
-- and supplies (non-orphan) instances that way.
|
|
|
|
|
2022-07-18 20:01:05 +03:00
|
|
|
{-# ANN arbitraryGraphQLName ("HLint: ignore Use mkName" :: String) #-}
|
|
|
|
-- Factored out so that we can annotate it.
|
|
|
|
arbitraryGraphQLName :: Gen GraphQL.Name
|
|
|
|
arbitraryGraphQLName = GraphQL.unsafeMkName . T.pack <$> listOf1 (elements ['a' .. 'z'])
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary GraphQL.Name where
|
2022-07-18 20:01:05 +03:00
|
|
|
arbitrary = arbitraryGraphQLName
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary GraphQL.Description where
|
|
|
|
arbitrary = GraphQL.Description <$> arbitrary
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary GraphQL.EnumValue where
|
|
|
|
arbitrary = GraphQL.EnumValue <$> arbitrary
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary GraphQL.EnumValueDefinition where
|
2021-08-06 13:17:43 +03:00
|
|
|
arbitrary =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.EnumValueDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> pure []
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary GraphQL.Nullability where
|
|
|
|
arbitrary = GraphQL.Nullability <$> arbitrary
|
2021-08-06 13:17:43 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary (GraphQL.Value Void) where
|
2021-08-06 13:17:43 +03:00
|
|
|
arbitrary =
|
|
|
|
oneof
|
2022-02-22 18:32:36 +03:00
|
|
|
[ pure GraphQL.VNull,
|
|
|
|
GraphQL.VInt <$> arbitrary,
|
|
|
|
GraphQL.VFloat <$> arbitraryScientific,
|
|
|
|
GraphQL.VString <$> arbitrary,
|
|
|
|
GraphQL.VBoolean <$> arbitrary,
|
|
|
|
GraphQL.VEnum <$> arbitrary,
|
2021-08-06 13:17:43 +03:00
|
|
|
-- reduce the internal size factor at every level, so that this
|
|
|
|
-- recursion is guaranteed to terminate
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.VList <$> scale (`div` 2) arbitrary,
|
|
|
|
GraphQL.VObject <$> scale (`div` 2) arbitrary
|
2021-08-06 13:17:43 +03:00
|
|
|
]
|
|
|
|
where
|
|
|
|
arbitraryScientific = do
|
|
|
|
-- fromRational can create invalid repeating values that loop forever
|
|
|
|
-- we avoid this by creating known good ratios
|
|
|
|
num :: Integer <- arbitrary
|
|
|
|
dem :: Integer <- elements [1 .. 32]
|
|
|
|
pure $ fromRational $ num % (10 ^ dem)
|
2021-07-30 14:33:06 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-- Generators for Language.GraphQL.Draft.Syntax types
|
2021-08-06 13:17:43 +03:00
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
genGType :: [GraphQL.Name] -> Gen GraphQL.GType
|
2021-08-06 13:17:43 +03:00
|
|
|
genGType typeNames =
|
|
|
|
frequency
|
|
|
|
-- bias towards avoiding deeply nested lists
|
2022-02-22 18:32:36 +03:00
|
|
|
[ (7, GraphQL.TypeNamed <$> arbitrary <*> elements typeNames),
|
|
|
|
(3, GraphQL.TypeList <$> arbitrary <*> genGType typeNames)
|
2021-08-06 13:17:43 +03:00
|
|
|
]
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
genInputValueDefinition :: [GraphQL.Name] -> Gen GraphQL.InputValueDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
genInputValueDefinition inputTypeNames =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.InputValueDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> genGType inputTypeNames
|
|
|
|
<*> arbitrary
|
|
|
|
<*> pure []
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
genScalarTypeDefinition :: GraphQL.Name -> Gen GraphQL.ScalarTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
genScalarTypeDefinition name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.ScalarTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> pure []
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
genEnumTypeDefinition :: GraphQL.Name -> Gen GraphQL.EnumTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
genEnumTypeDefinition name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.EnumTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> pure []
|
|
|
|
<*> listOf1 arbitrary
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
genUnionTypeDefinition ::
|
|
|
|
[GraphQL.Name] -> GraphQL.Name -> Gen GraphQL.UnionTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
genUnionTypeDefinition objectTypeNames name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.UnionTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> pure []
|
|
|
|
<*> sublistOf1 objectTypeNames
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-08-06 13:17:43 +03:00
|
|
|
genFieldDefinition ::
|
|
|
|
Gen [inputType] ->
|
2022-02-22 18:32:36 +03:00
|
|
|
[GraphQL.Name] ->
|
|
|
|
GraphQL.Name ->
|
|
|
|
Gen (GraphQL.FieldDefinition inputType)
|
2021-08-06 13:17:43 +03:00
|
|
|
genFieldDefinition inputTypes outputTypeNames name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.FieldDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> inputTypes
|
|
|
|
<*> genGType outputTypeNames
|
|
|
|
<*> pure []
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-08-06 13:17:43 +03:00
|
|
|
genObjectTypeDefinition ::
|
|
|
|
Gen [inputType] ->
|
2022-02-22 18:32:36 +03:00
|
|
|
[GraphQL.Name] ->
|
|
|
|
[GraphQL.Name] ->
|
|
|
|
GraphQL.Name ->
|
|
|
|
Gen (GraphQL.ObjectTypeDefinition inputType)
|
2021-08-06 13:17:43 +03:00
|
|
|
genObjectTypeDefinition inputTypes outputTypeNames interfaceTypeNames name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.ObjectTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> sublistOf interfaceTypeNames
|
|
|
|
<*> pure []
|
|
|
|
<*> fields
|
|
|
|
where
|
|
|
|
fields = distinct1 >>= traverse (genFieldDefinition inputTypes outputTypeNames)
|
|
|
|
|
|
|
|
genInterfaceTypeDefinition ::
|
|
|
|
Arbitrary possibleType =>
|
|
|
|
Gen [inputType] ->
|
2022-02-22 18:32:36 +03:00
|
|
|
[GraphQL.Name] ->
|
|
|
|
GraphQL.Name ->
|
|
|
|
Gen (GraphQL.InterfaceTypeDefinition [possibleType] inputType)
|
2021-08-06 13:17:43 +03:00
|
|
|
genInterfaceTypeDefinition inputTypes outputTypeNames name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.InterfaceTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> pure []
|
|
|
|
<*> fields
|
|
|
|
<*> listOf1 arbitrary
|
|
|
|
where
|
|
|
|
fields = distinct1 >>= traverse (genFieldDefinition inputTypes outputTypeNames)
|
|
|
|
|
|
|
|
genInputObjectTypeDefinition ::
|
|
|
|
Gen [inputType] ->
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.Name ->
|
|
|
|
Gen (GraphQL.InputObjectTypeDefinition inputType)
|
2021-08-06 13:17:43 +03:00
|
|
|
genInputObjectTypeDefinition values name =
|
2022-02-22 18:32:36 +03:00
|
|
|
GraphQL.InputObjectTypeDefinition
|
2021-08-06 13:17:43 +03:00
|
|
|
<$> arbitrary
|
|
|
|
<*> pure name
|
|
|
|
<*> pure []
|
|
|
|
<*> values
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Instances for GraphQL Engine types
|
|
|
|
|
2022-03-01 19:03:23 +03:00
|
|
|
instance Arbitrary a => Arbitrary (PathComponent a) where
|
|
|
|
arbitrary =
|
|
|
|
oneof
|
|
|
|
[ PathLiteral <$> arbitrary,
|
|
|
|
pure PathParam
|
|
|
|
]
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
instance Arbitrary SessionVariable where
|
|
|
|
arbitrary = do
|
|
|
|
name <- arbitrary
|
|
|
|
pure $ mkSessionVariable $ Utils.sessionVariablePrefix <> name
|
|
|
|
|
|
|
|
instance Arbitrary IntrospectionResult where
|
|
|
|
arbitrary = do
|
|
|
|
-- first, generate distinct names for each kind of object
|
|
|
|
scalarTypeNames <- distinct
|
|
|
|
objectTypeNames <- distinctExcluding1 scalarTypeNames
|
|
|
|
interfaceTypeNames <- distinctExcluding $ scalarTypeNames ++ objectTypeNames
|
|
|
|
unionTypeNames <- distinctExcluding $ scalarTypeNames ++ objectTypeNames ++ interfaceTypeNames
|
|
|
|
enumTypeNames <- distinctExcluding $ scalarTypeNames ++ objectTypeNames ++ interfaceTypeNames ++ unionTypeNames
|
|
|
|
let outputTypeNames = scalarTypeNames ++ objectTypeNames ++ interfaceTypeNames ++ unionTypeNames ++ enumTypeNames
|
|
|
|
inputObjectTypeNames <- distinctExcluding outputTypeNames
|
|
|
|
let inputTypeNames = scalarTypeNames ++ enumTypeNames ++ inputObjectTypeNames
|
|
|
|
let inputValues = case inputTypeNames of
|
|
|
|
[] -> pure []
|
|
|
|
_ -> listOf $ genRemoteSchemaInputValueDefinition inputTypeNames
|
|
|
|
|
|
|
|
-- then, create a matching definition for each name
|
|
|
|
scalarTypeDefinitions <-
|
|
|
|
for scalarTypeNames genScalarTypeDefinition
|
|
|
|
objectTypeDefinitions <-
|
|
|
|
for objectTypeNames $
|
|
|
|
genObjectTypeDefinition inputValues outputTypeNames interfaceTypeNames
|
|
|
|
interfaceTypeDefinitions <-
|
|
|
|
for interfaceTypeNames $
|
|
|
|
genInterfaceTypeDefinition inputValues outputTypeNames
|
|
|
|
unionTypeDefinitions <-
|
|
|
|
for unionTypeNames $
|
|
|
|
genUnionTypeDefinition objectTypeNames
|
|
|
|
enumTypeDefinitions <-
|
|
|
|
for enumTypeNames genEnumTypeDefinition
|
|
|
|
inputObjectTypeDefinitions <-
|
|
|
|
for inputObjectTypeNames $
|
|
|
|
genInputObjectTypeDefinition inputValues
|
|
|
|
|
|
|
|
-- finally, create an IntrospectionResult from the aggregated definitions
|
|
|
|
let irDoc =
|
|
|
|
RemoteSchemaIntrospection $
|
|
|
|
HashMap.fromListOn getTypeName $
|
|
|
|
concat
|
|
|
|
[ GraphQL.TypeDefinitionScalar <$> scalarTypeDefinitions,
|
|
|
|
GraphQL.TypeDefinitionObject <$> objectTypeDefinitions,
|
|
|
|
GraphQL.TypeDefinitionInterface <$> interfaceTypeDefinitions,
|
|
|
|
GraphQL.TypeDefinitionUnion <$> unionTypeDefinitions,
|
|
|
|
GraphQL.TypeDefinitionEnum <$> enumTypeDefinitions,
|
|
|
|
GraphQL.TypeDefinitionInputObject <$> inputObjectTypeDefinitions
|
|
|
|
]
|
|
|
|
irQueryRoot <- elements objectTypeNames
|
|
|
|
let maybeObjectTypeName = elements $ Nothing : (Just <$> objectTypeNames)
|
|
|
|
irMutationRoot <- maybeObjectTypeName
|
|
|
|
irSubscriptionRoot <- maybeObjectTypeName
|
|
|
|
pure $ IntrospectionResult {..}
|
2021-10-29 17:42:07 +03:00
|
|
|
|
|
|
|
instance Arbitrary a => Arbitrary (NamespacedField a) where
|
|
|
|
arbitrary = oneof [NotNamespaced <$> arbitrary, Namespaced <$> arbitrary]
|
|
|
|
shrink = namespacedField (fmap NotNamespaced . shrink) (fmap Namespaced . shrink)
|
2022-02-22 18:32:36 +03:00
|
|
|
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
withRecordInconsistencyM metadataObject $ do
modifyErr (addTableContext @b table . addRelationshipContext) $ do
(info, dependencies) <- liftEither $ buildRelInfo relDef
recordDependenciesM metadataObject schemaObject dependencies
return info
```
Background
---
We use Haskell's `Arrows` language extension to gain some syntactic sugar when working with `Arrow`s. `Arrow`s are a programming abstraction comparable to `Monad`s.
Unfortunately the syntactic sugar provided by this language extension is not very sweet.
This PR shows how we can sometimes avoid using `Arrow`s altogether, without loss of functionality or correctness. It is a demo of a technique that can be used to cut down the amount of `Arrows`-based code in our codebase by about half.
Approach
---
Although _in general_ not every `Monad` is an `Arrow`, specific `Arrow` instantiations are exactly as powerful as their `Monad` equivalents. Otherwise they wouldn't be very equivalent, would they?
Just like `liftEither` interprets the `Either e` monad into an arbitrary monad implementing `MonadError e`, we add `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable effects can be written _monadically_, and then used in _arrow_ constructions down the line.
This approach cannot be used for arrow effects which do not have a monadic equivalent. In our codebase, the only instance of this is `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
instance Arbitrary QErrExtra where
|
|
|
|
arbitrary =
|
|
|
|
oneof
|
|
|
|
[ ExtraExtensions <$> arbitrary,
|
|
|
|
ExtraInternal <$> arbitrary
|
|
|
|
]
|
|
|
|
|
|
|
|
instance Arbitrary MetadataObjId where
|
|
|
|
arbitrary =
|
|
|
|
oneof
|
|
|
|
-- This is not exhaustive, because it wasn't needed.
|
|
|
|
[ pure $ MOSource SNDefault,
|
|
|
|
MORemoteSchema . RemoteSchemaName <$> arbitrary
|
|
|
|
]
|
|
|
|
|
|
|
|
instance Arbitrary MetadataObject where
|
|
|
|
arbitrary = MetadataObject <$> arbitrary <*> arbitrary
|
|
|
|
|
|
|
|
instance Arbitrary QErr where
|
|
|
|
arbitrary = do
|
|
|
|
-- This is not exhaustive, because it wasn't needed.
|
|
|
|
--
|
|
|
|
-- I just picked a few random error codes.
|
|
|
|
let genCode =
|
|
|
|
elements
|
|
|
|
[ Error.AlreadyExists,
|
|
|
|
Error.Conflict,
|
|
|
|
Error.ConstraintError,
|
|
|
|
Error.ConstraintViolation,
|
|
|
|
Error.NotFound,
|
|
|
|
Error.Unexpected
|
|
|
|
]
|
|
|
|
QErr
|
|
|
|
<$> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> genCode
|
|
|
|
<*> arbitrary
|
|
|
|
|
2022-02-28 10:49:13 +03:00
|
|
|
instance Arbitrary Comment where
|
|
|
|
arbitrary =
|
|
|
|
oneof
|
|
|
|
[ pure Automatic,
|
|
|
|
Explicit <$> arbitrary
|
|
|
|
]
|
|
|
|
|
|
|
|
shrink Automatic = []
|
|
|
|
shrink (Explicit t) = Explicit <$> shrink t
|
|
|
|
|
|
|
|
instance Arbitrary CustomRootField where
|
|
|
|
arbitrary = CustomRootField <$> arbitrary <*> arbitrary
|
|
|
|
|
|
|
|
instance Arbitrary TableCustomRootFields where
|
|
|
|
arbitrary =
|
|
|
|
( TableCustomRootFields
|
|
|
|
<$> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
<*> arbitrary
|
2022-07-06 15:12:55 +03:00
|
|
|
<*> arbitrary
|
2022-07-18 18:15:34 +03:00
|
|
|
<*> arbitrary
|
2022-02-28 10:49:13 +03:00
|
|
|
)
|
|
|
|
`suchThat` allFieldNamesAreUnique
|
|
|
|
where
|
|
|
|
allFieldNamesAreUnique :: TableCustomRootFields -> Bool
|
|
|
|
allFieldNamesAreUnique tcrf =
|
|
|
|
let allNames = mapMaybe _crfName $ getAllCustomRootFields tcrf
|
|
|
|
uniqueNames = HashSet.fromList allNames
|
|
|
|
in length allNames == length uniqueNames
|
|
|
|
|
2022-03-09 09:34:47 +03:00
|
|
|
instance Arbitrary ColumnConfig where
|
|
|
|
arbitrary =
|
|
|
|
ColumnConfig
|
|
|
|
<$> arbitrary
|
|
|
|
<*> arbitrary
|
|
|
|
|
2022-02-22 18:32:36 +03:00
|
|
|
-- Generators for GraphQL Engine types
|
|
|
|
|
|
|
|
genRemoteSchemaInputValueDefinition ::
|
|
|
|
[GraphQL.Name] -> Gen RemoteSchemaInputValueDefinition
|
|
|
|
genRemoteSchemaInputValueDefinition inputTypeNames =
|
|
|
|
RemoteSchemaInputValueDefinition
|
|
|
|
<$> genInputValueDefinition inputTypeNames
|
|
|
|
<*> pure Nothing
|