19 Commits

Author SHA1 Message Date
1c4584abdd Add a release task
Some checks failed
Build / audit (push) Successful in 13m31s
Build / test (push) Successful in 6m11s
Build / doc (push) Successful in 5m23s
Release / release (push) Failing after 16s
2024-05-01 16:38:17 +02:00
e071553e75 Update QuickCheck and containers
All checks were successful
Build / audit (push) Successful in 13m26s
Build / test (push) Successful in 6m12s
Build / doc (push) Successful in 5m17s
2024-05-01 14:06:33 +02:00
e731c7db07 Remove deprecated symbols from the Error module
All checks were successful
Build / audit (push) Successful in 14m21s
Build / test (push) Successful in 6m22s
Build / doc (push) Successful in 5m33s
2024-04-04 18:51:58 +02:00
303cf18d77 Switch to haskell images in the CI
All checks were successful
Build / audit (push) Successful in 13m52s
Build / test (push) Successful in 6m12s
Build / doc (push) Successful in 5m18s
2024-04-03 18:17:23 +02:00
6b8346e527 Update hlint to 3.8
All checks were successful
Build / audit (push) Successful in 13m37s
Build / test (push) Successful in 6m16s
Build / doc (push) Successful in 6m20s
2024-04-02 22:06:16 +02:00
303f84ed41 Release 1.2.0.3
All checks were successful
Build / audit (push) Successful in 15m3s
Build / test (push) Successful in 8m10s
Build / doc (push) Successful in 6m52s
2024-01-09 14:29:54 +01:00
d2ea9fb467 Release 1.2.0.2
All checks were successful
Build / audit (push) Successful in 15m2s
Build / test (push) Successful in 8m0s
Build / doc (push) Successful in 6m51s
2024-01-08 22:29:58 +01:00
809f446ff1 Fix variable location in objects and lists
All checks were successful
Build / audit (push) Successful in 15m35s
Build / test (push) Successful in 8m6s
Build / doc (push) Successful in 6m59s
2024-01-05 20:46:02 +01:00
b1b6bfcdb9 Add a test for the wrong variable location
All checks were successful
Build / audit (push) Successful in 16m30s
Build / test (push) Successful in 8m26s
Build / doc (push) Successful in 7m6s
inside an input object for the role checking for unused and undefined
variables.
2023-12-28 09:45:39 +01:00
59aa010f0b Fix "variable is not used" error
All checks were successful
Build / audit (pull_request) Successful in 16m24s
Build / test (pull_request) Successful in 9m2s
Build / doc (pull_request) Successful in 7m22s
Build / audit (push) Successful in 16m16s
Build / test (push) Successful in 8m29s
Build / doc (push) Successful in 7m36s
2023-12-27 12:50:17 +01:00
b1c5a568dd Add a failing test for unused variables bug
All checks were successful
Build / audit (push) Successful in 15m27s
Build / test (push) Successful in 8m32s
Build / doc (push) Successful in 7m24s
2023-12-21 21:34:37 +01:00
5ffe8c72fa Add a workflow
All checks were successful
Build / audit (push) Successful in 16m26s
Build / test (push) Successful in 7m51s
Build / doc (push) Successful in 6m26s
2023-11-27 13:00:55 +01:00
a961b168db Add a test for the input object coercion issue 2023-11-08 20:08:47 +01:00
a1cda38e20 Fix values not being coerced to lists 2023-11-04 13:46:10 +01:00
7c78497e04 Add a CHANGELOG entry for the show type fix 2023-10-14 16:40:19 +02:00
fdc43e4e25 Fix non nullable type representation
…in executor error messages.
2023-10-13 20:42:24 +02:00
2fdf04f54a Remove leading carriage return in gql 2023-08-03 08:00:36 +02:00
3ed7dcd401 Support hspec 2.11 2023-04-25 08:51:18 +02:00
408dfb4301 Update web links
All checks were successful
test Test.
2023-03-23 09:46:04 +01:00
16 changed files with 309 additions and 161 deletions

View File

@ -0,0 +1,52 @@
name: Build
on:
push:
branches:
- '**'
pull_request:
branches: [master]
jobs:
audit:
runs-on: haskell
steps:
- name: Set up environment
run: |
apt-get update -y
apt-get upgrade -y
apt-get install -y nodejs pkg-config
- uses: actions/checkout@v4
- name: Install dependencies
run: |
cabal update
cabal install hlint "--constraint=hlint ==3.8"
- run: cabal exec hlint -- src tests
test:
runs-on: haskell
steps:
- name: Set up environment
run: |
apt-get update -y
apt-get upgrade -y
apt-get install -y nodejs pkg-config
- uses: actions/checkout@v4
- name: Install dependencies
run: cabal update
- name: Prepare system
run: cabal build graphql-test
- run: cabal test --test-show-details=streaming
doc:
runs-on: haskell
steps:
- name: Set up environment
run: |
apt-get update -y
apt-get upgrade -y
apt-get install -y nodejs pkg-config
- uses: actions/checkout@v4
- name: Install dependencies
run: cabal update
- run: cabal haddock --enable-documentation

View File

