Compare commits
10 Commits
bb4375313e
...
3b69dac371
Author | SHA1 | Date | |
---|---|---|---|
3b69dac371 | |||
2834360411 | |||
83f2dc1a2d | |||
3b0da4f3d7 | |||
d83f75b341 | |||
85d876e131 | |||
05fa5df558 | |||
9021f3a25d | |||
025331a9ee | |||
ab4808c44d |
16
CHANGELOG.md
16
CHANGELOG.md
@ -6,10 +6,20 @@ The format is based on
|
|||||||
and this project adheres to
|
and this project adheres to
|
||||||
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
|
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [1.2.0.0] - 2023-02-28
|
||||||
### Added
|
### Added
|
||||||
- Partial schema printing: schema definition encoder.
|
- Schema printing.
|
||||||
- `Semigroup` and `Monoid` instances for `AST.Document.Description`.
|
- `Semigroup` and `Monoid` instances for `AST.Document.Description`.
|
||||||
|
- Support for vector 0.13.0.0 and transformers 0.6.1.0.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix resolvers returning a list in the reverse order.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- GHC 8 support.
|
||||||
|
- Cabal -json flag.
|
||||||
|
- `Test.Hspec.GraphQL`: moved to `graphql-spice` package.
|
||||||
|
- CPP `ifdef WITH_JSON` blocks.
|
||||||
|
|
||||||
## [1.1.0.0] - 2022-12-24
|
## [1.1.0.0] - 2022-12-24
|
||||||
### Changed
|
### Changed
|
||||||
@ -495,7 +505,7 @@ and this project adheres to
|
|||||||
### Added
|
### Added
|
||||||
- Data types for the GraphQL language.
|
- Data types for the GraphQL language.
|
||||||
|
|
||||||
[Unreleased]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=master&rev_to=v1.1.0.0
|
[1.2.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.2.0.0&rev_to=v1.1.0.0
|
||||||
[1.1.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.1.0.0&rev_to=v1.0.3.0
|
[1.1.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.1.0.0&rev_to=v1.0.3.0
|
||||||
[1.0.3.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.3.0&rev_to=v1.0.2.0
|
[1.0.3.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.3.0&rev_to=v1.0.2.0
|
||||||
[1.0.2.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.2.0&rev_to=v1.0.1.0
|
[1.0.2.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.2.0&rev_to=v1.0.1.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cabal-version: 2.4
|
cabal-version: 2.4
|
||||||
|
|
||||||
name: graphql
|
name: graphql
|
||||||
version: 1.1.0.0
|
version: 1.2.0.0
|
||||||
synopsis: Haskell GraphQL implementation
|
synopsis: Haskell GraphQL implementation
|
||||||
description: Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation.
|
description: Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation.
|
||||||
category: Language
|
category: Language
|
||||||
@ -11,7 +11,7 @@ author: Danny Navarro <j@dannynavarro.net>,
|
|||||||
Matthías Páll Gissurarson <mpg@mpg.is>,
|
Matthías Páll Gissurarson <mpg@mpg.is>,
|
||||||
Sólrún Halla Einarsdóttir <she@mpg.is>
|
Sólrún Halla Einarsdóttir <she@mpg.is>
|
||||||
maintainer: belka@caraus.de
|
maintainer: belka@caraus.de
|
||||||
copyright: (c) 2019-2022 Eugen Wissner,
|
copyright: (c) 2019-2023 Eugen Wissner,
|
||||||
(c) 2015-2017 J. Daniel Navarro
|
(c) 2015-2017 J. Daniel Navarro
|
||||||
license: MPL-2.0 AND BSD-3-Clause
|
license: MPL-2.0 AND BSD-3-Clause
|
||||||
license-files: LICENSE,
|
license-files: LICENSE,
|
||||||
@ -21,18 +21,12 @@ extra-source-files:
|
|||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
README.md
|
README.md
|
||||||
tested-with:
|
tested-with:
|
||||||
GHC == 8.10.7,
|
GHC == 9.2.5
|
||||||
GHC == 9.2.4
|
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
location: git://caraus.tech/pub/graphql.git
|
location: git://caraus.tech/pub/graphql.git
|
||||||
|
|
||||||
flag Json
|
|
||||||
description: Whether to build against @aeson 1.x@
|
|
||||||
default: False
|
|
||||||
manual: True
|
|
||||||
|
|
||||||
library
|
library
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Language.GraphQL
|
Language.GraphQL
|
||||||
@ -53,7 +47,6 @@ library
|
|||||||
Language.GraphQL.Type.Schema
|
Language.GraphQL.Type.Schema
|
||||||
Language.GraphQL.Validate
|
Language.GraphQL.Validate
|
||||||
Language.GraphQL.Validate.Validation
|
Language.GraphQL.Validate.Validation
|
||||||
Test.Hspec.GraphQL
|
|
||||||
other-modules:
|
other-modules:
|
||||||
Language.GraphQL.Execute.Transform
|
Language.GraphQL.Execute.Transform
|
||||||
Language.GraphQL.Type.Definition
|
Language.GraphQL.Type.Definition
|
||||||
@ -72,15 +65,9 @@ library
|
|||||||
parser-combinators >= 1.3 && < 2,
|
parser-combinators >= 1.3 && < 2,
|
||||||
template-haskell >= 2.16 && < 3,
|
template-haskell >= 2.16 && < 3,
|
||||||
text >= 1.2 && < 3,
|
text >= 1.2 && < 3,
|
||||||
transformers ^>= 0.5.6,
|
transformers >= 0.5.6 && < 0.7,
|
||||||
unordered-containers ^>= 0.2.14,
|
unordered-containers ^>= 0.2.14,
|
||||||
vector ^>= 0.12.3
|
vector >= 0.12 && < 0.14
|
||||||
if flag(Json)
|
|
||||||
build-depends:
|
|
||||||
aeson >= 1.5.6 && < 1.6,
|
|
||||||
hspec-expectations >= 0.8.2 && < 0.9,
|
|
||||||
scientific >= 0.3.7 && < 0.4
|
|
||||||
cpp-options: -DWITH_JSON
|
|
||||||
|
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
@ -110,7 +97,7 @@ test-suite graphql-test
|
|||||||
conduit,
|
conduit,
|
||||||
exceptions,
|
exceptions,
|
||||||
graphql,
|
graphql,
|
||||||
hspec ^>= 2.9.1,
|
hspec ^>= 2.10.9,
|
||||||
hspec-expectations ^>= 0.8.2,
|
hspec-expectations ^>= 0.8.2,
|
||||||
hspec-megaparsec ^>= 2.2.0,
|
hspec-megaparsec ^>= 2.2.0,
|
||||||
megaparsec,
|
megaparsec,
|
||||||
|
@ -1,105 +1,5 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
#ifdef WITH_JSON
|
|
||||||
-- | This module provides the functions to parse and execute @GraphQL@ queries.
|
|
||||||
--
|
|
||||||
-- The content of this module depends on the value of the __json__ flag, which
|
|
||||||
-- is currently on by default. This behavior will change in the future, the flag
|
|
||||||
-- will be switched off by default and then removed.
|
|
||||||
--
|
|
||||||
-- This documentation is generated with the enabled __json__ flag and functions
|
|
||||||
-- described here support JSON and are deprecated. JSON instances are provided
|
|
||||||
-- now by an additional package, __graphql-spice__. To start using the new
|
|
||||||
-- package create __cabal.project__ in the root directory of your project with
|
|
||||||
-- the following contents:
|
|
||||||
--
|
|
||||||
-- @
|
|
||||||
-- packages: .
|
|
||||||
-- constraints: graphql -json
|
|
||||||
-- @
|
|
||||||
--
|
|
||||||
-- Then add __graphql-spice__ as dependency.
|
|
||||||
--
|
|
||||||
-- The new version of this module defines only one function, @graphql@, which
|
|
||||||
-- works with the internal GraphQL value representation used by this lbirary.
|
|
||||||
-- Refer to @Language.GraphQL.JSON.graphql@ in __graphql-spice__ for the
|
|
||||||
-- function that accepts and returns JSON.
|
|
||||||
module Language.GraphQL
|
|
||||||
( graphql
|
|
||||||
, graphqlSubs
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Control.Monad.Catch (MonadCatch)
|
|
||||||
import qualified Data.Aeson as Aeson
|
|
||||||
import qualified Data.Aeson.Types as Aeson
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
|
||||||
import Data.Maybe (catMaybes)
|
|
||||||
import qualified Data.Sequence as Seq
|
|
||||||
import Data.Text (Text)
|
|
||||||
import Language.GraphQL.AST
|
|
||||||
import Language.GraphQL.Error
|
|
||||||
import Language.GraphQL.Execute
|
|
||||||
import qualified Language.GraphQL.Validate as Validate
|
|
||||||
import Language.GraphQL.Type.Schema (Schema)
|
|
||||||
import Text.Megaparsec (parse)
|
|
||||||
|
|
||||||
{-# DEPRECATED graphql "Use graphql-spice package instead" #-}
|
|
||||||
-- | If the text parses correctly as a @GraphQL@ query the query is
|
|
||||||
-- executed using the given 'Schema'.
|
|
||||||
graphql :: MonadCatch m
|
|
||||||
=> Schema m -- ^ Resolvers.
|
|
||||||
-> Text -- ^ Text representing a @GraphQL@ request document.
|
|
||||||
-> m (Either (ResponseEventStream m Aeson.Value) Aeson.Object) -- ^ Response.
|
|
||||||
graphql schema = graphqlSubs schema mempty mempty
|
|
||||||
|
|
||||||
{-# DEPRECATED graphqlSubs "Use graphql-spice package instead" #-}
|
|
||||||
-- | If the text parses correctly as a @GraphQL@ query the substitution is
|
|
||||||
-- applied to the query and the query is then executed using to the given
|
|
||||||
-- 'Schema'.
|
|
||||||
graphqlSubs :: MonadCatch m
|
|
||||||
=> Schema m -- ^ Resolvers.
|
|
||||||
-> Maybe Text -- ^ Operation name.
|
|
||||||
-> Aeson.Object -- ^ Variable substitution function.
|
|
||||||
-> Text -- ^ Text representing a @GraphQL@ request document.
|
|
||||||
-> m (Either (ResponseEventStream m Aeson.Value) Aeson.Object) -- ^ Response.
|
|
||||||
graphqlSubs schema operationName variableValues document' =
|
|
||||||
case parse document "" document' of
|
|
||||||
Left errorBundle -> pure . formatResponse <$> parseError errorBundle
|
|
||||||
Right parsed ->
|
|
||||||
case validate parsed of
|
|
||||||
Seq.Empty -> fmap formatResponse
|
|
||||||
<$> execute schema operationName variableValues parsed
|
|
||||||
errors -> pure $ pure
|
|
||||||
$ HashMap.singleton "errors"
|
|
||||||
$ Aeson.toJSON
|
|
||||||
$ fromValidationError <$> errors
|
|
||||||
where
|
|
||||||
validate = Validate.document schema Validate.specifiedRules
|
|
||||||
formatResponse (Response data'' Seq.Empty) = HashMap.singleton "data" data''
|
|
||||||
formatResponse (Response data'' errors') = HashMap.fromList
|
|
||||||
[ ("data", data'')
|
|
||||||
, ("errors", Aeson.toJSON $ fromError <$> errors')
|
|
||||||
]
|
|
||||||
fromError Error{..} = Aeson.object $ catMaybes
|
|
||||||
[ Just ("message", Aeson.toJSON message)
|
|
||||||
, toMaybe fromLocation "locations" locations
|
|
||||||
, toMaybe fromPath "path" path
|
|
||||||
]
|
|
||||||
fromValidationError Validate.Error{..} = Aeson.object
|
|
||||||
[ ("message", Aeson.toJSON message)
|
|
||||||
, ("locations", Aeson.listValue fromLocation locations)
|
|
||||||
]
|
|
||||||
toMaybe _ _ [] = Nothing
|
|
||||||
toMaybe f key xs = Just (key, Aeson.listValue f xs)
|
|
||||||
fromPath (Segment segment) = Aeson.String segment
|
|
||||||
fromPath (Index index) = Aeson.toJSON index
|
|
||||||
fromLocation Location{..} = Aeson.object
|
|
||||||
[ ("line", Aeson.toJSON line)
|
|
||||||
, ("column", Aeson.toJSON column)
|
|
||||||
]
|
|
||||||
#else
|
|
||||||
-- | This module provides the functions to parse and execute @GraphQL@ queries.
|
-- | This module provides the functions to parse and execute @GraphQL@ queries.
|
||||||
module Language.GraphQL
|
module Language.GraphQL
|
||||||
( graphql
|
( graphql
|
||||||
@ -144,4 +44,3 @@ graphql schema operationName variableValues document' =
|
|||||||
, locations = locations
|
, locations = locations
|
||||||
, path = []
|
, path = []
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
@ -29,6 +29,7 @@ import qualified Data.Text.Lazy.Builder as Builder
|
|||||||
import Data.Text.Lazy.Builder.Int (decimal)
|
import Data.Text.Lazy.Builder.Int (decimal)
|
||||||
import Data.Text.Lazy.Builder.RealFloat (realFloat)
|
import Data.Text.Lazy.Builder.RealFloat (realFloat)
|
||||||
import qualified Language.GraphQL.AST.Document as Full
|
import qualified Language.GraphQL.AST.Document as Full
|
||||||
|
import qualified Language.GraphQL.AST.DirectiveLocation as DirectiveLocation
|
||||||
|
|
||||||
-- | Instructs the encoder whether the GraphQL document should be minified or
|
-- | Instructs the encoder whether the GraphQL document should be minified or
|
||||||
-- pretty printed.
|
-- pretty printed.
|
||||||
@ -57,25 +58,117 @@ document formatter defs
|
|||||||
definition formatter executableDefinition' : acc
|
definition formatter executableDefinition' : acc
|
||||||
executableDefinition (Full.TypeSystemDefinition typeSystemDefinition' _location) acc =
|
executableDefinition (Full.TypeSystemDefinition typeSystemDefinition' _location) acc =
|
||||||
typeSystemDefinition formatter typeSystemDefinition' : acc
|
typeSystemDefinition formatter typeSystemDefinition' : acc
|
||||||
executableDefinition _ acc = acc -- TODO: TypeSystemExtension missing.
|
executableDefinition (Full.TypeSystemExtension typeSystemExtension' _location) acc =
|
||||||
|
typeSystemExtension formatter typeSystemExtension' : acc
|
||||||
|
|
||||||
|
directiveLocation :: DirectiveLocation.DirectiveLocation -> Lazy.Text
|
||||||
|
directiveLocation = Lazy.Text.pack . show
|
||||||
|
|
||||||
withLineBreak :: Formatter -> Lazy.Text.Text -> Lazy.Text.Text
|
withLineBreak :: Formatter -> Lazy.Text.Text -> Lazy.Text.Text
|
||||||
withLineBreak formatter encodeDefinition
|
withLineBreak formatter encodeDefinition
|
||||||
| Pretty _ <- formatter = Lazy.Text.snoc encodeDefinition '\n'
|
| Pretty _ <- formatter = Lazy.Text.snoc encodeDefinition '\n'
|
||||||
| Minified <- formatter = encodeDefinition
|
| Minified <- formatter = encodeDefinition
|
||||||
|
|
||||||
|
typeSystemExtension :: Formatter -> Full.TypeSystemExtension -> Lazy.Text
|
||||||
|
typeSystemExtension formatter = \case
|
||||||
|
Full.SchemaExtension schemaExtension' ->
|
||||||
|
schemaExtension formatter schemaExtension'
|
||||||
|
Full.TypeExtension typeExtension' -> typeExtension formatter typeExtension'
|
||||||
|
|
||||||
|
schemaExtension :: Formatter -> Full.SchemaExtension -> Lazy.Text
|
||||||
|
schemaExtension formatter = \case
|
||||||
|
Full.SchemaOperationExtension operationDirectives operationTypeDefinitions' ->
|
||||||
|
withLineBreak formatter
|
||||||
|
$ "extend schema "
|
||||||
|
<> optempty (directives formatter) operationDirectives
|
||||||
|
<> bracesList formatter (operationTypeDefinition formatter) (NonEmpty.toList operationTypeDefinitions')
|
||||||
|
Full.SchemaDirectivesExtension operationDirectives -> "extend schema "
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList operationDirectives)
|
||||||
|
|
||||||
|
typeExtension :: Formatter -> Full.TypeExtension -> Lazy.Text
|
||||||
|
typeExtension formatter = \case
|
||||||
|
Full.ScalarTypeExtension name' directives'
|
||||||
|
-> "extend scalar "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> directives formatter (NonEmpty.toList directives')
|
||||||
|
Full.ObjectTypeFieldsDefinitionExtension name' ifaces' directives' fields'
|
||||||
|
-> "extend type "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (" " <>) (implementsInterfaces ifaces')
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (fieldDefinition nextFormatter) (NonEmpty.toList fields')
|
||||||
|
Full.ObjectTypeDirectivesExtension name' ifaces' directives'
|
||||||
|
-> "extend type "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (" " <>) (implementsInterfaces ifaces')
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList directives')
|
||||||
|
Full.ObjectTypeImplementsInterfacesExtension name' ifaces'
|
||||||
|
-> "extend type "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (" " <>) (implementsInterfaces ifaces')
|
||||||
|
Full.InterfaceTypeFieldsDefinitionExtension name' directives' fields'
|
||||||
|
-> "extend interface "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (fieldDefinition nextFormatter) (NonEmpty.toList fields')
|
||||||
|
Full.InterfaceTypeDirectivesExtension name' directives'
|
||||||
|
-> "extend interface "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList directives')
|
||||||
|
Full.UnionTypeUnionMemberTypesExtension name' directives' members'
|
||||||
|
-> "extend union "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> unionMemberTypes formatter members'
|
||||||
|
Full.UnionTypeDirectivesExtension name' directives'
|
||||||
|
-> "extend union "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList directives')
|
||||||
|
Full.EnumTypeEnumValuesDefinitionExtension name' directives' members'
|
||||||
|
-> "extend enum "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (enumValueDefinition formatter) (NonEmpty.toList members')
|
||||||
|
Full.EnumTypeDirectivesExtension name' directives'
|
||||||
|
-> "extend enum "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList directives')
|
||||||
|
Full.InputObjectTypeInputFieldsDefinitionExtension name' directives' fields'
|
||||||
|
-> "extend input "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (inputValueDefinition nextFormatter) (NonEmpty.toList fields')
|
||||||
|
Full.InputObjectTypeDirectivesExtension name' directives'
|
||||||
|
-> "extend input "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) (NonEmpty.toList directives')
|
||||||
|
where
|
||||||
|
nextFormatter = incrementIndent formatter
|
||||||
|
|
||||||
-- | Converts a t'Full.TypeSystemDefinition' into a string.
|
-- | Converts a t'Full.TypeSystemDefinition' into a string.
|
||||||
typeSystemDefinition :: Formatter -> Full.TypeSystemDefinition -> Lazy.Text
|
typeSystemDefinition :: Formatter -> Full.TypeSystemDefinition -> Lazy.Text
|
||||||
typeSystemDefinition formatter = \case
|
typeSystemDefinition formatter = \case
|
||||||
Full.SchemaDefinition operationDirectives operationTypeDefinitions' ->
|
Full.SchemaDefinition operationDirectives operationTypeDefinitions' ->
|
||||||
withLineBreak formatter
|
withLineBreak formatter
|
||||||
$ optempty (directives formatter) operationDirectives
|
$ "schema "
|
||||||
<> "schema "
|
<> optempty (directives formatter) operationDirectives
|
||||||
<> bracesList formatter operationTypeDefinition (NonEmpty.toList operationTypeDefinitions')
|
<> bracesList formatter (operationTypeDefinition formatter) (NonEmpty.toList operationTypeDefinitions')
|
||||||
Full.TypeDefinition typeDefinition' -> typeDefinition formatter typeDefinition'
|
Full.TypeDefinition typeDefinition' -> typeDefinition formatter typeDefinition'
|
||||||
_ -> "" -- TODO: DerictiveDefinition missing.
|
Full.DirectiveDefinition description' name' arguments' locations
|
||||||
where
|
-> description formatter description'
|
||||||
operationTypeDefinition (Full.OperationTypeDefinition operationType' namedType')
|
<> "@"
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> argumentsDefinition formatter arguments'
|
||||||
|
<> " on"
|
||||||
|
<> pipeList formatter (directiveLocation <$> locations)
|
||||||
|
|
||||||
|
operationTypeDefinition :: Formatter -> Full.OperationTypeDefinition -> Lazy.Text.Text
|
||||||
|
operationTypeDefinition formatter (Full.OperationTypeDefinition operationType' namedType')
|
||||||
= indentLine (incrementIndent formatter)
|
= indentLine (incrementIndent formatter)
|
||||||
<> operationType formatter operationType'
|
<> operationType formatter operationType'
|
||||||
<> colon formatter
|
<> colon formatter
|
||||||
@ -106,6 +199,17 @@ argumentDefinition formatter definition' =
|
|||||||
<> maybe mempty (defaultValue formatter . Full.node) defaultValue'
|
<> maybe mempty (defaultValue formatter . Full.node) defaultValue'
|
||||||
<> directives formatter directives'
|
<> directives formatter directives'
|
||||||
|
|
||||||
|
inputValueDefinition :: Formatter -> Full.InputValueDefinition -> Lazy.Text.Text
|
||||||
|
inputValueDefinition formatter definition' =
|
||||||
|
let Full.InputValueDefinition description' name' type'' defaultValue' directives' = definition'
|
||||||
|
in optempty (description formatter) description'
|
||||||
|
<> indentLine formatter
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> colon formatter
|
||||||
|
<> type' type''
|
||||||
|
<> maybe mempty (defaultValue formatter . Full.node) defaultValue'
|
||||||
|
<> directives formatter directives'
|
||||||
|
|
||||||
typeDefinition :: Formatter -> Full.TypeDefinition -> Lazy.Text
|
typeDefinition :: Formatter -> Full.TypeDefinition -> Lazy.Text
|
||||||
typeDefinition formatter = \case
|
typeDefinition formatter = \case
|
||||||
Full.ScalarTypeDefinition description' name' directives'
|
Full.ScalarTypeDefinition description' name' directives'
|
||||||
@ -128,7 +232,27 @@ typeDefinition formatter = \case
|
|||||||
<> optempty (directives formatter) directives'
|
<> optempty (directives formatter) directives'
|
||||||
<> eitherFormat formatter " " ""
|
<> eitherFormat formatter " " ""
|
||||||
<> bracesList formatter (fieldDefinition nextFormatter) fields'
|
<> bracesList formatter (fieldDefinition nextFormatter) fields'
|
||||||
_typeDefinition' -> "" -- TODO: Types missing.
|
Full.UnionTypeDefinition description' name' directives' members'
|
||||||
|
-> optempty (description formatter) description'
|
||||||
|
<> "union "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> unionMemberTypes formatter members'
|
||||||
|
Full.EnumTypeDefinition description' name' directives' members'
|
||||||
|
-> optempty (description formatter) description'
|
||||||
|
<> "enum "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (enumValueDefinition formatter) members'
|
||||||
|
Full.InputObjectTypeDefinition description' name' directives' fields'
|
||||||
|
-> optempty (description formatter) description'
|
||||||
|
<> "input "
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> optempty (directives formatter) directives'
|
||||||
|
<> eitherFormat formatter " " ""
|
||||||
|
<> bracesList formatter (inputValueDefinition nextFormatter) fields'
|
||||||
where
|
where
|
||||||
nextFormatter = incrementIndent formatter
|
nextFormatter = incrementIndent formatter
|
||||||
|
|
||||||
@ -136,10 +260,38 @@ implementsInterfaces :: Foldable t => Full.ImplementsInterfaces t -> Lazy.Text
|
|||||||
implementsInterfaces (Full.ImplementsInterfaces interfaces)
|
implementsInterfaces (Full.ImplementsInterfaces interfaces)
|
||||||
| null interfaces = mempty
|
| null interfaces = mempty
|
||||||
| otherwise = Lazy.Text.fromStrict
|
| otherwise = Lazy.Text.fromStrict
|
||||||
$ Text.append "implements"
|
$ Text.append "implements "
|
||||||
$ Text.intercalate " & "
|
$ Text.intercalate " & "
|
||||||
$ toList interfaces
|
$ toList interfaces
|
||||||
|
|
||||||
|
unionMemberTypes :: Foldable t => Formatter -> Full.UnionMemberTypes t -> Lazy.Text
|
||||||
|
unionMemberTypes formatter (Full.UnionMemberTypes memberTypes)
|
||||||
|
| null memberTypes = mempty
|
||||||
|
| otherwise = Lazy.Text.append "="
|
||||||
|
$ pipeList formatter
|
||||||
|
$ Lazy.Text.fromStrict
|
||||||
|
<$> toList memberTypes
|
||||||
|
|
||||||
|
pipeList :: Foldable t => Formatter -> t Lazy.Text -> Lazy.Text
|
||||||
|
pipeList Minified = (" " <>) . Lazy.Text.intercalate " | " . toList
|
||||||
|
pipeList (Pretty _) = Lazy.Text.concat
|
||||||
|
. fmap (("\n" <> indentSymbol <> "| ") <>)
|
||||||
|
. toList
|
||||||
|
|
||||||
|
enumValueDefinition :: Formatter -> Full.EnumValueDefinition -> Lazy.Text
|
||||||
|
enumValueDefinition (Pretty _) enumValue =
|
||||||
|
let Full.EnumValueDefinition description' name' directives' = enumValue
|
||||||
|
formatter = Pretty 1
|
||||||
|
in description formatter description'
|
||||||
|
<> indentLine formatter
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> directives formatter directives'
|
||||||
|
enumValueDefinition Minified enumValue =
|
||||||
|
let Full.EnumValueDefinition description' name' directives' = enumValue
|
||||||
|
in description Minified description'
|
||||||
|
<> Lazy.Text.fromStrict name'
|
||||||
|
<> directives Minified directives'
|
||||||
|
|
||||||
description :: Formatter -> Full.Description -> Lazy.Text.Text
|
description :: Formatter -> Full.Description -> Lazy.Text.Text
|
||||||
description _formatter (Full.Description Nothing) = ""
|
description _formatter (Full.Description Nothing) = ""
|
||||||
description formatter (Full.Description (Just description')) =
|
description formatter (Full.Description (Just description')) =
|
||||||
|
@ -39,6 +39,7 @@ import qualified Data.List.NonEmpty as NonEmpty
|
|||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Sequence (Seq)
|
import Data.Sequence (Seq)
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import Data.Typeable (cast)
|
import Data.Typeable (cast)
|
||||||
@ -466,12 +467,12 @@ completeValue :: (MonadCatch m, Serialize a)
|
|||||||
completeValue (Out.isNonNullType -> False) _ _ Type.Null =
|
completeValue (Out.isNonNullType -> False) _ _ Type.Null =
|
||||||
pure null
|
pure null
|
||||||
completeValue outputType@(Out.ListBaseType listType) fields errorPath (Type.List list)
|
completeValue outputType@(Out.ListBaseType listType) fields errorPath (Type.List list)
|
||||||
= foldM go (0, []) list >>= coerceResult outputType . List . snd
|
= foldM go Vector.empty list >>= coerceResult outputType . List . Vector.toList
|
||||||
where
|
where
|
||||||
go (index, accumulator) listItem = do
|
go accumulator listItem =
|
||||||
let updatedPath = Index index : errorPath
|
let updatedPath = Index (Vector.length accumulator) : errorPath
|
||||||
completedValue <- completeValue listType fields updatedPath listItem
|
in Vector.snoc accumulator
|
||||||
pure (index + 1, completedValue : accumulator)
|
<$> completeValue listType fields updatedPath listItem
|
||||||
completeValue outputType@(Out.ScalarBaseType _) _ _ (Type.Int int) =
|
completeValue outputType@(Out.ScalarBaseType _) _ _ (Type.Int int) =
|
||||||
coerceResult outputType $ Int int
|
coerceResult outputType $ Int int
|
||||||
completeValue outputType@(Out.ScalarBaseType _) _ _ (Type.Boolean boolean) =
|
completeValue outputType@(Out.ScalarBaseType _) _ _ (Type.Boolean boolean) =
|
||||||
|
@ -5,14 +5,8 @@
|
|||||||
{-# LANGUAGE ExplicitForAll #-}
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
{-# LANGUAGE CPP #-}
|
|
||||||
|
|
||||||
-- | Types and functions used for input and result coercion.
|
-- | Types and functions used for input and result coercion.
|
||||||
--
|
|
||||||
-- JSON instances in this module are only available with the __json__
|
|
||||||
-- flag that is currently on by default, but will be disabled in the future.
|
|
||||||
-- Refer to the documentation in the 'Language.GraphQL' module and to
|
|
||||||
-- the __graphql-spice__ package.
|
|
||||||
module Language.GraphQL.Execute.Coerce
|
module Language.GraphQL.Execute.Coerce
|
||||||
( Output(..)
|
( Output(..)
|
||||||
, Serialize(..)
|
, Serialize(..)
|
||||||
@ -21,10 +15,6 @@ module Language.GraphQL.Execute.Coerce
|
|||||||
, matchFieldValues
|
, matchFieldValues
|
||||||
) where
|
) where
|
||||||
|
|
||||||
#ifdef WITH_JSON
|
|
||||||
import qualified Data.Aeson as Aeson
|
|
||||||
import Data.Scientific (toBoundedInteger, toRealFloat)
|
|
||||||
#endif
|
|
||||||
import Data.Int (Int32)
|
import Data.Int (Int32)
|
||||||
import Data.HashMap.Strict (HashMap)
|
import Data.HashMap.Strict (HashMap)
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
@ -232,69 +222,3 @@ instance Serialize Type.Value where
|
|||||||
$ HashMap.fromList
|
$ HashMap.fromList
|
||||||
$ OrderedMap.toList object
|
$ OrderedMap.toList object
|
||||||
serialize _ _ = Nothing
|
serialize _ _ = Nothing
|
||||||
|
|
||||||
#ifdef WITH_JSON
|
|
||||||
instance Serialize Aeson.Value where
|
|
||||||
serialize (Out.ScalarBaseType scalarType) value
|
|
||||||
| Type.ScalarType "Int" _ <- scalarType
|
|
||||||
, Int int <- value = Just $ Aeson.toJSON int
|
|
||||||
| Type.ScalarType "Float" _ <- scalarType
|
|
||||||
, Float float <- value = Just $ Aeson.toJSON float
|
|
||||||
| Type.ScalarType "String" _ <- scalarType
|
|
||||||
, String string <- value = Just $ Aeson.String string
|
|
||||||
| Type.ScalarType "ID" _ <- scalarType
|
|
||||||
, String string <- value = Just $ Aeson.String string
|
|
||||||
| Type.ScalarType "Boolean" _ <- scalarType
|
|
||||||
, Boolean boolean <- value = Just $ Aeson.Bool boolean
|
|
||||||
serialize _ (Enum enum) = Just $ Aeson.String enum
|
|
||||||
serialize _ (List list) = Just $ Aeson.toJSON list
|
|
||||||
serialize _ (Object object) = Just
|
|
||||||
$ Aeson.object
|
|
||||||
$ OrderedMap.toList
|
|
||||||
$ Aeson.toJSON <$> object
|
|
||||||
serialize _ _ = Nothing
|
|
||||||
null = Aeson.Null
|
|
||||||
|
|
||||||
instance VariableValue Aeson.Value where
|
|
||||||
coerceVariableValue _ Aeson.Null = Just Type.Null
|
|
||||||
coerceVariableValue (In.ScalarBaseType scalarType) value
|
|
||||||
| (Aeson.String stringValue) <- value = Just $ Type.String stringValue
|
|
||||||
| (Aeson.Bool booleanValue) <- value = Just $ Type.Boolean booleanValue
|
|
||||||
| (Aeson.Number numberValue) <- value
|
|
||||||
, (Type.ScalarType "Float" _) <- scalarType =
|
|
||||||
Just $ Type.Float $ toRealFloat numberValue
|
|
||||||
| (Aeson.Number numberValue) <- value = -- ID or Int
|
|
||||||
Type.Int <$> toBoundedInteger numberValue
|
|
||||||
coerceVariableValue (In.EnumBaseType _) (Aeson.String stringValue) =
|
|
||||||
Just $ Type.Enum stringValue
|
|
||||||
coerceVariableValue (In.InputObjectBaseType objectType) value
|
|
||||||
| (Aeson.Object objectValue) <- value = do
|
|
||||||
let (In.InputObjectType _ _ inputFields) = objectType
|
|
||||||
(newObjectValue, resultMap) <- foldWithKey objectValue inputFields
|
|
||||||
if HashMap.null newObjectValue
|
|
||||||
then Just $ Type.Object resultMap
|
|
||||||
else Nothing
|
|
||||||
where
|
|
||||||
foldWithKey objectValue = HashMap.foldrWithKey matchFieldValues'
|
|
||||||
$ Just (objectValue, HashMap.empty)
|
|
||||||
matchFieldValues' _ _ Nothing = Nothing
|
|
||||||
matchFieldValues' fieldName inputField (Just (objectValue, resultMap)) =
|
|
||||||
let (In.InputField _ fieldType _) = inputField
|
|
||||||
insert = flip (HashMap.insert fieldName) resultMap
|
|
||||||
newObjectValue = HashMap.delete fieldName objectValue
|
|
||||||
in case HashMap.lookup fieldName objectValue of
|
|
||||||
Just variableValue -> do
|
|
||||||
coerced <- coerceVariableValue fieldType variableValue
|
|
||||||
pure (newObjectValue, insert coerced)
|
|
||||||
Nothing -> Just (objectValue, resultMap)
|
|
||||||
coerceVariableValue (In.ListBaseType listType) value
|
|
||||||
| (Aeson.Array arrayValue) <- value =
|
|
||||||
Type.List <$> foldr foldVector (Just []) arrayValue
|
|
||||||
| otherwise = coerceVariableValue listType value
|
|
||||||
where
|
|
||||||
foldVector _ Nothing = Nothing
|
|
||||||
foldVector variableValue (Just list) = do
|
|
||||||
coerced <- coerceVariableValue listType variableValue
|
|
||||||
pure $ coerced : list
|
|
||||||
coerceVariableValue _ _ = Nothing
|
|
||||||
#endif
|
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
{- This Source Code Form is subject to the terms of the Mozilla Public License,
|
|
||||||
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
|
||||||
obtain one at https://mozilla.org/MPL/2.0/. -}
|
|
||||||
|
|
||||||
{-# LANGUAGE CPP #-}
|
|
||||||
|
|
||||||
#ifdef WITH_JSON
|
|
||||||
{-# LANGUAGE ExplicitForAll #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
-- | Test helpers.
|
|
||||||
module Test.Hspec.GraphQL {-# DEPRECATED "Use graphql-spice package instead" #-}
|
|
||||||
( shouldResolve
|
|
||||||
, shouldResolveTo
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Control.Monad.Catch (MonadCatch)
|
|
||||||
import qualified Data.Aeson as Aeson
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
|
||||||
import Data.Text (Text)
|
|
||||||
import Language.GraphQL.Error
|
|
||||||
import Test.Hspec.Expectations (Expectation, expectationFailure, shouldBe, shouldNotSatisfy)
|
|
||||||
|
|
||||||
-- | Asserts that a query resolves to some value.
|
|
||||||
shouldResolveTo :: MonadCatch m
|
|
||||||
=> Either (ResponseEventStream m Aeson.Value) Aeson.Object
|
|
||||||
-> Aeson.Object
|
|
||||||
-> Expectation
|
|
||||||
shouldResolveTo (Right actual) expected = actual `shouldBe` expected
|
|
||||||
shouldResolveTo _ _ = expectationFailure
|
|
||||||
"the query is expected to resolve to a value, but it resolved to an event stream"
|
|
||||||
|
|
||||||
-- | Asserts that the response doesn't contain any errors.
|
|
||||||
shouldResolve :: MonadCatch m
|
|
||||||
=> (Text -> IO (Either (ResponseEventStream m Aeson.Value) Aeson.Object))
|
|
||||||
-> Text
|
|
||||||
-> Expectation
|
|
||||||
shouldResolve executor query = do
|
|
||||||
actual <- executor query
|
|
||||||
case actual of
|
|
||||||
Right response ->
|
|
||||||
response `shouldNotSatisfy` HashMap.member "errors"
|
|
||||||
_ -> expectationFailure
|
|
||||||
"the query is expected to resolve to a value, but it resolved to an event stream"
|
|
||||||
#else
|
|
||||||
module Test.Hspec.GraphQL {-# DEPRECATED "Use graphql-spice package instead" #-}
|
|
||||||
(
|
|
||||||
) where
|
|
||||||
#endif
|
|
@ -217,3 +217,53 @@ spec = do
|
|||||||
|]
|
|]
|
||||||
actual = typeSystemDefinition pretty definition'
|
actual = typeSystemDefinition pretty definition'
|
||||||
in actual `shouldBe` expected
|
in actual `shouldBe` expected
|
||||||
|
|
||||||
|
it "encodes an union definition" $
|
||||||
|
let definition' = Full.TypeDefinition
|
||||||
|
$ Full.UnionTypeDefinition mempty "SearchResult" mempty
|
||||||
|
$ Full.UnionMemberTypes ["Photo", "Person"]
|
||||||
|
expected = [gql|
|
||||||
|
union SearchResult =
|
||||||
|
| Photo
|
||||||
|
| Person
|
||||||
|
|]
|
||||||
|
actual = typeSystemDefinition pretty definition'
|
||||||
|
in actual `shouldBe` expected
|
||||||
|
|
||||||
|
it "encodes an enum definition" $
|
||||||
|
let values =
|
||||||
|
[ Full.EnumValueDefinition mempty "NORTH" mempty
|
||||||
|
, Full.EnumValueDefinition mempty "EAST" mempty
|
||||||
|
, Full.EnumValueDefinition mempty "SOUTH" mempty
|
||||||
|
, Full.EnumValueDefinition mempty "WEST" mempty
|
||||||
|
]
|
||||||
|
definition' = Full.TypeDefinition
|
||||||
|
$ Full.EnumTypeDefinition mempty "Direction" mempty values
|
||||||
|
expected = [gql|
|
||||||
|
enum Direction {
|
||||||
|
NORTH
|
||||||
|
EAST
|
||||||
|
SOUTH
|
||||||
|
WEST
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
actual = typeSystemDefinition pretty definition'
|
||||||
|
in actual `shouldBe` expected
|
||||||
|
|
||||||
|
it "encodes an input type" $
|
||||||
|
let intType = Full.TypeNonNull $ Full.NonNullTypeNamed "Int"
|
||||||
|
stringType = Full.TypeNamed "String"
|
||||||
|
fields =
|
||||||
|
[ Full.InputValueDefinition mempty "a" stringType Nothing mempty
|
||||||
|
, Full.InputValueDefinition mempty "b" intType Nothing mempty
|
||||||
|
]
|
||||||
|
definition' = Full.TypeDefinition
|
||||||
|
$ Full.InputObjectTypeDefinition mempty "ExampleInputObject" mempty fields
|
||||||
|
expected = [gql|
|
||||||
|
input ExampleInputObject {
|
||||||
|
a: String
|
||||||
|
b: Int!
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
actual = typeSystemDefinition pretty definition'
|
||||||
|
in actual `shouldBe` expected
|
||||||
|
@ -66,8 +66,9 @@ queryType :: Out.ObjectType IO
|
|||||||
queryType = Out.ObjectType "Query" Nothing []
|
queryType = Out.ObjectType "Query" Nothing []
|
||||||
$ HashMap.fromList
|
$ HashMap.fromList
|
||||||
[ ("philosopher", ValueResolver philosopherField philosopherResolver)
|
[ ("philosopher", ValueResolver philosopherField philosopherResolver)
|
||||||
, ("genres", ValueResolver genresField genresResolver)
|
, ("throwing", ValueResolver throwingField throwingResolver)
|
||||||
, ("count", ValueResolver countField countResolver)
|
, ("count", ValueResolver countField countResolver)
|
||||||
|
, ("sequence", ValueResolver sequenceField sequenceResolver)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
philosopherField =
|
philosopherField =
|
||||||
@ -75,15 +76,22 @@ queryType = Out.ObjectType "Query" Nothing []
|
|||||||
$ HashMap.singleton "id"
|
$ HashMap.singleton "id"
|
||||||
$ In.Argument Nothing (In.NamedScalarType id) Nothing
|
$ In.Argument Nothing (In.NamedScalarType id) Nothing
|
||||||
philosopherResolver = pure $ Object mempty
|
philosopherResolver = pure $ Object mempty
|
||||||
genresField =
|
throwingField =
|
||||||
let fieldType = Out.ListType $ Out.NonNullScalarType string
|
let fieldType = Out.ListType $ Out.NonNullScalarType string
|
||||||
in Out.Field Nothing fieldType HashMap.empty
|
in Out.Field Nothing fieldType HashMap.empty
|
||||||
genresResolver :: Resolve IO
|
throwingResolver :: Resolve IO
|
||||||
genresResolver = throwM PhilosopherException
|
throwingResolver = throwM PhilosopherException
|
||||||
countField =
|
countField =
|
||||||
let fieldType = Out.NonNullScalarType int
|
let fieldType = Out.NonNullScalarType int
|
||||||
in Out.Field Nothing fieldType HashMap.empty
|
in Out.Field Nothing fieldType HashMap.empty
|
||||||
countResolver = pure ""
|
countResolver = pure ""
|
||||||
|
sequenceField =
|
||||||
|
let fieldType = Out.ListType $ Out.NonNullScalarType int
|
||||||
|
in Out.Field Nothing fieldType HashMap.empty
|
||||||
|
sequenceResolver = pure intSequence
|
||||||
|
|
||||||
|
intSequence :: Value
|
||||||
|
intSequence = Type.List [Type.Int 1, Type.Int 2, Type.Int 3]
|
||||||
|
|
||||||
musicType :: Out.ObjectType IO
|
musicType :: Out.ObjectType IO
|
||||||
musicType = Out.ObjectType "Music" Nothing []
|
musicType = Out.ObjectType "Music" Nothing []
|
||||||
@ -344,14 +352,14 @@ spec =
|
|||||||
in sourceQuery `shouldResolveTo` expected
|
in sourceQuery `shouldResolveTo` expected
|
||||||
|
|
||||||
it "gives location information for failed result coercion" $
|
it "gives location information for failed result coercion" $
|
||||||
let data'' = Object $ HashMap.singleton "genres" Null
|
let data'' = Object $ HashMap.singleton "throwing" Null
|
||||||
executionErrors = pure $ Error
|
executionErrors = pure $ Error
|
||||||
{ message = "PhilosopherException"
|
{ message = "PhilosopherException"
|
||||||
, locations = [Location 1 3]
|
, locations = [Location 1 3]
|
||||||
, path = [Segment "genres"]
|
, path = [Segment "throwing"]
|
||||||
}
|
}
|
||||||
expected = Response data'' executionErrors
|
expected = Response data'' executionErrors
|
||||||
sourceQuery = "{ genres }"
|
sourceQuery = "{ throwing }"
|
||||||
in sourceQuery `shouldResolveTo` expected
|
in sourceQuery `shouldResolveTo` expected
|
||||||
|
|
||||||
it "sets data to null if a root field isn't nullable" $
|
it "sets data to null if a root field isn't nullable" $
|
||||||
@ -375,6 +383,12 @@ spec =
|
|||||||
sourceQuery = "{ philosopher(id: \"1\") { firstLanguage } }"
|
sourceQuery = "{ philosopher(id: \"1\") { firstLanguage } }"
|
||||||
in sourceQuery `shouldResolveTo` expected
|
in sourceQuery `shouldResolveTo` expected
|
||||||
|
|
||||||
|
it "returns list elements in the original order" $
|
||||||
|
let data'' = Object $ HashMap.singleton "sequence" intSequence
|
||||||
|
expected = Response data'' mempty
|
||||||
|
sourceQuery = "{ sequence }"
|
||||||
|
in sourceQuery `shouldResolveTo` expected
|
||||||
|
|
||||||
context "queryError" $ do
|
context "queryError" $ do
|
||||||
let namedQuery name = "query " <> name <> " { philosopher(id: \"1\") { interest } }"
|
let namedQuery name = "query " <> name <> " { philosopher(id: \"1\") { interest } }"
|
||||||
twoQueries = namedQuery "A" <> " " <> namedQuery "B"
|
twoQueries = namedQuery "A" <> " " <> namedQuery "B"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user