diff options
| -rw-r--r-- | .gitea/workflows/build.yml | 68 | ||||
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | graphql.cabal | 9 | ||||
| -rw-r--r-- | src/Language/GraphQL/AST/Document.hs | 4 | ||||
| -rw-r--r-- | src/Language/GraphQL/Execute.hs | 41 | ||||
| -rw-r--r-- | src/Language/GraphQL/TH.hs | 2 | ||||
| -rw-r--r-- | src/Language/GraphQL/Type/Definition.hs | 10 | ||||
| -rw-r--r-- | src/Language/GraphQL/Type/In.hs | 9 | ||||
| -rw-r--r-- | src/Language/GraphQL/Type/Out.hs | 12 | ||||
| -rw-r--r-- | src/Language/GraphQL/Validate/Rules.hs | 21 | ||||
| -rw-r--r-- | tests/Language/GraphQL/ExecuteSpec.hs | 55 | ||||
| -rw-r--r-- | tests/Language/GraphQL/Validate/RulesSpec.hs | 54 |
12 files changed, 225 insertions, 68 deletions
diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..6851bbd --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,68 @@ +name: Build + +on: + push: + pull_request: + branches: [master] + +jobs: + audit: + runs-on: alpine + steps: + - name: Set up environment + shell: ash {0} + run: | + apk add --no-cache git bash curl build-base readline-dev openssl-dev zlib-dev libpq-dev gmp-dev + - name: Prepare system + run: | + curl --create-dirs --output-dir \ + ~/.ghcup/bin https://downloads.haskell.org/~ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 -o ghcup + chmod +x ~/.ghcup/bin/ghcup + ~/.ghcup/bin/ghcup install ghc 9.4.8 + ~/.ghcup/bin/ghcup install cabal 3.6.2.0 + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal update + ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal install hlint --constraint="hlint ==3.6.1" + - run: ~/.cabal/bin/hlint -- src tests + + test: + runs-on: alpine + steps: + - name: Set up environment + shell: ash {0} + run: | + apk add --no-cache git bash curl build-base readline-dev openssl-dev zlib-dev libpq-dev gmp-dev + - name: Prepare system + run: | + curl --create-dirs --output-dir \ + ~/.ghcup/bin https://downloads.haskell.org/~ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 -o ghcup + chmod +x ~/.ghcup/bin/ghcup + ~/.ghcup/bin/ghcup install ghc 9.4.8 + ~/.ghcup/bin/ghcup install cabal 3.6.2.0 + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal update + ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal build graphql-test + - run: ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal test --test-show-details=direct + + doc: + runs-on: alpine + steps: + - name: Set up environment + shell: ash {0} + run: | + apk add --no-cache git bash curl build-base readline-dev openssl-dev zlib-dev libpq-dev gmp-dev + - name: Prepare system + run: | + curl --create-dirs --output-dir \ + ~/.ghcup/bin https://downloads.haskell.org/~ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 -o ghcup + chmod +x ~/.ghcup/bin/ghcup + ~/.ghcup/bin/ghcup install ghc 9.4.8 + ~/.ghcup/bin/ghcup install cabal 3.6.2.0 + - uses: actions/checkout@v4 + - name: Install dependencies + run: ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal update + - run: ~/.ghcup/bin/ghcup run --ghc 9.4.8 --cabal 3.6.2.0 -- cabal haddock --enable-documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index b37a40b..f88ebdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [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. @@ -508,6 +515,7 @@ and this project adheres to ### Added - Data types for the GraphQL language. +[1.2.0.2]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.1...v1.2.0.2 [1.2.0.1]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.0...v1.2.0.1 [1.2.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.1.0.0...v1.2.0.0 [1.1.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.0.3.0...v1.1.0.0 diff --git a/graphql.cabal b/graphql.cabal index 72f81e4..dc9b9e3 100644 --- a/graphql.cabal +++ b/graphql.cabal @@ -1,7 +1,7 @@ cabal-version: 2.4 name: graphql -version: 1.2.0.1 +version: 1.2.0.2 synopsis: Haskell GraphQL implementation description: Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation. category: Language @@ -11,7 +11,7 @@ author: Danny Navarro <j@dannynavarro.net>, Matthías Páll Gissurarson <mpg@mpg.is>, Sólrún Halla Einarsdóttir <she@mpg.is> maintainer: belka@caraus.de -copyright: (c) 2019-2023 Eugen Wissner, +copyright: (c) 2019-2024 Eugen Wissner, (c) 2015-2017 J. Daniel Navarro license: MPL-2.0 AND BSD-3-Clause license-files: LICENSE, @@ -21,7 +21,8 @@ extra-source-files: CHANGELOG.md README.md tested-with: - GHC == 9.2.5 + GHC == 9.4.7, + GHC == 9.6.3 source-repository head type: git @@ -105,4 +106,6 @@ test-suite graphql-test unordered-containers, containers, vector + build-tool-depends: + hspec-discover:hspec-discover default-language: Haskell2010 diff --git a/src/Language/GraphQL/AST/Document.hs b/src/Language/GraphQL/AST/Document.hs index 131285d..66fc246 100644 --- a/src/Language/GraphQL/AST/Document.hs +++ b/src/Language/GraphQL/AST/Document.hs @@ -371,8 +371,8 @@ data NonNullType deriving Eq instance Show NonNullType where - show (NonNullTypeNamed typeName) = '!' : Text.unpack typeName - show (NonNullTypeList listType) = concat ["![", show listType, "]"] + show (NonNullTypeNamed typeName) = Text.unpack $ typeName <> "!" + show (NonNullTypeList listType) = concat ["[", show listType, "]!"] -- ** Directives diff --git a/src/Language/GraphQL/Execute.hs b/src/Language/GraphQL/Execute.hs index bbacdd2..265e94f 100644 --- a/src/Language/GraphQL/Execute.hs +++ b/src/Language/GraphQL/Execute.hs @@ -556,33 +556,24 @@ coerceArgumentValues argumentDefinitions argumentValues = $ Just inputValue | otherwise -> throwM $ InputCoercionException (Text.unpack argumentName) variableType Nothing + matchFieldValues' = matchFieldValues coerceArgumentValue $ Full.node <$> argumentValues - coerceArgumentValue inputType (Transform.Int integer) = - coerceInputLiteral inputType (Type.Int integer) - coerceArgumentValue inputType (Transform.Boolean boolean) = - coerceInputLiteral inputType (Type.Boolean boolean) - coerceArgumentValue inputType (Transform.String string) = - coerceInputLiteral inputType (Type.String string) - coerceArgumentValue inputType (Transform.Float float) = - coerceInputLiteral inputType (Type.Float float) - coerceArgumentValue inputType (Transform.Enum enum) = - coerceInputLiteral inputType (Type.Enum enum) - coerceArgumentValue inputType Transform.Null - | In.isNonNullType inputType = Nothing - | otherwise = coerceInputLiteral inputType Type.Null - coerceArgumentValue (In.ListBaseType inputType) (Transform.List list) = - let coerceItem = coerceArgumentValue inputType - 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 + + coerceArgumentValue inputType transform = + coerceInputLiteral inputType $ extractArgumentValue transform + + extractArgumentValue (Transform.Int integer) = Type.Int integer + extractArgumentValue (Transform.Boolean boolean) = Type.Boolean boolean + extractArgumentValue (Transform.String string) = Type.String string + extractArgumentValue (Transform.Float float) = Type.Float float + extractArgumentValue (Transform.Enum enum) = Type.Enum enum + extractArgumentValue Transform.Null = Type.Null + extractArgumentValue (Transform.List list) = + Type.List $ extractArgumentValue <$> list + extractArgumentValue (Transform.Object object) = + Type.Object $ extractArgumentValue <$> object + extractArgumentValue (Transform.Variable variable) = variable collectFields :: Monad m => Out.ObjectType m diff --git a/src/Language/GraphQL/TH.hs b/src/Language/GraphQL/TH.hs index b6bc18c..8e1fcb3 100644 --- a/src/Language/GraphQL/TH.hs +++ b/src/Language/GraphQL/TH.hs @@ -21,7 +21,7 @@ stripIndentation code = reverse indent count (' ' : xs) = indent (count - 1) xs indent _ xs = xs withoutLeadingNewlines = dropNewlines code - dropNewlines = dropWhile (== '\n') + dropNewlines = dropWhile $ flip any ['\n', '\r'] . (==) spaces = length $ takeWhile (== ' ') withoutLeadingNewlines -- | Removes leading and trailing newlines. Indentation of the first line is diff --git a/src/Language/GraphQL/Type/Definition.hs b/src/Language/GraphQL/Type/Definition.hs index 1c8876a..ad4b538 100644 --- a/src/Language/GraphQL/Type/Definition.hs +++ b/src/Language/GraphQL/Type/Definition.hs @@ -18,6 +18,8 @@ module Language.GraphQL.Type.Definition , float , id , int + , showNonNullType + , showNonNullListType , selection , string ) where @@ -207,3 +209,11 @@ include = handle include' (Just (Boolean True)) -> Include directive' _ -> Skip 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, "]!"] diff --git a/src/Language/GraphQL/Type/In.hs b/src/Language/GraphQL/Type/In.hs index 376ed6f..c777e69 100644 --- a/src/Language/GraphQL/Type/In.hs +++ b/src/Language/GraphQL/Type/In.hs @@ -66,10 +66,11 @@ instance Show Type where show (NamedEnumType enumType) = show enumType show (NamedInputObjectType inputObjectType) = show inputObjectType show (ListType baseType) = concat ["[", show baseType, "]"] - show (NonNullScalarType scalarType) = '!' : show scalarType - show (NonNullEnumType enumType) = '!' : show enumType - show (NonNullInputObjectType inputObjectType) = '!' : show inputObjectType - show (NonNullListType baseType) = concat ["![", show baseType, "]"] + show (NonNullScalarType scalarType) = Definition.showNonNullType scalarType + show (NonNullEnumType enumType) = Definition.showNonNullType enumType + show (NonNullInputObjectType inputObjectType) = + Definition.showNonNullType inputObjectType + show (NonNullListType baseType) = Definition.showNonNullListType baseType -- | Field argument definition. data Argument = Argument (Maybe Text) Type (Maybe Definition.Value) diff --git a/src/Language/GraphQL/Type/Out.hs b/src/Language/GraphQL/Type/Out.hs index 847a8a5..6854926 100644 --- a/src/Language/GraphQL/Type/Out.hs +++ b/src/Language/GraphQL/Type/Out.hs @@ -115,12 +115,12 @@ instance forall a. Show (Type a) where show (NamedInterfaceType interfaceType) = show interfaceType show (NamedUnionType unionType) = show unionType show (ListType baseType) = concat ["[", show baseType, "]"] - show (NonNullScalarType scalarType) = '!' : show scalarType - show (NonNullEnumType enumType) = '!' : show enumType - show (NonNullObjectType inputObjectType) = '!' : show inputObjectType - show (NonNullInterfaceType interfaceType) = '!' : show interfaceType - show (NonNullUnionType unionType) = '!' : show unionType - show (NonNullListType baseType) = concat ["![", show baseType, "]"] + show (NonNullScalarType scalarType) = showNonNullType scalarType + show (NonNullEnumType enumType) = showNonNullType enumType + show (NonNullObjectType inputObjectType) = showNonNullType inputObjectType + show (NonNullInterfaceType interfaceType) = showNonNullType interfaceType + show (NonNullUnionType unionType) = showNonNullType unionType + show (NonNullListType baseType) = showNonNullListType baseType -- | Matches either 'NamedScalarType' or 'NonNullScalarType'. pattern ScalarBaseType :: forall m. ScalarType -> Type m diff --git a/src/Language/GraphQL/Validate/Rules.hs b/src/Language/GraphQL/Validate/Rules.hs index 8c3156b..2d7adba 100644 --- a/src/Language/GraphQL/Validate/Rules.hs +++ b/src/Language/GraphQL/Validate/Rules.hs @@ -2,11 +2,13 @@ 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 DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} -- | 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 Data.Text (Text) import qualified Data.Text as Text +import GHC.Records (HasField(..)) import qualified Language.GraphQL.AST.Document as Full import qualified Language.GraphQL.Type.Definition as Definition 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 = HashMap Full.Name [Full.Location] -> HashMap Full.Name [Full.Location] @@ -664,11 +671,17 @@ variableUsageDifference difference errorMessage = OperationDefinitionRule $ \cas = filterSelections' selections >>= lift . mapReaderT (<> mapDirectives directives') . pure findDirectiveVariables (Full.Directive _ arguments _) = mapArguments arguments - mapArguments = Seq.fromList . mapMaybe findArgumentVariables + mapArguments = Seq.fromList . (>>= findArgumentVariables) mapDirectives = foldMap findDirectiveVariables - findArgumentVariables (Full.Argument _ Full.Node{ node = Full.Variable value', ..} _) = - Just (value', [location]) - findArgumentVariables _ = Nothing + + findArgumentVariables (Full.Argument _ value _) = findNodeVariables value + 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 { message = errorMessage operationName variableName , locations = locations' diff --git a/tests/Language/GraphQL/ExecuteSpec.hs b/tests/Language/GraphQL/ExecuteSpec.hs index 65033ae..52bd410 100644 --- a/tests/Language/GraphQL/ExecuteSpec.hs +++ b/tests/Language/GraphQL/ExecuteSpec.hs @@ -69,6 +69,7 @@ queryType = Out.ObjectType "Query" Nothing [] , ("throwing", ValueResolver throwingField throwingResolver) , ("count", ValueResolver countField countResolver) , ("sequence", ValueResolver sequenceField sequenceResolver) + , ("withInputObject", ValueResolver withInputObjectField withInputObjectResolver) ] where philosopherField = @@ -89,6 +90,17 @@ queryType = Out.ObjectType "Query" Nothing [] let fieldType = Out.ListType $ Out.NonNullScalarType int in Out.Field Nothing fieldType HashMap.empty 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 = Type.List [Type.Int 1, Type.Int 2, Type.Int 3] @@ -295,7 +307,7 @@ spec = let data'' = Object $ HashMap.singleton "philosopher" Null executionErrors = pure $ Error { message = - "Value completion error. Expected type !School, found: EXISTENTIALISM." + "Value completion error. Expected type School!, found: EXISTENTIALISM." , locations = [Location 1 17] , path = [Segment "philosopher", Segment "school"] } @@ -307,7 +319,7 @@ spec = let data'' = Object $ HashMap.singleton "philosopher" Null executionErrors = pure $ Error { message = - "Value completion error. Expected type !Interest, found: { instrument: \"piano\" }." + "Value completion error. Expected type Interest!, found: { instrument: \"piano\" }." , locations = [Location 1 17] , path = [Segment "philosopher", Segment "interest"] } @@ -319,7 +331,7 @@ spec = let data'' = Object $ HashMap.singleton "philosopher" Null executionErrors = pure $ Error { 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\" }." , locations = [Location 1 17] , path = [Segment "philosopher", Segment "majorWork"] @@ -328,22 +340,10 @@ spec = sourceQuery = "{ philosopher { majorWork { title } } }" 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" $ let data'' = Object $ HashMap.singleton "philosopher" Null executionErrors = pure $ Error - { message = "Unable to coerce result to !Int." + { message = "Unable to coerce result to Int!." , locations = [Location 1 26] , path = [Segment "philosopher", Segment "century"] } @@ -364,7 +364,7 @@ spec = it "sets data to null if a root field isn't nullable" $ let executionErrors = pure $ Error - { message = "Unable to coerce result to !Int." + { message = "Unable to coerce result to Int!." , locations = [Location 1 3] , path = [Segment "count"] } @@ -375,7 +375,7 @@ spec = it "detects nullability errors" $ let data'' = Object $ HashMap.singleton "philosopher" Null 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] , path = [Segment "philosopher", Segment "firstLanguage"] } @@ -389,6 +389,25 @@ spec = sourceQuery = "{ sequence }" 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 let namedQuery name = "query " <> name <> " { philosopher(id: \"1\") { interest } }" twoQueries = namedQuery "A" <> " " <> namedQuery "B" diff --git a/tests/Language/GraphQL/Validate/RulesSpec.hs b/tests/Language/GraphQL/Validate/RulesSpec.hs index 7a5f4cc..7bdbd86 100644 --- a/tests/Language/GraphQL/Validate/RulesSpec.hs +++ b/tests/Language/GraphQL/Validate/RulesSpec.hs @@ -29,6 +29,7 @@ queryType = ObjectType "Query" Nothing [] $ HashMap.fromList [ ("dog", dogResolver) , ("cat", catResolver) , ("findDog", findDogResolver) + , ("findCats", findCatsResolver) ] where dogField = Field Nothing (Out.NamedObjectType dogType) mempty @@ -39,6 +40,11 @@ queryType = ObjectType "Query" Nothing [] $ HashMap.fromList findDogResolver = ValueResolver findDogField $ pure Null catField = Field Nothing (Out.NamedObjectType catType) mempty 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 "CatCommand" Nothing $ HashMap.fromList @@ -538,7 +544,7 @@ spec = } in validate queryString `shouldContain` [expected] - context "noUndefinedVariablesRule" $ + context "noUndefinedVariablesRule" $ do it "rejects undefined variables" $ let queryString = [gql| query variableIsNotDefinedUsedInSingleFragment { @@ -560,7 +566,35 @@ spec = } 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" $ let queryString = [gql| query variableUnused($atOtherHomes: Boolean) { @@ -577,6 +611,16 @@ spec = } 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" $ it "rejects duplicate fields in input objects" $ let queryString = [gql| @@ -878,7 +922,7 @@ spec = { message = "Variable \"$dogCommandArg\" of type \ \\"DogCommand\" used in position expecting type \ - \\"!DogCommand\"." + \\"DogCommand!\"." , locations = [AST.Location 1 26] } in validate queryString `shouldBe` [expected] @@ -925,7 +969,7 @@ spec = |] expected = Error { message = - "Value 3 cannot be coerced to type \"!CatCommand\"." + "Value 3 cannot be coerced to type \"CatCommand!\"." , locations = [AST.Location 3 36] } in validate queryString `shouldBe` [expected] @@ -940,7 +984,7 @@ spec = |] expected = Error { message = - "Value 3 cannot be coerced to type \"!String\"." + "Value 3 cannot be coerced to type \"String!\"." , locations = [AST.Location 2 28] } in validate queryString `shouldBe` [expected] |