@ -0,0 +1,23 @@
name: Release
on:
push:
tags:
- '**'
jobs:
release:
runs-on: haskell
steps:
- name: Set up environment
run: |
apt-get update -y
apt-get upgrade -y
apt-get install -y nodejs pkg-config
- uses: actions/checkout@v4
- name: Upload a candidate
env:
HACKAGE_PASSWORD: ${{ secrets.HACKAGE_PASSWORD }}
run: |
cabal sdist
cabal upload --username belka --password ${HACKAGE_PASSWORD}

View File

@ -6,6 +6,26 @@ 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/).
## [1.3.0.0] - 2024-05-01
### Changed
- Remove deprecated `runCollectErrs`, `Resolution`, `CollectErrsT` from the
`Error` module.
## [1.2.0.3] - 2024-01-09
### Fixed
- Fix corrupted source distribution.
## [1.2.0.2] - 2024-01-09
### Fixed
- `gql` removes not only leading `\n` but also `\r`.
- Fix non nullable type string representation in executor error messages.
- Fix input objects not being coerced to lists.
- Fix used variables are not found in the properties of input objects.
## [1.2.0.1] - 2023-04-25
### Fixed
- Support hspec 2.11.
## [1.2.0.0] - 2023-02-28 ## [1.2.0.0] - 2023-02-28
### Added ### Added
- Schema printing. - Schema printing.
@ -400,7 +420,6 @@ and this project adheres to
- `Language.GraphQL.Schema`: `enum`, `enumA`, `wrappedEnum` and `wrappedEnumA`. - `Language.GraphQL.Schema`: `enum`, `enumA`, `wrappedEnum` and `wrappedEnumA`.
Use `scalar`, `scalarA`, `wrappedScalar` and `wrappedScalarA` instead. Use `scalar`, `scalarA`, `wrappedScalar` and `wrappedScalarA` instead.
## [0.5.1.0] - 2019-10-22 ## [0.5.1.0] - 2019-10-22
### Deprecated ### Deprecated
- `Language.GraphQL.AST.Arguments`. Use `[Language.GraphQL.AST.Argument]` - `Language.GraphQL.AST.Arguments`. Use `[Language.GraphQL.AST.Argument]`
@ -505,24 +524,28 @@ and this project adheres to
### Added ### Added
- Data types for the GraphQL language. - Data types for the GraphQL language.
[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.3.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.3...v1.3.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.2.0.3]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.2...v1.2.0.3
[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.2.0.2]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.1...v1.2.0.2
[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.2.0.1]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.0...v1.2.0.1
[1.0.1.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.1.0&rev_to=v1.0.0.0 [1.2.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.1.0.0...v1.2.0.0
[1.0.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.0.0&rev_to=v0.11.1.0 [1.1.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.0.3.0...v1.1.0.0
[0.11.1.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.11.1.0&rev_to=v0.11.0.0 [1.0.3.0]: https://git.caraus.tech/OSS/graphql/compare/v1.0.2.0...v1.0.3.0
[0.11.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.11.0.0&rev_to=v0.10.0.0 [1.0.2.0]: https://git.caraus.tech/OSS/graphql/compare/v1.0.1.0...v1.0.2.0
[0.10.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.10.0.0&rev_to=v0.9.0.0 [1.0.1.0]: https://git.caraus.tech/OSS/graphql/compare/v1.0.0.0...v1.0.1.0
[0.9.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.9.0.0&rev_to=v0.8.0.0 [1.0.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.11.1.0...v1.0.0.0
[0.8.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.8.0.0&rev_to=v0.7.0.0 [0.11.1.0]: https://git.caraus.tech/OSS/graphql/compare/v0.11.0.0...v0.11.1.0
[0.7.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.7.0.0&rev_to=v0.6.1.0 [0.11.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.10.0.0...v0.11.0.0
[0.6.1.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.6.1.0&rev_to=v0.6.0.0 [0.10.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.9.0.0...v0.10.0.0
[0.6.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.6.0.0&rev_to=v0.5.1.0 [0.9.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.8.0.0...v0.9.0.0
[0.5.1.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.5.1.0&rev_to=v0.5.0.1 [0.8.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.7.0.0...v0.8.0.0
[0.5.0.1]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.5.0.1&rev_to=v0.5.0.0 [0.7.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.6.1.0...v0.7.0.0
[0.5.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.5.0.0&rev_to=v0.4.0.0 [0.6.1.0]: https://git.caraus.tech/OSS/graphql/compare/v0.6.0.0...v0.6.1.0
[0.4.0.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.4.0.0&rev_to=v0.3 [0.6.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.5.1.0...v0.6.0.0
[0.3]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.3&rev_to=v0.2.1 [0.5.1.0]: https://git.caraus.tech/OSS/graphql/compare/v0.5.0.1...v0.5.1.0
[0.2.1]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.2.1&rev_to=v0.2 [0.5.0.1]: https://git.caraus.tech/OSS/graphql/compare/v0.5.0.0...v0.5.0.1
[0.2]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v0.2&rev_to=v0.1 [0.5.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.4.0.0...v0.5.0.0
[0.4.0.0]: https://git.caraus.tech/OSS/graphql/compare/v0.3...v0.4.0.0
[0.3]: https://git.caraus.tech/OSS/graphql/compare/v0.2.1...v0.3
[0.2.1]: https://git.caraus.tech/OSS/graphql/compare/v0.2...v0.2.1
[0.2]: https://git.caraus.tech/OSS/graphql/compare/v0.1...v0.2

View File

@ -1,15 +1,12 @@
# GraphQL implementation in Haskell # GraphQL implementation in Haskell
[![Simple Haskell](https://www.simplehaskell.org/badges/badge.svg)](https://www.simplehaskell.org) See https://git.caraus.tech/OSS/graphql.
[![CI/CD](https://img.shields.io/badge/CI-CD-brightgreen)](https://build.caraus.tech/go/pipelines)
See https://www.caraus.tech/projects/pub-graphql.
Report issues on the Report issues on the
[bug tracker](https://www.caraus.tech/projects/pub-graphql/issues). [bug tracker](https://git.caraus.tech/OSS/graphql/issues).
API documentation is available through API documentation is available through
[Hackage](https://hackage.haskell.org/package/graphql). [Hackage](https://hackage.haskell.org/package/graphql).
Further documentation will be made available in the Further documentation will be made available in the
[Wiki](https://www.caraus.tech/projects/pub-graphql/wiki). [Wiki](https://git.caraus.tech/OSS/graphql/wiki).

View File

@ -1,17 +1,17 @@
cabal-version: 2.4 cabal-version: 2.4
name: graphql name: graphql
version: 1.2.0.0 version: 1.3.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
homepage: https://www.caraus.tech/projects/pub-graphql homepage: https://git.caraus.tech/OSS/graphql
bug-reports: https://www.caraus.tech/projects/pub-graphql/issues bug-reports: https://git.caraus.tech/OSS/graphql/issues
author: Danny Navarro <j@dannynavarro.net>, 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-2023 Eugen Wissner, copyright: (c) 2019-2024 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,11 +21,12 @@ extra-source-files:
CHANGELOG.md CHANGELOG.md
README.md README.md
tested-with: tested-with:
GHC == 9.2.5 GHC == 9.4.7,
GHC == 9.6.3
source-repository head source-repository head
type: git type: git
location: git://caraus.tech/pub/graphql.git location: https://git.caraus.tech/OSS/graphql.git
library library
exposed-modules: exposed-modules:
@ -59,7 +60,7 @@ library
build-depends: build-depends:
base >= 4.7 && < 5, base >= 4.7 && < 5,
conduit ^>= 1.3.4, conduit ^>= 1.3.4,
containers ^>= 0.6.2, containers >= 0.6 && < 0.8,
exceptions ^>= 0.10.4, exceptions ^>= 0.10.4,
megaparsec >= 9.0 && < 10, megaparsec >= 9.0 && < 10,
parser-combinators >= 1.3 && < 2, parser-combinators >= 1.3 && < 2,
@ -92,12 +93,12 @@ test-suite graphql-test
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
build-depends: build-depends:
QuickCheck ^>= 2.14.1, QuickCheck >= 2.14 && < 2.16,
base, base,
conduit, conduit,
exceptions, exceptions,
graphql, graphql,
hspec ^>= 2.10.9, hspec >= 2.10.9 && < 2.12,
hspec-expectations ^>= 0.8.2, hspec-expectations ^>= 0.8.2,
hspec-megaparsec ^>= 2.2.0, hspec-megaparsec ^>= 2.2.0,
megaparsec, megaparsec,
@ -105,4 +106,6 @@ test-suite graphql-test
unordered-containers, unordered-containers,
containers, containers,
vector vector
build-tool-depends:
hspec-discover:hspec-discover
default-language: Haskell2010 default-language: Haskell2010

View File

@ -371,8 +371,8 @@ data NonNullType
deriving Eq deriving Eq
instance Show NonNullType where instance Show NonNullType where
show (NonNullTypeNamed typeName) = '!' : Text.unpack typeName show (NonNullTypeNamed typeName) = Text.unpack $ typeName <> "!"
show (NonNullTypeList listType) = concat ["![", show listType, "]"] show (NonNullTypeList listType) = concat ["[", show listType, "]!"]
-- ** Directives -- ** Directives

View File

@ -8,28 +8,22 @@
-- | Error handling. -- | Error handling.
module Language.GraphQL.Error module Language.GraphQL.Error
( CollectErrsT ( Error(..)
, Error(..)
, Path(..) , Path(..)
, Resolution(..)
, ResolverException(..) , ResolverException(..)
, Response(..) , Response(..)
, ResponseEventStream , ResponseEventStream
, parseError , parseError
, runCollectErrs
) where ) where
import Conduit import Conduit
import Control.Exception (Exception(..)) import Control.Exception (Exception(..))
import Control.Monad.Trans.State (StateT, runStateT)
import Data.HashMap.Strict (HashMap)
import Data.Sequence (Seq(..), (|>)) import Data.Sequence (Seq(..), (|>))
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text import qualified Data.Text as Text
import Language.GraphQL.AST (Location(..), Name) import Language.GraphQL.AST (Location(..))
import Language.GraphQL.Execute.Coerce import Language.GraphQL.Execute.Coerce
import qualified Language.GraphQL.Type.Schema as Schema
import Prelude hiding (null) import Prelude hiding (null)
import Text.Megaparsec import Text.Megaparsec
( ParseErrorBundle(..) ( ParseErrorBundle(..)
@ -97,28 +91,3 @@ instance Show ResolverException where
show (ResolverException e) = show e show (ResolverException e) = show e
instance Exception ResolverException instance Exception ResolverException
-- * Deprecated
{-# DEPRECATED runCollectErrs "runCollectErrs was part of the old executor and isn't used anymore" #-}
-- | Runs the given query computation, but collects the errors into an error
-- list, which is then sent back with the data.
runCollectErrs :: (Monad m, Serialize a)
=> HashMap Name (Schema.Type m)
-> CollectErrsT m a
-> m (Response a)
runCollectErrs types' res = do
(dat, Resolution{..}) <- runStateT res
$ Resolution{ errors = Seq.empty, types = types' }
pure $ Response dat errors
{-# DEPRECATED Resolution "Resolution was part of the old executor and isn't used anymore" #-}
-- | Executor context.
data Resolution m = Resolution
{ errors :: Seq Error
, types :: HashMap Name (Schema.Type m)
}
{-# DEPRECATED CollectErrsT "CollectErrsT was part of the old executor and isn't used anymore" #-}
-- | A wrapper to pass error messages around.
type CollectErrsT m = StateT (Resolution m) m

View File

@ -556,33 +556,24 @@ coerceArgumentValues argumentDefinitions argumentValues =
$ Just inputValue $ Just inputValue
| otherwise -> throwM | otherwise -> throwM
$ InputCoercionException (Text.unpack argumentName) variableType Nothing $ InputCoercionException (Text.unpack argumentName) variableType Nothing
matchFieldValues' = matchFieldValues coerceArgumentValue matchFieldValues' = matchFieldValues coerceArgumentValue
$ Full.node <$> argumentValues $ Full.node <$> argumentValues
coerceArgumentValue inputType (Transform.Int integer) =
coerceInputLiteral inputType (Type.Int integer) coerceArgumentValue inputType transform =
coerceArgumentValue inputType (Transform.Boolean boolean) = coerceInputLiteral inputType $ extractArgumentValue transform
coerceInputLiteral inputType (Type.Boolean boolean)
coerceArgumentValue inputType (Transform.String string) = extractArgumentValue (Transform.Int integer) = Type.Int integer
coerceInputLiteral inputType (Type.String string) extractArgumentValue (Transform.Boolean boolean) = Type.Boolean boolean
coerceArgumentValue inputType (Transform.Float float) = extractArgumentValue (Transform.String string) = Type.String string
coerceInputLiteral inputType (Type.Float float) extractArgumentValue (Transform.Float float) = Type.Float float
coerceArgumentValue inputType (Transform.Enum enum) = extractArgumentValue (Transform.Enum enum) = Type.Enum enum
coerceInputLiteral inputType (Type.Enum enum) extractArgumentValue Transform.Null = Type.Null
coerceArgumentValue inputType Transform.Null extractArgumentValue (Transform.List list) =
| In.isNonNullType inputType = Nothing Type.List $ extractArgumentValue <$> list
| otherwise = coerceInputLiteral inputType Type.Null extractArgumentValue (Transform.Object object) =
coerceArgumentValue (In.ListBaseType inputType) (Transform.List list) = Type.Object $ extractArgumentValue <$> object
let coerceItem = coerceArgumentValue inputType extractArgumentValue (Transform.Variable variable) = variable
in Type.List <$> traverse coerceItem list
coerceArgumentValue (In.InputObjectBaseType inputType) (Transform.Object object)
| In.InputObjectType _ _ inputFields <- inputType =
let go = forEachField object
resultMap = HashMap.foldrWithKey go (pure mempty) inputFields
in Type.Object <$> resultMap
coerceArgumentValue _ (Transform.Variable variable) = pure variable
coerceArgumentValue _ _ = Nothing
forEachField object variableName (In.InputField _ variableType defaultValue) =
matchFieldValues coerceArgumentValue object variableName variableType defaultValue
collectFields :: Monad m collectFields :: Monad m
=> Out.ObjectType m => Out.ObjectType m

View File

@ -21,7 +21,7 @@ stripIndentation code = reverse
indent count (' ' : xs) = indent (count - 1) xs indent count (' ' : xs) = indent (count - 1) xs
indent _ xs = xs indent _ xs = xs
withoutLeadingNewlines = dropNewlines code withoutLeadingNewlines = dropNewlines code
dropNewlines = dropWhile (== '\n') dropNewlines = dropWhile $ flip any ['\n', '\r'] . (==)
spaces = length $ takeWhile (== ' ') withoutLeadingNewlines spaces = length $ takeWhile (== ' ') withoutLeadingNewlines
-- | Removes leading and trailing newlines. Indentation of the first line is -- | Removes leading and trailing newlines. Indentation of the first line is

View File

@ -18,6 +18,8 @@ module Language.GraphQL.Type.Definition
, float , float
, id , id
, int , int
, showNonNullType
, showNonNullListType
, selection , selection
, string , string
) where ) where
@ -207,3 +209,11 @@ include = handle include'
(Just (Boolean True)) -> Include directive' (Just (Boolean True)) -> Include directive'
_ -> Skip _ -> Skip
include' directive' = Continue directive' include' directive' = Continue directive'
showNonNullType :: Show a => a -> String
showNonNullType = (++ "!") . show
showNonNullListType :: Show a => a -> String
showNonNullListType listType =
let representation = show listType
in concat ["[", representation, "]!"]

View File

@ -66,10 +66,11 @@ instance Show Type where
show (NamedEnumType enumType) = show enumType show (NamedEnumType enumType) = show enumType
show (NamedInputObjectType inputObjectType) = show inputObjectType show (NamedInputObjectType inputObjectType) = show inputObjectType
show (ListType baseType) = concat ["[", show baseType, "]"] show (ListType baseType) = concat ["[", show baseType, "]"]
show (NonNullScalarType scalarType) = '!' : show scalarType show (NonNullScalarType scalarType) = Definition.showNonNullType scalarType
show (NonNullEnumType enumType) = '!' : show enumType show (NonNullEnumType enumType) = Definition.showNonNullType enumType
show (NonNullInputObjectType inputObjectType) = '!' : show inputObjectType show (NonNullInputObjectType inputObjectType) =
show (NonNullListType baseType) = concat ["![", show baseType, "]"] Definition.showNonNullType inputObjectType
show (NonNullListType baseType) = Definition.showNonNullListType baseType
-- | Field argument definition. -- | Field argument definition.
data Argument = Argument (Maybe Text) Type (Maybe Definition.Value) data Argument = Argument (Maybe Text) Type (Maybe Definition.Value)

View File

@ -115,12 +115,12 @@ instance forall a. Show (Type a) where
show (NamedInterfaceType interfaceType) = show interfaceType show (NamedInterfaceType interfaceType) = show interfaceType
show (NamedUnionType unionType) = show unionType show (NamedUnionType unionType) = show unionType
show (ListType baseType) = concat ["[", show baseType, "]"] show (ListType baseType) = concat ["[", show baseType, "]"]
show (NonNullScalarType scalarType) = '!' : show scalarType show (NonNullScalarType scalarType) = showNonNullType scalarType
show (NonNullEnumType enumType) = '!' : show enumType show (NonNullEnumType enumType) = showNonNullType enumType
show (NonNullObjectType inputObjectType) = '!' : show inputObjectType show (NonNullObjectType inputObjectType) = showNonNullType inputObjectType
show (NonNullInterfaceType interfaceType) = '!' : show interfaceType show (NonNullInterfaceType interfaceType) = showNonNullType interfaceType
show (NonNullUnionType unionType) = '!' : show unionType show (NonNullUnionType unionType) = showNonNullType unionType
show (NonNullListType baseType) = concat ["![", show baseType, "]"] show (NonNullListType baseType) = showNonNullListType baseType
-- | Matches either 'NamedScalarType' or 'NonNullScalarType'. -- | Matches either 'NamedScalarType' or 'NonNullScalarType'.
pattern ScalarBaseType :: forall m. ScalarType -> Type m pattern ScalarBaseType :: forall m. ScalarType -> Type m

View File

@ -2,11 +2,13 @@
v. 2.0. If a copy of the MPL was not distributed with this file, You can 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/. -} obtain one at https://mozilla.org/MPL/2.0/. -}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
-- | This module contains default rules defined in the GraphQL specification. -- | This module contains default rules defined in the GraphQL specification.
@ -61,6 +63,7 @@ import Data.Sequence (Seq(..), (|>))
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text import qualified Data.Text as Text
import GHC.Records (HasField(..))
import qualified Language.GraphQL.AST.Document as Full import qualified Language.GraphQL.AST.Document as Full
import qualified Language.GraphQL.Type.Definition as Definition import qualified Language.GraphQL.Type.Definition as Definition
import qualified Language.GraphQL.Type.Internal as Type import qualified Language.GraphQL.Type.Internal as Type
@ -618,6 +621,10 @@ noUndefinedVariablesRule =
, "\"." , "\"."
] ]
-- Used to find the difference between defined and used variables. The first
-- argument are variables defined in the operation, the second argument are
-- variables used in the query. It should return the difference between these
-- 2 sets.
type UsageDifference type UsageDifference
= HashMap Full.Name [Full.Location] = HashMap Full.Name [Full.Location]
-> HashMap Full.Name [Full.Location] -> HashMap Full.Name [Full.Location]
@ -664,11 +671,17 @@ variableUsageDifference difference errorMessage = OperationDefinitionRule $ \cas
= filterSelections' selections = filterSelections' selections
>>= lift . mapReaderT (<> mapDirectives directives') . pure >>= lift . mapReaderT (<> mapDirectives directives') . pure
findDirectiveVariables (Full.Directive _ arguments _) = mapArguments arguments findDirectiveVariables (Full.Directive _ arguments _) = mapArguments arguments
mapArguments = Seq.fromList . mapMaybe findArgumentVariables mapArguments = Seq.fromList . (>>= findArgumentVariables)
mapDirectives = foldMap findDirectiveVariables mapDirectives = foldMap findDirectiveVariables
findArgumentVariables (Full.Argument _ Full.Node{ node = Full.Variable value', ..} _) =
Just (value', [location]) findArgumentVariables (Full.Argument _ value _) = findNodeVariables value
findArgumentVariables _ = Nothing findNodeVariables Full.Node{ node = value, ..} = findValueVariables location value
findValueVariables location (Full.Variable value') = [(value', [location])]
findValueVariables _ (Full.List values) = values >>= findNodeVariables
findValueVariables _ (Full.Object fields) = fields
>>= findNodeVariables . getField @"value"
findValueVariables _ _ = []
makeError operationName (variableName, locations') = Error makeError operationName (variableName, locations') = Error
{ message = errorMessage operationName variableName { message = errorMessage operationName variableName
, locations = locations' , locations = locations'

View File

@ -7,6 +7,7 @@ import Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
import Test.QuickCheck (oneof, elements, listOf, resize, NonEmptyList (..)) import Test.QuickCheck (oneof, elements, listOf, resize, NonEmptyList (..))
import Test.QuickCheck.Gen (Gen (..)) import Test.QuickCheck.Gen (Gen (..))
import Data.Text (Text, pack) import Data.Text (Text, pack)
import Data.Functor ((<&>))
newtype AnyPrintableChar = AnyPrintableChar { getAnyPrintableChar :: Char } deriving (Eq, Show) newtype AnyPrintableChar = AnyPrintableChar { getAnyPrintableChar :: Char } deriving (Eq, Show)
@ -59,34 +60,36 @@ instance Arbitrary a => Arbitrary (AnyObjectField a) where
location' <- getAnyLocation <$> arbitrary location' <- getAnyLocation <$> arbitrary
pure $ AnyObjectField $ Doc.ObjectField name' value' location' pure $ AnyObjectField $ Doc.ObjectField name' value' location'
newtype AnyValue = AnyValue { getAnyValue :: Doc.Value } deriving (Eq, Show) newtype AnyValue = AnyValue { getAnyValue :: Doc.Value }
deriving (Eq, Show)
instance Arbitrary AnyValue where instance Arbitrary AnyValue
arbitrary = AnyValue <$> oneof where
[ variableGen arbitrary =
, Doc.Int <$> arbitrary let variableGen :: Gen Doc.Value
, Doc.Float <$> arbitrary variableGen = Doc.Variable . getAnyName <$> arbitrary
, Doc.String <$> (getAnyPrintableText <$> arbitrary) listGen :: Gen [Doc.Node Doc.Value]
, Doc.Boolean <$> arbitrary listGen = (resize 5 . listOf) nodeGen
, MkGen $ \_ _ -> Doc.Null nodeGen :: Gen (Doc.Node Doc.Value)
, Doc.Enum <$> (getAnyName <$> arbitrary) nodeGen = fmap getAnyNode arbitrary <&> fmap getAnyValue
, Doc.List <$> listGen objectGen :: Gen [Doc.ObjectField Doc.Value]
, Doc.Object <$> objectGen objectGen = resize 1
] $ fmap getNonEmpty arbitrary
where <&> map (fmap getAnyValue . getAnyObjectField)
variableGen :: Gen Doc.Value in AnyValue <$> oneof
variableGen = Doc.Variable <$> (getAnyName <$> arbitrary) [ variableGen
listGen :: Gen [Doc.Node Doc.Value] , Doc.Int <$> arbitrary
listGen = (resize 5 . listOf) nodeGen , Doc.Float <$> arbitrary
nodeGen = do , Doc.String . getAnyPrintableText <$> arbitrary
node' <- getAnyNode <$> (arbitrary :: Gen (AnyNode AnyValue)) , Doc.Boolean <$> arbitrary
pure (getAnyValue <$> node') , MkGen $ \_ _ -> Doc.Null
objectGen :: Gen [Doc.ObjectField Doc.Value] , Doc.Enum . getAnyName <$> arbitrary
objectGen = resize 1 $ do , Doc.List <$> listGen
list <- getNonEmpty <$> (arbitrary :: Gen (NonEmptyList (AnyObjectField AnyValue))) , Doc.Object <$> objectGen
pure $ map (fmap getAnyValue . getAnyObjectField) list ]
newtype AnyArgument a = AnyArgument { getAnyArgument :: Doc.Argument } deriving (Eq, Show) newtype AnyArgument a = AnyArgument { getAnyArgument :: Doc.Argument }
deriving (Eq, Show)
instance Arbitrary a => Arbitrary (AnyArgument a) where instance Arbitrary a => Arbitrary (AnyArgument a) where
arbitrary = do arbitrary = do

View File

@ -69,6 +69,7 @@ queryType = Out.ObjectType "Query" Nothing []
, ("throwing", ValueResolver throwingField throwingResolver) , ("throwing", ValueResolver throwingField throwingResolver)
, ("count", ValueResolver countField countResolver) , ("count", ValueResolver countField countResolver)
, ("sequence", ValueResolver sequenceField sequenceResolver) , ("sequence", ValueResolver sequenceField sequenceResolver)
, ("withInputObject", ValueResolver withInputObjectField withInputObjectResolver)
] ]
where where
philosopherField = philosopherField =
@ -89,6 +90,17 @@ queryType = Out.ObjectType "Query" Nothing []
let fieldType = Out.ListType $ Out.NonNullScalarType int let fieldType = Out.ListType $ Out.NonNullScalarType int
in Out.Field Nothing fieldType HashMap.empty in Out.Field Nothing fieldType HashMap.empty
sequenceResolver = pure intSequence sequenceResolver = pure intSequence
withInputObjectResolver = pure $ Type.Int 0
withInputObjectField =
Out.Field Nothing (Out.NonNullScalarType int) $ HashMap.fromList
[("values", In.Argument Nothing withInputObjectArgumentType Nothing)]
withInputObjectArgumentType = In.NonNullListType
$ In.NonNullInputObjectType inputObjectType
inputObjectType :: In.InputObjectType
inputObjectType = In.InputObjectType "InputObject" Nothing $
HashMap.singleton "name" $
In.InputField Nothing (In.NonNullScalarType int) Nothing
intSequence :: Value intSequence :: Value
intSequence = Type.List [Type.Int 1, Type.Int 2, Type.Int 3] intSequence = Type.List [Type.Int 1, Type.Int 2, Type.Int 3]
@ -295,7 +307,7 @@ spec =
let data'' = Object $ HashMap.singleton "philosopher" Null let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error executionErrors = pure $ Error
{ message = { message =
"Value completion error. Expected type !School, found: EXISTENTIALISM." "Value completion error. Expected type School!, found: EXISTENTIALISM."
, locations = [Location 1 17] , locations = [Location 1 17]
, path = [Segment "philosopher", Segment "school"] , path = [Segment "philosopher", Segment "school"]
} }
@ -307,7 +319,7 @@ spec =
let data'' = Object $ HashMap.singleton "philosopher" Null let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error executionErrors = pure $ Error
{ message = { message =
"Value completion error. Expected type !Interest, found: { instrument: \"piano\" }." "Value completion error. Expected type Interest!, found: { instrument: \"piano\" }."
, locations = [Location 1 17] , locations = [Location 1 17]
, path = [Segment "philosopher", Segment "interest"] , path = [Segment "philosopher", Segment "interest"]
} }
@ -319,7 +331,7 @@ spec =
let data'' = Object $ HashMap.singleton "philosopher" Null let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error executionErrors = pure $ Error
{ message { message
= "Value completion error. Expected type !Work, found:\ = "Value completion error. Expected type Work!, found:\
\ { title: \"Also sprach Zarathustra: Ein Buch f\252r Alle und Keinen\" }." \ { title: \"Also sprach Zarathustra: Ein Buch f\252r Alle und Keinen\" }."
, locations = [Location 1 17] , locations = [Location 1 17]
, path = [Segment "philosopher", Segment "majorWork"] , path = [Segment "philosopher", Segment "majorWork"]
@ -328,22 +340,10 @@ spec =
sourceQuery = "{ philosopher { majorWork { title } } }" sourceQuery = "{ philosopher { majorWork { title } } }"
in sourceQuery `shouldResolveTo` expected in sourceQuery `shouldResolveTo` expected
it "gives location information for invalid scalar arguments" $
let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error
{ message =
"Argument \"id\" has invalid type. Expected type ID, found: True."
, locations = [Location 1 15]
, path = [Segment "philosopher"]
}
expected = Response data'' executionErrors
sourceQuery = "{ philosopher(id: true) { lastName } }"
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 "philosopher" Null let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error executionErrors = pure $ Error
{ message = "Unable to coerce result to !Int." { message = "Unable to coerce result to Int!."
, locations = [Location 1 26] , locations = [Location 1 26]
, path = [Segment "philosopher", Segment "century"] , path = [Segment "philosopher", Segment "century"]
} }
@ -364,7 +364,7 @@ spec =
it "sets data to null if a root field isn't nullable" $ it "sets data to null if a root field isn't nullable" $
let executionErrors = pure $ Error let executionErrors = pure $ Error
{ message = "Unable to coerce result to !Int." { message = "Unable to coerce result to Int!."
, locations = [Location 1 3] , locations = [Location 1 3]
, path = [Segment "count"] , path = [Segment "count"]
} }
@ -375,7 +375,7 @@ spec =
it "detects nullability errors" $ it "detects nullability errors" $
let data'' = Object $ HashMap.singleton "philosopher" Null let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error executionErrors = pure $ Error
{ message = "Value completion error. Expected type !String, found: null." { message = "Value completion error. Expected type String!, found: null."
, locations = [Location 1 26] , locations = [Location 1 26]
, path = [Segment "philosopher", Segment "firstLanguage"] , path = [Segment "philosopher", Segment "firstLanguage"]
} }
@ -389,6 +389,25 @@ spec =
sourceQuery = "{ sequence }" sourceQuery = "{ sequence }"
in sourceQuery `shouldResolveTo` expected in sourceQuery `shouldResolveTo` expected
context "Arguments" $ do
it "gives location information for invalid scalar arguments" $
let data'' = Object $ HashMap.singleton "philosopher" Null
executionErrors = pure $ Error
{ message =
"Argument \"id\" has invalid type. Expected type ID, found: True."
, locations = [Location 1 15]
, path = [Segment "philosopher"]
}
expected = Response data'' executionErrors
sourceQuery = "{ philosopher(id: true) { lastName } }"
in sourceQuery `shouldResolveTo` expected
it "puts an object in a list if needed" $
let data'' = Object $ HashMap.singleton "withInputObject" $ Type.Int 0
expected = Response data'' mempty
sourceQuery = "{ withInputObject(values: { name: 0 }) }"
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"

View File

@ -29,6 +29,7 @@ queryType = ObjectType "Query" Nothing [] $ HashMap.fromList
[ ("dog", dogResolver) [ ("dog", dogResolver)
, ("cat", catResolver) , ("cat", catResolver)
, ("findDog", findDogResolver) , ("findDog", findDogResolver)
, ("findCats", findCatsResolver)
] ]
where where
dogField = Field Nothing (Out.NamedObjectType dogType) mempty dogField = Field Nothing (Out.NamedObjectType dogType) mempty
@ -39,6 +40,11 @@ queryType = ObjectType "Query" Nothing [] $ HashMap.fromList
findDogResolver = ValueResolver findDogField $ pure Null findDogResolver = ValueResolver findDogField $ pure Null
catField = Field Nothing (Out.NamedObjectType catType) mempty catField = Field Nothing (Out.NamedObjectType catType) mempty
catResolver = ValueResolver catField $ pure Null catResolver = ValueResolver catField $ pure Null
findCatsArguments = HashMap.singleton "commands"
$ In.Argument Nothing (In.NonNullListType $ In.NonNullEnumType catCommandType)
$ Just $ List []
findCatsField = Field Nothing (Out.NonNullListType $ Out.NonNullObjectType catType) findCatsArguments
findCatsResolver = ValueResolver findCatsField $ pure $ List []
catCommandType :: EnumType catCommandType :: EnumType
catCommandType = EnumType "CatCommand" Nothing $ HashMap.fromList catCommandType = EnumType "CatCommand" Nothing $ HashMap.fromList
@ -538,7 +544,7 @@ spec =
} }
in validate queryString `shouldContain` [expected] in validate queryString `shouldContain` [expected]
context "noUndefinedVariablesRule" $ context "noUndefinedVariablesRule" $ do
it "rejects undefined variables" $ it "rejects undefined variables" $
let queryString = [gql| let queryString = [gql|
query variableIsNotDefinedUsedInSingleFragment { query variableIsNotDefinedUsedInSingleFragment {
@ -560,7 +566,35 @@ spec =
} }
in validate queryString `shouldBe` [expected] in validate queryString `shouldBe` [expected]
context "noUnusedVariablesRule" $ it "gets variable location inside an input object" $
let queryString = [gql|
query {
findDog (complex: { name: $name }) {
name
}
}
|]
expected = Error
{ message = "Variable \"$name\" is not defined."
, locations = [AST.Location 2 29]
}
in validate queryString `shouldBe` [expected]
it "gets variable location inside an array" $
let queryString = [gql|
query {
findCats (commands: [JUMP, $command]) {
name
}
}
|]
expected = Error
{ message = "Variable \"$command\" is not defined."
, locations = [AST.Location 2 30]
}
in validate queryString `shouldBe` [expected]
context "noUnusedVariablesRule" $ do
it "rejects unused variables" $ it "rejects unused variables" $
let queryString = [gql| let queryString = [gql|
query variableUnused($atOtherHomes: Boolean) { query variableUnused($atOtherHomes: Boolean) {
@ -577,6 +611,16 @@ spec =
} }
in validate queryString `shouldBe` [expected] in validate queryString `shouldBe` [expected]
it "detects variables in properties of input objects" $
let queryString = [gql|
query withVar ($name: String!) {
findDog (complex: { name: $name }) {
name
}
}
|]
in validate queryString `shouldBe` []
context "uniqueInputFieldNamesRule" $ context "uniqueInputFieldNamesRule" $
it "rejects duplicate fields in input objects" $ it "rejects duplicate fields in input objects" $
let queryString = [gql| let queryString = [gql|
@ -878,7 +922,7 @@ spec =
{ message = { message =
"Variable \"$dogCommandArg\" of type \ "Variable \"$dogCommandArg\" of type \
\\"DogCommand\" used in position expecting type \ \\"DogCommand\" used in position expecting type \
\\"!DogCommand\"." \\"DogCommand!\"."
, locations = [AST.Location 1 26] , locations = [AST.Location 1 26]
} }
in validate queryString `shouldBe` [expected] in validate queryString `shouldBe` [expected]
@ -925,7 +969,7 @@ spec =
|] |]
expected = Error expected = Error
{ message = { message =
"Value 3 cannot be coerced to type \"!CatCommand\"." "Value 3 cannot be coerced to type \"CatCommand!\"."
, locations = [AST.Location 3 36] , locations = [AST.Location 3 36]
} }
in validate queryString `shouldBe` [expected] in validate queryString `shouldBe` [expected]
@ -940,7 +984,7 @@ spec =
|] |]
expected = Error expected = Error
{ message = { message =
"Value 3 cannot be coerced to type \"!String\"." "Value 3 cannot be coerced to type \"String!\"."
, locations = [AST.Location 2 28] , locations = [AST.Location 2 28]
} }
in validate queryString `shouldBe` [expected] in validate queryString `shouldBe` [expected]