packages feed

graphql 0.11.1.0 → 1.0.0.0

raw patch · 23 files changed

+1092/−398 lines, 23 filesdep +vectordep ~QuickCheckdep ~aesondep ~conduitPVP ok

version bump matches the API change (PVP)

Dependencies added: vector

Dependency ranges changed: QuickCheck, aeson, conduit, containers, exceptions, hspec, hspec-expectations, hspec-megaparsec, megaparsec, parser-combinators, raw-strings-qq, scientific, text, transformers, unordered-containers

API changes (from Hackage documentation)

+ Language.GraphQL.Execute.OrderedMap: data OrderedMap v
+ Language.GraphQL.Execute.OrderedMap: elems :: forall v. OrderedMap v -> [v]
+ Language.GraphQL.Execute.OrderedMap: empty :: forall v. OrderedMap v
+ Language.GraphQL.Execute.OrderedMap: foldlWithKey' :: forall v a. (a -> Text -> v -> a) -> a -> OrderedMap v -> a
+ Language.GraphQL.Execute.OrderedMap: insert :: Semigroup v => Text -> v -> OrderedMap v -> OrderedMap v
+ Language.GraphQL.Execute.OrderedMap: instance Data.Foldable.Foldable Language.GraphQL.Execute.OrderedMap.OrderedMap
+ Language.GraphQL.Execute.OrderedMap: instance Data.Traversable.Traversable Language.GraphQL.Execute.OrderedMap.OrderedMap
+ Language.GraphQL.Execute.OrderedMap: instance GHC.Base.Functor Language.GraphQL.Execute.OrderedMap.OrderedMap
+ Language.GraphQL.Execute.OrderedMap: instance GHC.Base.Semigroup v => GHC.Base.Monoid (Language.GraphQL.Execute.OrderedMap.OrderedMap v)
+ Language.GraphQL.Execute.OrderedMap: instance GHC.Base.Semigroup v => GHC.Base.Semigroup (Language.GraphQL.Execute.OrderedMap.OrderedMap v)
+ Language.GraphQL.Execute.OrderedMap: instance GHC.Classes.Eq v => GHC.Classes.Eq (Language.GraphQL.Execute.OrderedMap.OrderedMap v)
+ Language.GraphQL.Execute.OrderedMap: instance GHC.Show.Show v => GHC.Show.Show (Language.GraphQL.Execute.OrderedMap.OrderedMap v)
+ Language.GraphQL.Execute.OrderedMap: keys :: forall v. OrderedMap v -> [Text]
+ Language.GraphQL.Execute.OrderedMap: lookup :: forall v. Text -> OrderedMap v -> Maybe v
+ Language.GraphQL.Execute.OrderedMap: replace :: Text -> v -> OrderedMap v -> OrderedMap v
+ Language.GraphQL.Execute.OrderedMap: singleton :: forall v. Text -> v -> OrderedMap v
+ Language.GraphQL.Execute.OrderedMap: size :: forall v. OrderedMap v -> Int
+ Language.GraphQL.Execute.OrderedMap: toList :: forall v. OrderedMap v -> [(Text, v)]
+ Language.GraphQL.Execute.OrderedMap: traverseMaybe :: Applicative f => forall a. (a -> f (Maybe b)) -> OrderedMap a -> f (OrderedMap b)
+ Language.GraphQL.Type: schemaWithTypes :: forall m. Maybe Text -> ObjectType m -> Maybe (ObjectType m) -> Maybe (ObjectType m) -> [Type m] -> Directives -> Schema m
+ Language.GraphQL.Type.Schema: description :: forall m. Schema m -> Maybe Text
+ Language.GraphQL.Type.Schema: schemaWithTypes :: forall m. Maybe Text -> ObjectType m -> Maybe (ObjectType m) -> Maybe (ObjectType m) -> [Type m] -> Directives -> Schema m
- Language.GraphQL.AST.Document: ConstList :: [ConstValue] -> ConstValue
+ Language.GraphQL.AST.Document: ConstList :: [Node ConstValue] -> ConstValue
- Language.GraphQL.AST.Document: List :: [Value] -> Value
+ Language.GraphQL.AST.Document: List :: [Node Value] -> Value
- Language.GraphQL.Execute.Coerce: Object :: Map Name a -> Output a
+ Language.GraphQL.Execute.Coerce: Object :: OrderedMap a -> Output a

Files

CHANGELOG.md view
@@ -6,6 +6,36 @@ and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.0.0.0]+### Added+- `Language.GraphQL.Execute.OrderedMap` is a map data structure, that preserves+  insertion order.+- `Language.GraphQL.Schema.schemaWithTypes` constructs a complete schema,+  including an optional schema description and user-defined types not referenced+  in the schema directly (for example interface implementations).+- `Language.GraphQL.Schema.description` returns the optional schema description.+- All errors that can be associated with a location in the query contain+  location information.++### Fixed+- Parser now accepts empty lists and objects.+- Parser now accepts all directive locations.+- `valuesOfCorrectTypeRule` doesn't check lists recursively since the+  validation traverser calls it on all list items.+- `valuesOfCorrectTypeRule` doesn't check objects recursively since the+  validation traverser calls it on all object properties.+- Validation of non-nullable values inside lists.+- `executeField` shouldn't assume that a selection has only one field with a+  given name, but it should take the first field. The underlying cause is a+  wrong pattern, which (because of the laziness) is executed only if the field+  has arguments.++### Changed+- `AST.Document.Value.List` and `AST.Document.ConstValue.ConstList` contain+  location information for each list item.+- `Error`: `singleError`, `addErr` and `addErrMsg` are deprecated. They are+  internal functions used by the executor for error handling.+ ## [0.11.1.0] - 2021-02-07 ### Added - `Validate.Rules`:@@ -104,7 +134,7 @@   `locations`. - Parsing comments in the front of definitions. - Some missing labels were added to the parsers, some labels were fixed to-  refer to the AST nodes being parsed. +  refer to the AST nodes being parsed.  ### Added - `AST` reexports `AST.Parser`.@@ -413,6 +443,7 @@ ### Added - Data types for the GraphQL language. +[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 [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 [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 [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
graphql.cabal view
@@ -1,115 +1,110 @@-cabal-version:      2.2-name:               graphql-version:            0.11.1.0-license:            MPL-2.0 AND BSD-3-Clause-license-file:       LICENSE LICENSE.MPL-copyright:-    (c) 2019-2021 Eugen Wissner,-    (c) 2015-2017 J. Daniel Navarro--maintainer:         belka@caraus.de-author:-    Danny Navarro <j@dannynavarro.net>,-    Matthías Páll Gissurarson <mpg@mpg.is>,-    Sólrún Halla Einarsdóttir <she@mpg.is>--homepage:           https://www.caraus.tech/projects/pub-graphql-bug-reports:        https://www.caraus.tech/projects/pub-graphql/issues-synopsis:           Haskell GraphQL implementation-description:-    Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation.+cabal-version: 2.2 -category:           Language-build-type:         Simple+name:           graphql+version:        1.0.0.0+synopsis:       Haskell GraphQL implementation+description:    Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation.+category:       Language+homepage:       https://www.caraus.tech/projects/pub-graphql+bug-reports:    https://www.caraus.tech/projects/pub-graphql/issues+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-2021 Eugen Wissner,+                (c) 2015-2017 J. Daniel Navarro+license:        MPL-2.0 AND BSD-3-Clause+license-files:  LICENSE,+                LICENSE.MPL+build-type:     Simple extra-source-files:     CHANGELOG.md     README.md+tested-with: GHC == 8.10.4  source-repository head-    type:     git-    location: git://caraus.tech/pub/graphql.git+  type: git+  location: git://caraus.tech/pub/graphql.git  library-    exposed-modules:-        Language.GraphQL-        Language.GraphQL.AST-        Language.GraphQL.AST.DirectiveLocation-        Language.GraphQL.AST.Document-        Language.GraphQL.AST.Encoder-        Language.GraphQL.AST.Lexer-        Language.GraphQL.AST.Parser-        Language.GraphQL.Error-        Language.GraphQL.Execute-        Language.GraphQL.Execute.Coerce-        Language.GraphQL.Type-        Language.GraphQL.Type.In-        Language.GraphQL.Type.Out-        Language.GraphQL.Type.Schema-        Language.GraphQL.Validate-        Language.GraphQL.Validate.Validation-        Test.Hspec.GraphQL--    hs-source-dirs:   src-    other-modules:-        Language.GraphQL.Execute.Execution-        Language.GraphQL.Execute.Subscribe-        Language.GraphQL.Execute.Transform-        Language.GraphQL.Type.Definition-        Language.GraphQL.Type.Internal-        Language.GraphQL.Validate.Rules--    default-language: Haskell2010-    build-depends:-        aeson >=1.5.5.1,-        base >=4.7 && <5,-        conduit >=1.3.4,-        containers >=0.6.2.1,-        exceptions >=0.10.4,-        hspec-expectations >=0.8.2,-        megaparsec >=9.0.1,-        parser-combinators >=1.2.1,-        scientific >=0.3.6.2,-        text >=1.2.4.1,-        transformers >=0.5.6.2,-        unordered-containers >=0.2.13.0+  exposed-modules:+      Language.GraphQL+      Language.GraphQL.AST+      Language.GraphQL.AST.DirectiveLocation+      Language.GraphQL.AST.Document+      Language.GraphQL.AST.Encoder+      Language.GraphQL.AST.Lexer+      Language.GraphQL.AST.Parser+      Language.GraphQL.Error+      Language.GraphQL.Execute+      Language.GraphQL.Execute.Coerce+      Language.GraphQL.Execute.OrderedMap+      Language.GraphQL.Type+      Language.GraphQL.Type.In+      Language.GraphQL.Type.Out+      Language.GraphQL.Type.Schema+      Language.GraphQL.Validate+      Language.GraphQL.Validate.Validation+      Test.Hspec.GraphQL+  other-modules:+      Language.GraphQL.Execute.Execution+      Language.GraphQL.Execute.Internal+      Language.GraphQL.Execute.Subscribe+      Language.GraphQL.Execute.Transform+      Language.GraphQL.Type.Definition+      Language.GraphQL.Type.Internal+      Language.GraphQL.Validate.Rules+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      aeson >= 1.5.6 && < 1.6+    , base >= 4.7 && < 5+    , conduit >= 1.3.4 && < 1.4+    , containers >= 0.6.2 && < 0.7+    , exceptions >= 0.10.4 && < 0.11+    , hspec-expectations >= 0.8.2 && < 0.9+    , megaparsec >= 9.0.1 && < 9.1+    , parser-combinators >= 1.3.0 && < 1.4+    , scientific >= 0.3.7 && < 0.4+    , text >= 1.2.4 && < 1.3+    , transformers >= 0.5.6 && < 0.6+    , unordered-containers >= 0.2.14 && < 0.3+    , vector >= 0.12.3 && < 0.13+  default-language: Haskell2010  test-suite graphql-test-    type:             exitcode-stdio-1.0-    main-is:          Spec.hs-    hs-source-dirs:   tests-    other-modules:-        Language.GraphQL.AST.DocumentSpec-        Language.GraphQL.AST.EncoderSpec-        Language.GraphQL.AST.LexerSpec-        Language.GraphQL.AST.ParserSpec-        Language.GraphQL.ErrorSpec-        Language.GraphQL.Execute.CoerceSpec-        Language.GraphQL.ExecuteSpec-        Language.GraphQL.Type.OutSpec-        Language.GraphQL.Validate.RulesSpec-        Test.DirectiveSpec-        Test.FragmentSpec-        Test.RootOperationSpec-        Paths_graphql--    autogen-modules:  Paths_graphql-    default-language: Haskell2010-    ghc-options:      -threaded -rtsopts -with-rtsopts=-N-    build-depends:-        QuickCheck >=2.14.2,-        aeson >=1.5.5.1,-        base >=4.7 && <5,-        conduit >=1.3.4,-        containers >=0.6.2.1,-        exceptions >=0.10.4,-        graphql -any,-        hspec >=2.7.8,-        hspec-expectations >=0.8.2,-        hspec-megaparsec >=2.2.0,-        megaparsec >=9.0.1,-        parser-combinators >=1.2.1,-        raw-strings-qq >=1.1,-        scientific >=0.3.6.2,-        text >=1.2.4.1,-        transformers >=0.5.6.2,-        unordered-containers >=0.2.13.0+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Language.GraphQL.AST.DocumentSpec+      Language.GraphQL.AST.EncoderSpec+      Language.GraphQL.AST.LexerSpec+      Language.GraphQL.AST.ParserSpec+      Language.GraphQL.ErrorSpec+      Language.GraphQL.Execute.CoerceSpec+      Language.GraphQL.Execute.OrderedMapSpec+      Language.GraphQL.ExecuteSpec+      Language.GraphQL.Type.OutSpec+      Language.GraphQL.Validate.RulesSpec+      Test.DirectiveSpec+      Test.FragmentSpec+      Test.RootOperationSpec+  hs-source-dirs:+      tests+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+  build-depends:+      QuickCheck >= 2.14.1 && < 2.15+    , aeson+    , base >= 4.7 && < 5+    , conduit+    , exceptions+    , graphql+    , hspec >= 2.8.2 && < 2.9+    , hspec-megaparsec >= 2.2.0 && < 2.3+    , megaparsec+    , raw-strings-qq >= 1.1 && < 1.2+    , scientific+    , text+    , unordered-containers+  default-language: Haskell2010
src/Language/GraphQL/AST/Document.hs view
@@ -226,6 +226,12 @@  -- ** Input Values +-- | Escapes a single character according to the GraphQL escaping rules for+-- double-quoted string values.+--+-- Characters, that should be escaped, are written as escaped characters with a+-- backslash or Unicode with an \"\\u\". Other characters are returned as+-- strings. escape :: Char -> String escape char'     | char' == '\\' = "\\\\"@@ -257,7 +263,7 @@     | Boolean Bool     | Null     | Enum Name-    | List [Value]+    | List [Node Value]     | Object [ObjectField Value]     deriving Eq @@ -281,7 +287,7 @@     | ConstBoolean Bool     | ConstNull     | ConstEnum Name-    | ConstList [ConstValue]+    | ConstList [Node ConstValue]     | ConstObject [ObjectField ConstValue]     deriving Eq @@ -318,13 +324,13 @@ -- Each operation can include a list of variables: -- -- @--- query (protagonist: String = "Zarathustra") {+-- query (protagonist: String = \"Zarathustra\") { --   getAuthor(protagonist: $protagonist) -- } -- @ -- -- This query defines an optional variable @protagonist@ of type @String@,--- its default value is "Zarathustra". If no default value is defined and no+-- its default value is \"Zarathustra\". If no default value is defined and no -- value is provided, a variable can still be @null@ if its type is nullable. -- -- Variables are usually passed along with the query, but not in the query
src/Language/GraphQL/AST/Encoder.hs view
@@ -219,7 +219,7 @@ fromConstValue Full.ConstNull = Full.Null fromConstValue (Full.ConstString string) = Full.String string fromConstValue (Full.ConstEnum x) = Full.Enum x-fromConstValue (Full.ConstList x) = Full.List $ fromConstValue <$> x+fromConstValue (Full.ConstList x) = Full.List $ fmap fromConstValue <$> x fromConstValue (Full.ConstObject x) = Full.Object $ fromConstObjectField <$> x   where     fromConstObjectField Full.ObjectField{value = value', ..} =@@ -266,8 +266,8 @@             = Builder.fromLazyText (indent (indentation + 1))             <> line' <> newline <> acc -listValue :: Formatter -> [Full.Value] -> Lazy.Text-listValue formatter = bracketsCommas formatter $ value formatter+listValue :: Formatter -> [Full.Node Full.Value] -> Lazy.Text+listValue formatter = bracketsCommas formatter $ value formatter . Full.node  objectValue :: Formatter -> [Full.ObjectField Full.Value] -> Lazy.Text objectValue formatter = intercalate $ objectField formatter
src/Language/GraphQL/AST/Parser.hs view
@@ -14,11 +14,7 @@ import Data.List.NonEmpty (NonEmpty(..)) import Data.Text (Text) import qualified Language.GraphQL.AST.DirectiveLocation as Directive-import Language.GraphQL.AST.DirectiveLocation-    ( DirectiveLocation-    , ExecutableDirectiveLocation-    , TypeSystemDirectiveLocation-    )+import Language.GraphQL.AST.DirectiveLocation (DirectiveLocation) import qualified Language.GraphQL.AST.Document as Full import Language.GraphQL.AST.Lexer import Text.Megaparsec@@ -96,34 +92,28 @@     <?> "DirectiveLocations"  directiveLocation :: Parser DirectiveLocation-directiveLocation-    = Directive.ExecutableDirectiveLocation <$> executableDirectiveLocation-    <|> Directive.TypeSystemDirectiveLocation <$> typeSystemDirectiveLocation+directiveLocation = e (Directive.Query <$ symbol "QUERY")+    <|> e (Directive.Mutation <$ symbol "MUTATION")+    <|> e (Directive.Subscription <$ symbol "SUBSCRIPTION")+    <|> t (Directive.FieldDefinition <$ symbol "FIELD_DEFINITION")+    <|> e (Directive.Field <$ symbol "FIELD")+    <|> e (Directive.FragmentDefinition <$ "FRAGMENT_DEFINITION")+    <|> e (Directive.FragmentSpread <$ "FRAGMENT_SPREAD")+    <|> e (Directive.InlineFragment <$ "INLINE_FRAGMENT")+    <|> t (Directive.Schema <$ symbol "SCHEMA")+    <|> t (Directive.Scalar <$ symbol "SCALAR")+    <|> t (Directive.Object <$ symbol "OBJECT")+    <|> t (Directive.ArgumentDefinition <$ symbol "ARGUMENT_DEFINITION")+    <|> t (Directive.Interface <$ symbol "INTERFACE")+    <|> t (Directive.Union <$ symbol "UNION")+    <|> t (Directive.EnumValue <$ symbol "ENUM_VALUE")+    <|> t (Directive.Enum <$ symbol "ENUM")+    <|> t (Directive.InputObject <$ symbol "INPUT_OBJECT")+    <|> t (Directive.InputFieldDefinition <$ symbol "INPUT_FIELD_DEFINITION")     <?> "DirectiveLocation"--executableDirectiveLocation :: Parser ExecutableDirectiveLocation-executableDirectiveLocation = Directive.Query <$ symbol "QUERY"-    <|> Directive.Mutation <$ symbol "MUTATION"-    <|> Directive.Subscription <$ symbol "SUBSCRIPTION"-    <|> Directive.Field <$ symbol "FIELD"-    <|> Directive.FragmentDefinition <$ "FRAGMENT_DEFINITION"-    <|> Directive.FragmentSpread <$ "FRAGMENT_SPREAD"-    <|> Directive.InlineFragment <$ "INLINE_FRAGMENT"-    <?> "ExecutableDirectiveLocation"--typeSystemDirectiveLocation :: Parser TypeSystemDirectiveLocation-typeSystemDirectiveLocation = Directive.Schema <$ symbol "SCHEMA"-    <|> Directive.Scalar <$ symbol "SCALAR"-    <|> Directive.Object <$ symbol "OBJECT"-    <|> Directive.FieldDefinition <$ symbol "FIELD_DEFINITION"-    <|> Directive.ArgumentDefinition <$ symbol "ARGUMENT_DEFINITION"-    <|> Directive.Interface <$ symbol "INTERFACE"-    <|> Directive.Union <$ symbol "UNION"-    <|> Directive.Enum <$ symbol "ENUM"-    <|> Directive.EnumValue <$ symbol "ENUM_VALUE"-    <|> Directive.InputObject <$ symbol "INPUT_OBJECT"-    <|> Directive.InputFieldDefinition <$ symbol "INPUT_FIELD_DEFINITION"-    <?> "TypeSystemDirectiveLocation"+  where+    e = fmap Directive.ExecutableDirectiveLocation+    t = fmap Directive.TypeSystemDirectiveLocation  typeDefinition :: Full.Description -> Parser Full.TypeDefinition typeDefinition description' = scalarTypeDefinition description'@@ -460,7 +450,7 @@     <|> Full.Null <$  nullValue     <|> Full.String <$> stringValue     <|> Full.Enum <$> try enumValue-    <|> Full.List <$> brackets (some value)+    <|> Full.List <$> brackets (some $ valueNode value)     <|> Full.Object <$> braces (some $ objectField $ valueNode value)     <?> "Value" @@ -471,8 +461,8 @@     <|> Full.ConstNull <$ nullValue     <|> Full.ConstString <$> stringValue     <|> Full.ConstEnum <$> try enumValue-    <|> Full.ConstList <$> brackets (some constValue)-    <|> Full.ConstObject <$> braces (some $ objectField $ valueNode constValue)+    <|> Full.ConstList <$> brackets (many $ valueNode constValue)+    <|> Full.ConstObject <$> braces (many $ objectField $ valueNode constValue)     <?> "Value"  booleanValue :: Parser Bool
src/Language/GraphQL/Error.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}  -- | Error handling.@@ -70,21 +69,25 @@ type CollectErrsT m = StateT (Resolution m) m  -- | Adds an error to the list of errors.+{-# DEPRECATED #-} addErr :: Monad m => Error -> CollectErrsT m () addErr v = modify appender   where     appender :: Monad m => Resolution m -> Resolution m     appender resolution@Resolution{..} = resolution{ errors = errors |> v } +{-# DEPRECATED #-} makeErrorMessage :: Text -> Error makeErrorMessage s = Error s [] []  -- | Constructs a response object containing only the error with the given -- message.+{-# DEPRECATED #-} singleError :: Serialize a => Text -> Response a-singleError message = Response null $ Seq.singleton $ makeErrorMessage message+singleError message = Response null $ Seq.singleton $ Error message [] []  -- | Convenience function for just wrapping an error message.+{-# DEPRECATED #-} addErrMsg :: (Monad m, Serialize a) => Text -> CollectErrsT m a addErrMsg errorMessage = (addErr . makeErrorMessage) errorMessage >> pure null 
src/Language/GraphQL/Execute.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ExplicitForAll #-}  -- | This module provides functions to execute a @GraphQL@ request. module Language.GraphQL.Execute@@ -10,15 +10,22 @@ import Data.HashMap.Strict (HashMap) import Data.Sequence (Seq(..)) import Data.Text (Text)-import Language.GraphQL.AST.Document (Document, Name)+import qualified Language.GraphQL.AST.Document as Full import Language.GraphQL.Execute.Coerce import Language.GraphQL.Execute.Execution+import Language.GraphQL.Execute.Internal import qualified Language.GraphQL.Execute.Transform as Transform import qualified Language.GraphQL.Execute.Subscribe as Subscribe import Language.GraphQL.Error+    ( Error+    , ResponseEventStream+    , Response(..)+    , runCollectErrs+    ) import qualified Language.GraphQL.Type.Definition as Definition import qualified Language.GraphQL.Type.Out as Out import Language.GraphQL.Type.Schema+import Prelude hiding (null)  -- | The substitution is applied to the document, and the resolvers are applied -- to the resulting fields. The operation name can be used if the document@@ -29,35 +36,36 @@ execute :: (MonadCatch m, VariableValue a, Serialize b)     => Schema m -- ^ Resolvers.     -> Maybe Text -- ^ Operation name.-    -> HashMap Name a -- ^ Variable substitution function.-    -> Document -- @GraphQL@ document.+    -> HashMap Full.Name a -- ^ Variable substitution function.+    -> Full.Document -- @GraphQL@ document.     -> m (Either (ResponseEventStream m b) (Response b))-execute schema' operationName subs document =-    case Transform.document schema' operationName subs document of-        Left queryError -> pure-            $ Right-            $ singleError-            $ Transform.queryError queryError-        Right transformed -> executeRequest transformed+execute schema' operationName subs document+    = either (pure . rightErrorResponse . singleError [] . show) executeRequest+    $ Transform.document schema' operationName subs document  executeRequest :: (MonadCatch m, Serialize a)     => Transform.Document m     -> m (Either (ResponseEventStream m a) (Response a)) executeRequest (Transform.Document types' rootObjectType operation)-    | (Transform.Query _ fields) <- operation =-        Right <$> executeOperation types' rootObjectType fields-    | (Transform.Mutation _ fields) <- operation =-        Right <$> executeOperation types' rootObjectType fields-    | (Transform.Subscription _ fields) <- operation-        = either (Right . singleError) Left-        <$> Subscribe.subscribe types' rootObjectType fields+    | (Transform.Query _ fields objectLocation) <- operation =+        Right <$> executeOperation types' rootObjectType objectLocation fields+    | (Transform.Mutation _ fields objectLocation) <- operation =+        Right <$> executeOperation types' rootObjectType objectLocation fields+    | (Transform.Subscription _ fields objectLocation) <- operation+        = either rightErrorResponse Left+        <$> Subscribe.subscribe types' rootObjectType objectLocation fields  -- This is actually executeMutation, but we don't distinguish between queries -- and mutations yet. executeOperation :: (MonadCatch m, Serialize a)-    => HashMap Name (Type m)+    => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)     -> m (Response a)-executeOperation types' objectType fields =-    runCollectErrs types' $ executeSelectionSet Definition.Null objectType fields+executeOperation types' objectType objectLocation fields+    = runCollectErrs types'+    $ executeSelectionSet Definition.Null objectType objectLocation fields++rightErrorResponse :: Serialize b => forall a. Error -> Either a (Response b)+rightErrorResponse = Right . Response null . pure
src/Language/GraphQL/Execute/Coerce.hs view
@@ -19,7 +19,6 @@ import Data.Int (Int32) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap-import Data.Map.Strict (Map) import Data.String (IsString(..)) import Data.Text (Text) import qualified Data.Text.Lazy as Text.Lazy@@ -27,6 +26,8 @@ import qualified Data.Text.Lazy.Builder.Int as Text.Builder import Data.Scientific (toBoundedInteger, toRealFloat) import Language.GraphQL.AST (Name)+import Language.GraphQL.Execute.OrderedMap (OrderedMap)+import qualified Language.GraphQL.Execute.OrderedMap as OrderedMap import qualified Language.GraphQL.Type as Type import qualified Language.GraphQL.Type.In as In import qualified Language.GraphQL.Type.Out as Out@@ -209,7 +210,7 @@     | Boolean Bool     | Enum Name     | List [a]-    | Object (Map Name a)+    | Object (OrderedMap a)     deriving (Eq, Show)  instance forall a. IsString (Output a) where@@ -229,6 +230,9 @@         , 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.toJSON object+    serialize _ (Object object) = Just+        $ Aeson.object+        $ OrderedMap.toList+        $ Aeson.toJSON <$> object     serialize _ _ = Nothing     null = Aeson.Null
src/Language/GraphQL/Execute/Execution.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ViewPatterns #-} @@ -13,16 +14,18 @@ import Control.Monad.Trans.Reader (runReaderT) import Control.Monad.Trans.State (gets) import Data.List.NonEmpty (NonEmpty(..))-import Data.Map.Strict (Map)+import qualified Data.List.NonEmpty as NonEmpty import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap-import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) import Data.Sequence (Seq(..)) import qualified Data.Text as Text-import Language.GraphQL.AST (Name)+import qualified Language.GraphQL.AST as Full import Language.GraphQL.Error import Language.GraphQL.Execute.Coerce+import Language.GraphQL.Execute.Internal+import Language.GraphQL.Execute.OrderedMap (OrderedMap)+import qualified Language.GraphQL.Execute.OrderedMap as OrderedMap import qualified Language.GraphQL.Execute.Transform as Transform import qualified Language.GraphQL.Type as Type import qualified Language.GraphQL.Type.In as In@@ -34,15 +37,17 @@     => Type.Value     -> Type.Subs     -> Type.Resolve m+    -> Full.Location     -> CollectErrsT m Type.Value-resolveFieldValue result args resolver =+resolveFieldValue result args resolver location' =     catch (lift $ runReaderT resolver context) handleFieldError   where     handleFieldError :: MonadCatch m         => ResolverException         -> CollectErrsT m Type.Value-    handleFieldError e =-        addErr (Error (Text.pack $ displayException e) [] []) >> pure Type.Null+    handleFieldError e+        = addError Type.Null+        $ Error (Text.pack $ displayException e) [location'] []     context = Type.Context         { Type.arguments = Type.Arguments args         , Type.values = result@@ -51,21 +56,21 @@ collectFields :: Monad m     => Out.ObjectType m     -> Seq (Transform.Selection m)-    -> Map Name (NonEmpty (Transform.Field m))-collectFields objectType = foldl forEach Map.empty+    -> OrderedMap (NonEmpty (Transform.Field m))+collectFields objectType = foldl forEach OrderedMap.empty   where     forEach groupedFields (Transform.SelectionField field) =         let responseKey = aliasOrName field-         in Map.insertWith (<>) responseKey (field :| []) groupedFields+         in OrderedMap.insert responseKey (field :| []) groupedFields     forEach groupedFields (Transform.SelectionFragment selectionFragment)         | Transform.Fragment fragmentType fragmentSelectionSet <- selectionFragment         , Internal.doesFragmentTypeApply fragmentType objectType =             let fragmentGroupedFieldSet = collectFields objectType fragmentSelectionSet-             in Map.unionWith (<>) groupedFields fragmentGroupedFieldSet+             in groupedFields <> fragmentGroupedFieldSet         | otherwise = groupedFields -aliasOrName :: forall m. Transform.Field m -> Name-aliasOrName (Transform.Field alias name _ _) = fromMaybe name alias+aliasOrName :: forall m. Transform.Field m -> Full.Name+aliasOrName (Transform.Field alias name _ _ _) = fromMaybe name alias  resolveAbstractType :: Monad m     => Internal.AbstractType m@@ -95,11 +100,15 @@   where     executeField' fieldDefinition resolver = do         let Out.Field _ fieldType argumentDefinitions = fieldDefinition-        let (Transform.Field _ _ arguments' _ :| []) = fields+        let Transform.Field _ _ arguments' _ location' = NonEmpty.head fields         case coerceArgumentValues argumentDefinitions arguments' of-            Nothing -> addErrMsg "Argument coercing failed."-            Just argumentValues -> do-                answer <- resolveFieldValue prev argumentValues resolver+            Left [] ->+                let errorMessage = "Not all required arguments are specified."+                 in addError null $ Error errorMessage [location'] []+            Left errorLocations -> addError null+                $ Error "Argument coercing failed." errorLocations []+            Right argumentValues -> do+                answer <- resolveFieldValue prev argumentValues resolver location'                 completeValue fieldType fields answer  completeValue :: (MonadCatch m, Serialize a)@@ -110,55 +119,67 @@ completeValue (Out.isNonNullType -> False) _ Type.Null = pure null completeValue outputType@(Out.ListBaseType listType) fields (Type.List list)     = traverse (completeValue listType fields) list-    >>= coerceResult outputType . List-completeValue outputType@(Out.ScalarBaseType _) _ (Type.Int int) =-    coerceResult outputType $ Int int-completeValue outputType@(Out.ScalarBaseType _) _ (Type.Boolean boolean) =-    coerceResult outputType $ Boolean boolean-completeValue outputType@(Out.ScalarBaseType _) _ (Type.Float float) =-    coerceResult outputType $ Float float-completeValue outputType@(Out.ScalarBaseType _) _ (Type.String string) =-    coerceResult outputType $ String string-completeValue outputType@(Out.EnumBaseType enumType) _ (Type.Enum enum) =+    >>= coerceResult outputType (firstFieldLocation fields) . List+completeValue outputType@(Out.ScalarBaseType _) fields (Type.Int int) =+    coerceResult outputType (firstFieldLocation fields) $ Int int+completeValue outputType@(Out.ScalarBaseType _) fields (Type.Boolean boolean) =+    coerceResult outputType (firstFieldLocation fields) $ Boolean boolean+completeValue outputType@(Out.ScalarBaseType _) fields (Type.Float float) =+    coerceResult outputType (firstFieldLocation fields) $ Float float+completeValue outputType@(Out.ScalarBaseType _) fields (Type.String string) =+    coerceResult outputType (firstFieldLocation fields) $ String string+completeValue outputType@(Out.EnumBaseType enumType) fields (Type.Enum enum) =     let Type.EnumType _ _ enumMembers = enumType+        location = firstFieldLocation fields      in if HashMap.member enum enumMembers-        then coerceResult outputType $ Enum enum-        else addErrMsg "Enum value completion failed."-completeValue (Out.ObjectBaseType objectType) fields result =-    executeSelectionSet result objectType $ mergeSelectionSets fields+        then coerceResult outputType location $ Enum enum+        else addError null $ Error "Enum value completion failed." [location] []+completeValue (Out.ObjectBaseType objectType) fields result+    = executeSelectionSet result objectType (firstFieldLocation fields)+    $ mergeSelectionSets fields completeValue (Out.InterfaceBaseType interfaceType) fields result     | Type.Object objectMap <- result = do         let abstractType = Internal.AbstractInterfaceType interfaceType+        let location = firstFieldLocation fields         concreteType <- resolveAbstractType abstractType objectMap         case concreteType of-            Just objectType -> executeSelectionSet result objectType+            Just objectType -> executeSelectionSet result objectType location                 $ mergeSelectionSets fields-            Nothing -> addErrMsg "Interface value completion failed."+            Nothing -> addError null+                $ Error "Interface value completion failed." [location] [] completeValue (Out.UnionBaseType unionType) fields result     | Type.Object objectMap <- result = do         let abstractType = Internal.AbstractUnionType unionType+        let  location = firstFieldLocation fields         concreteType <- resolveAbstractType abstractType objectMap         case concreteType of             Just objectType -> executeSelectionSet result objectType-                $ mergeSelectionSets fields-            Nothing -> addErrMsg "Union value completion failed."-completeValue _ _ _ = addErrMsg "Value completion failed."+                location $ mergeSelectionSets fields+            Nothing -> addError null+                $ Error "Union value completion failed." [location] []+completeValue _ (Transform.Field _ _ _ _ location :| _) _ =+    addError null $ Error "Value completion failed." [location] []  mergeSelectionSets :: MonadCatch m     => NonEmpty (Transform.Field m)     -> Seq (Transform.Selection m) mergeSelectionSets = foldr forEach mempty   where-    forEach (Transform.Field _ _ _ fieldSelectionSet) selectionSet =+    forEach (Transform.Field _ _ _ fieldSelectionSet _) selectionSet =         selectionSet <> fieldSelectionSet +firstFieldLocation :: MonadCatch m => NonEmpty (Transform.Field m) -> Full.Location+firstFieldLocation (Transform.Field _ _ _ _ fieldLocation :| _) = fieldLocation+ coerceResult :: (MonadCatch m, Serialize a)     => Out.Type m+    -> Full.Location     -> Output a     -> CollectErrsT m a-coerceResult outputType result+coerceResult outputType parentLocation result     | Just serialized <- serialize outputType result = pure serialized-    | otherwise = addErrMsg "Result coercion failed."+    | otherwise = addError null+        $ Error "Result coercion failed." [parentLocation] []  -- | Takes an 'Out.ObjectType' and a list of 'Transform.Selection's and applies -- each field to each 'Transform.Selection'. Resolves into a value containing@@ -166,29 +187,45 @@ executeSelectionSet :: (MonadCatch m, Serialize a)     => Type.Value     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)     -> CollectErrsT m a-executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do+executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) objectLocation selectionSet = do     let fields = collectFields objectType selectionSet-    resolvedValues <- Map.traverseMaybeWithKey forEach fields-    coerceResult (Out.NonNullObjectType objectType) $ Object resolvedValues+    resolvedValues <- OrderedMap.traverseMaybe forEach fields+    coerceResult (Out.NonNullObjectType objectType) objectLocation+        $ Object resolvedValues   where-    forEach _ fields@(field :| _) =-        let Transform.Field _ name _ _ = field+    forEach fields@(field :| _) =+        let Transform.Field _ name _ _ _ = field          in traverse (tryResolver fields) $ lookupResolver name     lookupResolver = flip HashMap.lookup resolvers     tryResolver fields resolver =         executeField resolver result fields >>= lift . pure  coerceArgumentValues-    :: HashMap Name In.Argument-    -> HashMap Name Transform.Input-    -> Maybe Type.Subs-coerceArgumentValues argumentDefinitions argumentValues =+    :: HashMap Full.Name In.Argument+    -> HashMap Full.Name (Full.Node Transform.Input)+    -> Either [Full.Location] Type.Subs+coerceArgumentValues argumentDefinitions argumentNodes =     HashMap.foldrWithKey forEach (pure mempty) argumentDefinitions   where-    forEach variableName (In.Argument _ variableType defaultValue) =-        matchFieldValues coerceArgumentValue argumentValues variableName variableType defaultValue+    forEach argumentName (In.Argument _ variableType defaultValue) = \case+        Right resultMap+            | Just matchedValues+                <- matchFieldValues' argumentName variableType defaultValue $ Just resultMap+                -> Right matchedValues+            | otherwise -> Left $ generateError argumentName []+        Left errorLocations+            | Just _+                <- matchFieldValues' argumentName variableType defaultValue $ pure mempty+                -> Left errorLocations+            | otherwise -> Left $ generateError argumentName errorLocations+    generateError argumentName errorLocations =+        case HashMap.lookup argumentName argumentNodes of+            Just (Full.Node _ errorLocation) -> [errorLocation]+            Nothing -> errorLocations+    matchFieldValues' = matchFieldValues coerceArgumentValue (Full.node <$> argumentNodes)     coerceArgumentValue inputType (Transform.Int integer) =         coerceInputLiteral inputType (Type.Int integer)     coerceArgumentValue inputType (Transform.Boolean boolean) =
+ src/Language/GraphQL/Execute/Internal.hs view
@@ -0,0 +1,31 @@+{- 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 DuplicateRecordFields #-}+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE NamedFieldPuns #-}++module Language.GraphQL.Execute.Internal+    ( addError+    , singleError+    ) where++import Control.Monad.Trans.State (modify)+import Control.Monad.Catch (MonadCatch)+import Data.Sequence ((|>))+import qualified Data.Text as Text+import qualified Language.GraphQL.AST as Full+import Language.GraphQL.Error (CollectErrsT, Error(..), Resolution(..))+import Prelude hiding (null)++addError :: MonadCatch m => forall a. a -> Error -> CollectErrsT m a+addError returnValue error' = modify appender >> pure returnValue+  where+   appender :: Resolution m -> Resolution m+   appender resolution@Resolution{ errors } = resolution+       { errors = errors |> error'+       }++singleError :: [Full.Location] -> String -> Error+singleError errorLocations message = Error (Text.pack message) errorLocations []
+ src/Language/GraphQL/Execute/OrderedMap.hs view
@@ -0,0 +1,148 @@+{- 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 ExplicitForAll #-}++-- | This module contains a map data structure, that preserves insertion order.+-- Some definitions conflict with functions from prelude, so this module should+-- probably be imported qualified.+module Language.GraphQL.Execute.OrderedMap+    ( OrderedMap+    , elems+    , empty+    , insert+    , foldlWithKey'+    , keys+    , lookup+    , replace+    , singleton+    , size+    , toList+    , traverseMaybe+    ) where++import qualified Data.Foldable as Foldable+import Data.HashMap.Strict (HashMap, (!))+import qualified Data.HashMap.Strict as HashMap+import Data.Text (Text)+import Data.Vector (Vector)+import qualified Data.Vector as Vector+import Prelude hiding (filter, lookup)++-- | This map associates values with the given text keys. Insertion order is+-- preserved. When inserting a value with a key, that is already available in+-- the map, the existing value isn't overridden, but combined with the new value+-- using its 'Semigroup' instance.+--+-- Internally this map uses an array with keys to preserve the order and an+-- unorded map with key-value pairs.+data OrderedMap v = OrderedMap (Vector Text) (HashMap Text v)+    deriving (Eq)++instance Functor OrderedMap where+    fmap f (OrderedMap vector hashMap) = OrderedMap vector $ fmap f hashMap++instance Foldable OrderedMap where+    foldr f = foldrWithKey $ const f+    null (OrderedMap vector _) = Vector.null vector++instance Semigroup v => Semigroup (OrderedMap v) where+    (<>) = foldlWithKey'+        $ \accumulator key value -> insert key value accumulator++instance Semigroup v => Monoid (OrderedMap v) where+    mempty = empty++instance Traversable OrderedMap where+    traverse f (OrderedMap vector hashMap) = OrderedMap vector+        <$> traverse f hashMap++instance Show v => Show (OrderedMap v) where+    showsPrec precedence map' = showParen (precedence > 10)+        $ showString "fromList " . shows (toList map')++-- * Construction++-- | Constructs a map with a single element.+singleton :: forall v. Text -> v -> OrderedMap v+singleton key value = OrderedMap (Vector.singleton key)+    $ HashMap.singleton key value++-- | Constructs an empty map.+empty :: forall v. OrderedMap v+empty = OrderedMap mempty mempty++-- * Traversal++-- | Reduces this map by applying a binary operator from right to left to all+-- elements, using the given starting value.+foldrWithKey :: forall v a. (Text -> v -> a -> a) -> a -> OrderedMap v -> a+foldrWithKey f initial (OrderedMap vector hashMap) = foldr go initial vector+  where+    go key = f key (hashMap ! key)++-- | Reduces this map by applying a binary operator from left to right to all+-- elements, using the given starting value.+foldlWithKey' :: forall v a. (a -> Text -> v -> a) -> a -> OrderedMap v -> a+foldlWithKey' f initial (OrderedMap vector hashMap) =+    Vector.foldl' go initial vector+  where+    go accumulator key = f accumulator key (hashMap ! key)++-- | Traverse over the elements and collect the 'Just' results.+traverseMaybe+    :: Applicative f+    => forall a+    . (a -> f (Maybe b))+    -> OrderedMap a+    -> f (OrderedMap b)+traverseMaybe f orderedMap = foldlWithKey' filter empty+    <$> traverse f orderedMap+  where+    filter accumulator key (Just value) = replace key value accumulator+    filter accumulator _ Nothing = accumulator++-- * Lists++-- | Converts this map to the list of key-value pairs.+toList :: forall v. OrderedMap v -> [(Text, v)]+toList = foldrWithKey ((.) (:) . (,)) []++-- | Returns a list with all keys in this map.+keys :: forall v. OrderedMap v -> [Text]+keys (OrderedMap vector _) = Foldable.toList vector++-- | Returns a list with all elements in this map.+elems :: forall v. OrderedMap v -> [v]+elems = fmap snd . toList++-- * Basic interface++-- | Associates the specified value with the specified key in this map. If this+-- map previously contained a mapping for the key, the existing and new values+-- are combined.+insert :: Semigroup v => Text -> v -> OrderedMap v -> OrderedMap v+insert key value (OrderedMap vector hashMap)+    | Just available <- HashMap.lookup key hashMap = OrderedMap vector+        $ HashMap.insert key (available <> value) hashMap+    | otherwise = OrderedMap (Vector.snoc vector key)+        $ HashMap.insert key value hashMap++-- | Associates the specified value with the specified key in this map. If this+-- map previously contained a mapping for the key, the existing value is+-- replaced by the new one.+replace :: Text -> v -> OrderedMap v -> OrderedMap v+replace key value (OrderedMap vector hashMap)+    | HashMap.member key hashMap = OrderedMap vector+        $ HashMap.insert key value hashMap+    | otherwise = OrderedMap (Vector.snoc vector key)+        $ HashMap.insert key value hashMap++-- | Gives the size of this map, i.e. number of elements in it.+size :: forall v. OrderedMap v -> Int+size (OrderedMap vector _) = Vector.length vector++-- | Looks up a value in this map by key.+lookup :: forall v. Text -> OrderedMap v -> Maybe v+lookup key (OrderedMap _ hashMap) = HashMap.lookup key hashMap
src/Language/GraphQL/Execute/Subscribe.hs view
@@ -9,62 +9,78 @@     ) where  import Conduit+import Control.Arrow (left) import Control.Monad.Catch (Exception(..), MonadCatch(..)) import Control.Monad.Trans.Reader (ReaderT(..), runReaderT) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap-import qualified Data.Map.Strict as Map import qualified Data.List.NonEmpty as NonEmpty import Data.Sequence (Seq(..))-import Data.Text (Text)-import qualified Data.Text as Text-import Language.GraphQL.AST (Name)+import qualified Language.GraphQL.AST as Full import Language.GraphQL.Execute.Coerce import Language.GraphQL.Execute.Execution+import Language.GraphQL.Execute.Internal+import qualified Language.GraphQL.Execute.OrderedMap as OrderedMap import qualified Language.GraphQL.Execute.Transform as Transform import Language.GraphQL.Error+    ( Error(..)+    , ResolverException+    , Response+    , ResponseEventStream+    , runCollectErrs+    ) import qualified Language.GraphQL.Type.Definition as Definition import qualified Language.GraphQL.Type as Type import qualified Language.GraphQL.Type.Out as Out import Language.GraphQL.Type.Schema --- This is actually executeMutation, but we don't distinguish between queries--- and mutations yet. subscribe :: (MonadCatch m, Serialize a)-    => HashMap Name (Type m)+    => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)-    -> m (Either Text (ResponseEventStream m a))-subscribe types' objectType fields = do-    sourceStream <- createSourceEventStream types' objectType fields-    traverse (mapSourceToResponseEvent types' objectType fields) sourceStream+    -> m (Either Error (ResponseEventStream m a))+subscribe types' objectType objectLocation fields = do+    sourceStream <-+        createSourceEventStream types' objectType objectLocation fields+    let traverser =+            mapSourceToResponseEvent types' objectType objectLocation fields+    traverse traverser sourceStream  mapSourceToResponseEvent :: (MonadCatch m, Serialize a)-    => HashMap Name (Type m)+    => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)     -> Out.SourceEventStream m     -> m (ResponseEventStream m a)-mapSourceToResponseEvent types' subscriptionType fields sourceStream = pure+mapSourceToResponseEvent types' subscriptionType objectLocation fields sourceStream+    = pure     $ sourceStream-    .| mapMC (executeSubscriptionEvent types' subscriptionType fields)+    .| mapMC (executeSubscriptionEvent types' subscriptionType objectLocation fields)  createSourceEventStream :: MonadCatch m-    => HashMap Name (Type m)+    => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)-    -> m (Either Text (Out.SourceEventStream m))-createSourceEventStream _types subscriptionType@(Out.ObjectType _ _ _ fieldTypes) fields-    | [fieldGroup] <- Map.elems groupedFieldSet-    , Transform.Field _ fieldName arguments' _ <- NonEmpty.head fieldGroup+    -> m (Either Error (Out.SourceEventStream m))+createSourceEventStream _types subscriptionType objectLocation fields+    | [fieldGroup] <- OrderedMap.elems groupedFieldSet+    , Transform.Field _ fieldName arguments' _ errorLocation <- NonEmpty.head fieldGroup+    , Out.ObjectType _ _ _ fieldTypes <- subscriptionType     , resolverT <- fieldTypes HashMap.! fieldName     , Out.EventStreamResolver fieldDefinition _ resolver <- resolverT     , Out.Field _ _fieldType argumentDefinitions <- fieldDefinition =         case coerceArgumentValues argumentDefinitions arguments' of-            Nothing -> pure $ Left "Argument coercion failed."-            Just argumentValues ->-                resolveFieldEventStream Type.Null argumentValues resolver-    | otherwise = pure $ Left "Subscription contains more than one field."+            Left _ -> pure+                $ Left+                $ Error "Argument coercion failed." [errorLocation] []+            Right  argumentValues -> left (singleError [errorLocation])+                <$> resolveFieldEventStream Type.Null argumentValues resolver+    | otherwise = pure+        $ Left+        $ Error "Subscription contains more than one field." [objectLocation] []   where     groupedFieldSet = collectFields subscriptionType fields @@ -72,26 +88,26 @@     => Type.Value     -> Type.Subs     -> Out.Subscribe m-    -> m (Either Text (Out.SourceEventStream m))+    -> m (Either String (Out.SourceEventStream m)) resolveFieldEventStream result args resolver =     catch (Right <$> runReaderT resolver context) handleEventStreamError   where     handleEventStreamError :: MonadCatch m         => ResolverException-        -> m (Either Text (Out.SourceEventStream m))-    handleEventStreamError = pure . Left . Text.pack . displayException+        -> m (Either String (Out.SourceEventStream m))+    handleEventStreamError = pure . Left . displayException     context = Type.Context         { Type.arguments = Type.Arguments args         , Type.values = result         } --- This is actually executeMutation, but we don't distinguish between queries--- and mutations yet. executeSubscriptionEvent :: (MonadCatch m, Serialize a)-    => HashMap Name (Type m)+    => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Full.Location     -> Seq (Transform.Selection m)     -> Definition.Value     -> m (Response a)-executeSubscriptionEvent types' objectType fields initialValue =-    runCollectErrs types' $ executeSelectionSet initialValue objectType fields+executeSubscriptionEvent types' objectType objectLocation fields initialValue+    = runCollectErrs types'+    $ executeSelectionSet initialValue objectType objectLocation fields
src/Language/GraphQL/Execute/Transform.hs view
@@ -1,3 +1,7 @@+{- 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 ExplicitForAll #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}@@ -25,7 +29,6 @@     , QueryError(..)     , Selection(..)     , document-    , queryError     ) where  import Control.Monad (foldM, unless)@@ -71,16 +74,18 @@     | SelectionField (Field m)  -- | GraphQL has 3 operation types: queries, mutations and subscribtions.------ Currently only queries and mutations are supported. data Operation m-    = Query (Maybe Text) (Seq (Selection m))-    | Mutation (Maybe Text) (Seq (Selection m))-    | Subscription (Maybe Text) (Seq (Selection m))+    = Query (Maybe Text) (Seq (Selection m)) Full.Location+    | Mutation (Maybe Text) (Seq (Selection m)) Full.Location+    | Subscription (Maybe Text) (Seq (Selection m)) Full.Location  -- | Single GraphQL field. data Field m = Field-    (Maybe Full.Name) Full.Name (HashMap Full.Name Input) (Seq (Selection m))+    (Maybe Full.Name)+    Full.Name+    (HashMap Full.Name (Full.Node Input))+    (Seq (Selection m))+    Full.Location  -- | Contains the operation to be executed along with its root type. data Document m = Document@@ -92,16 +97,26 @@     [Full.VariableDefinition]     [Full.Directive]     Full.SelectionSet+    Full.Location  -- | Query error types. data QueryError     = OperationNotFound Text     | OperationNameRequired     | CoercionError-    | TransformationError     | EmptyDocument     | UnsupportedRootOperation +instance Show QueryError where+    show (OperationNotFound operationName) = unwords+        ["Operation", Text.unpack operationName, "couldn't be found in the document."]+    show OperationNameRequired = "Missing operation name."+    show CoercionError = "Coercion error."+    show EmptyDocument =+        "The document doesn't contain any executable operations."+    show UnsupportedRootOperation =+        "Root operation type couldn't be found in the schema."+ data Input     = Int Int32     | Float Double@@ -114,17 +129,6 @@     | Variable Type.Value     deriving (Eq, Show) -queryError :: QueryError -> Text-queryError (OperationNotFound operationName) = Text.unwords-    ["Operation", operationName, "couldn't be found in the document."]-queryError OperationNameRequired = "Missing operation name."-queryError CoercionError = "Coercion error."-queryError TransformationError = "Schema transformation error."-queryError EmptyDocument =-    "The document doesn't contain any executable operations."-queryError UnsupportedRootOperation =-    "Root operation type couldn't be found in the schema."- getOperation     :: Maybe Full.Name     -> NonEmpty OperationDefinition@@ -135,7 +139,7 @@     | Just operation' <- find matchingName operations = pure operation'     | otherwise = Left $ OperationNotFound operationName   where-    matchingName (OperationDefinition _ name _ _ _) =+    matchingName (OperationDefinition _ name _ _ _ _) =         name == Just operationName  coerceVariableValues :: Coerce.VariableValue a@@ -145,7 +149,7 @@     -> HashMap.HashMap Full.Name a     -> Either QueryError Type.Subs coerceVariableValues types operationDefinition variableValues =-    let OperationDefinition _ _ variableDefinitions _ _ = operationDefinition+    let OperationDefinition _ _ variableDefinitions _ _ _ = operationDefinition      in maybe (Left CoercionError) Right         $ foldr forEach (Just HashMap.empty) variableDefinitions   where@@ -173,7 +177,7 @@ constValue (Full.ConstBoolean b) = Type.Boolean b constValue Full.ConstNull = Type.Null constValue (Full.ConstEnum e) = Type.Enum e-constValue (Full.ConstList l) = Type.List $ constValue <$> l+constValue (Full.ConstList list) = Type.List $ constValue . Full.node <$> list constValue (Full.ConstObject o) =     Type.Object $ HashMap.fromList $ constObjectField <$> o   where@@ -203,14 +207,14 @@             , types = referencedTypes             }     case chosenOperation of-        OperationDefinition Full.Query _ _ _ _ ->+        OperationDefinition Full.Query _ _ _ _ _ ->             pure $ Document referencedTypes (Schema.query schema)                 $ operation chosenOperation replacement-        OperationDefinition Full.Mutation _ _ _ _+        OperationDefinition Full.Mutation _ _ _ _ _             | Just mutationType <- Schema.mutation schema ->                 pure $ Document referencedTypes mutationType                     $ operation chosenOperation replacement-        OperationDefinition Full.Subscription _ _ _ _+        OperationDefinition Full.Subscription _ _ _ _ _             | Just subscriptionType <- Schema.subscription schema ->                 pure $ Document referencedTypes subscriptionType                     $ operation chosenOperation replacement@@ -235,10 +239,10 @@             (operations, HashMap.insert name fragment fragments')     defragment' _ acc = acc     transform = \case-        Full.OperationDefinition type' name variables directives' selections _ ->-            OperationDefinition type' name variables directives' selections-        Full.SelectionSet selectionSet _ ->-            OperationDefinition Full.Query Nothing mempty mempty selectionSet+        Full.OperationDefinition type' name variables directives' selections location ->+            OperationDefinition type' name variables directives' selections location+        Full.SelectionSet selectionSet location ->+            OperationDefinition Full.Query Nothing mempty mempty selectionSet location  -- * Operation @@ -247,12 +251,12 @@     = runIdentity     $ evalStateT (collectFragments >> transform operationDefinition) replacement   where-    transform (OperationDefinition Full.Query name _ _ sels) =-        Query name <$> appendSelection sels-    transform (OperationDefinition Full.Mutation name _ _ sels) =-        Mutation name <$> appendSelection sels-    transform (OperationDefinition Full.Subscription name _ _ sels) =-        Subscription name <$> appendSelection sels+    transform (OperationDefinition Full.Query name _ _ sels location) =+        flip (Query name) location <$> appendSelection sels+    transform (OperationDefinition Full.Mutation name _ _ sels location) =+        flip (Mutation name) location <$> appendSelection sels+    transform (OperationDefinition Full.Subscription name _ _ sels location) =+        flip (Subscription name) location <$> appendSelection sels  -- * Selection @@ -268,15 +272,20 @@     inlineFragment fragmentSelection  field :: Full.Field ->  State (Replacement m) (Maybe (Field m))-field (Full.Field alias name arguments' directives' selections _) = do+field (Full.Field alias name arguments' directives' selections location) = do     fieldArguments <- foldM go HashMap.empty arguments'     fieldSelections <- appendSelection selections     fieldDirectives <- Definition.selection <$> directives directives'-    let field' = Field alias name fieldArguments fieldSelections+    let field' = Field alias name fieldArguments fieldSelections location     pure $ field' <$ fieldDirectives   where-    go arguments (Full.Argument name' (Full.Node value' _) _) =-        inputField arguments name' value'+    go arguments (Full.Argument name' (Full.Node value' _) location') = do+        objectFieldValue <-  input value'+        case objectFieldValue of+            Just fieldValue ->+                let argumentNode = Full.Node fieldValue location'+                 in pure $ HashMap.insert name' argumentNode arguments+            Nothing -> pure arguments  fragmentSpread     :: Full.FragmentSpread@@ -380,7 +389,7 @@ value (Full.Boolean boolean) = pure $ Type.Boolean boolean value Full.Null = pure Type.Null value (Full.Enum enum) = pure $ Type.Enum enum-value (Full.List list) = Type.List <$> traverse value list+value (Full.List list) = Type.List <$> traverse (value . Full.node) list value (Full.Object object) =     Type.Object . HashMap.fromList <$> traverse objectField object   where@@ -396,7 +405,7 @@ input (Full.Boolean boolean) = pure $ pure $ Boolean boolean input Full.Null = pure $ pure Null input (Full.Enum enum) = pure $ pure $ Enum enum-input (Full.List list) = pure . List <$> traverse value list+input (Full.List list) = pure . List <$> traverse (value . Full.node) list input (Full.Object object) = do     objectFields <- foldM objectField HashMap.empty object     pure $ pure $ Object objectFields
src/Language/GraphQL/Type.hs view
@@ -21,6 +21,6 @@     ) where  import Language.GraphQL.Type.Definition-import Language.GraphQL.Type.Schema (Schema, schema)+import Language.GraphQL.Type.Schema (Schema, schema, schemaWithTypes) import qualified Language.GraphQL.Type.In as In import qualified Language.GraphQL.Type.Out as Out
src/Language/GraphQL/Type/Internal.hs view
@@ -12,6 +12,7 @@     , Directives     , Schema(..)     , Type(..)+    , description     , directives     , doesFragmentTypeApply     , implementations@@ -55,41 +56,43 @@ -- | A Schema is created by supplying the root types of each type of operation, --   query and mutation (optional). A schema definition is then supplied to the --   validator and executor.------   __Note:__ When the schema is constructed, by default only the types that---   are reachable by traversing the root types are included, other types must---   be explicitly referenced. data Schema m = Schema-    (Out.ObjectType m)-    (Maybe (Out.ObjectType m))-    (Maybe (Out.ObjectType m))-    Directives-    (HashMap Full.Name (Type m))+    (Maybe Text) -- ^ Description.+    (Out.ObjectType m) -- ^ Query.+    (Maybe (Out.ObjectType m)) -- ^ Mutation.+    (Maybe (Out.ObjectType m)) -- ^ Subscription.+    Directives -- ^ Directives+    (HashMap Full.Name (Type m)) -- ^ Types.+    -- Interface implementations (used only for faster access).     (HashMap Full.Name [Type m]) +-- | Schema description.+description :: forall m. Schema m -> Maybe Text+description (Schema description' _ _ _ _ _ _) = description'+ -- | Schema query type. query :: forall m. Schema m -> Out.ObjectType m-query (Schema query' _ _ _ _ _) = query'+query (Schema _ query' _ _ _ _ _) = query'  -- | Schema mutation type. mutation :: forall m. Schema m -> Maybe (Out.ObjectType m)-mutation (Schema _ mutation' _ _ _ _) = mutation'+mutation (Schema _ _ mutation' _ _ _ _) = mutation'  -- | Schema subscription type. subscription :: forall m. Schema m -> Maybe (Out.ObjectType m)-subscription (Schema _ _ subscription' _ _ _) = subscription'+subscription (Schema _ _ _ subscription' _ _ _) = subscription'  -- | Schema directive definitions. directives :: forall m. Schema m -> Directives-directives (Schema _ _ _ directives' _ _) = directives'+directives (Schema _ _ _ _ directives' _ _) = directives'  -- | Types referenced by the schema. types :: forall m. Schema m -> HashMap Full.Name (Type m)-types (Schema _ _ _ _ types' _) = types'+types (Schema _ _ _ _ _ types' _) = types'  -- | Interface implementations. implementations :: forall m. Schema m -> HashMap Full.Name [Type m]-implementations (Schema _ _ _ _ _ implementations') = implementations'+implementations (Schema _ _ _ _ _ _ implementations') = implementations'  -- | These types may describe the parent context of a selection set. data CompositeType m
src/Language/GraphQL/Type/Schema.hs view
@@ -9,11 +9,13 @@ -- functions for defining and manipulating schemas. module Language.GraphQL.Type.Schema     ( schema+    , schemaWithTypes     , module Language.GraphQL.Type.Internal     ) where  import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap+import Data.Text (Text) import Language.GraphQL.AST.DirectiveLocation (DirectiveLocation(..)) import qualified Language.GraphQL.AST.DirectiveLocation as DirectiveLocation import qualified Language.GraphQL.AST as Full@@ -22,6 +24,7 @@     , Directives     , Schema     , Type(..)+    , description     , directives     , implementations     , mutation@@ -35,17 +38,47 @@ import qualified Language.GraphQL.Type.Out as Out  -- | Schema constructor.+--+-- __Note:__ When the schema is constructed, by default only the types that+-- are reachable by traversing the root types are included, other types must+-- be explicitly referenced using 'schemaWithTypes' instead. schema :: forall m     . Out.ObjectType m -- ^ Query type.     -> Maybe (Out.ObjectType m) -- ^ Mutation type.     -> Maybe (Out.ObjectType m) -- ^ Subscription type.     -> Directives -- ^ Directive definitions.     -> Schema m -- ^ Schema.-schema queryRoot mutationRoot subscriptionRoot directiveDefinitions =-    Internal.Schema queryRoot mutationRoot subscriptionRoot+schema queryRoot mutationRoot subscriptionRoot =+    schemaWithTypes Nothing queryRoot mutationRoot subscriptionRoot mempty++-- | Constructs a complete schema, including user-defined types not referenced+-- in the schema directly (for example interface implementations).+schemaWithTypes :: forall m+    . Maybe Text -- ^ Schema description+    -> Out.ObjectType m -- ^ Query type.+    -> Maybe (Out.ObjectType m) -- ^ Mutation type.+    -> Maybe (Out.ObjectType m) -- ^ Subscription type.+    -> [Type m] -- ^ Additional types.+    -> Directives -- ^ Directive definitions.+    -> Schema m -- ^ Schema.+schemaWithTypes description' queryRoot mutationRoot subscriptionRoot types' directiveDefinitions =+    Internal.Schema description' queryRoot mutationRoot subscriptionRoot         allDirectives collectedTypes collectedImplementations   where-    collectedTypes = collectReferencedTypes queryRoot mutationRoot subscriptionRoot+    allTypes = foldr addTypeDefinition HashMap.empty types'+    addTypeDefinition type'@(ScalarType (Definition.ScalarType typeName _)) accumulator =+        HashMap.insert typeName type' accumulator+    addTypeDefinition type'@(EnumType (Definition.EnumType typeName _ _)) accumulator =+        HashMap.insert typeName type' accumulator+    addTypeDefinition type'@(ObjectType (Out.ObjectType typeName _ _ _)) accumulator =+        HashMap.insert typeName type' accumulator+    addTypeDefinition type'@(InputObjectType (In.InputObjectType typeName _ _)) accumulator =+        HashMap.insert typeName type' accumulator+    addTypeDefinition type'@(InterfaceType (Out.InterfaceType typeName _ _ _)) accumulator =+        HashMap.insert typeName type' accumulator+    addTypeDefinition type'@(UnionType (Out.UnionType typeName _ _)) accumulator =+        HashMap.insert typeName type' accumulator+    collectedTypes = collectReferencedTypes queryRoot mutationRoot subscriptionRoot allTypes     collectedImplementations = collectImplementations collectedTypes     allDirectives = HashMap.union directiveDefinitions defaultDirectives     defaultDirectives = HashMap.fromList@@ -98,11 +131,12 @@     -> Maybe (Out.ObjectType m)     -> Maybe (Out.ObjectType m)     -> HashMap Full.Name (Type m)-collectReferencedTypes queryRoot mutationRoot subscriptionRoot =-    let queryTypes = traverseObjectType queryRoot HashMap.empty+    -> HashMap Full.Name (Type m)+collectReferencedTypes queryRoot mutationRoot subscriptionRoot extraTypes =+    let queryTypes = traverseObjectType queryRoot extraTypes         mutationTypes = maybe queryTypes (`traverseObjectType` queryTypes)             mutationRoot-     in maybe mutationTypes (`traverseObjectType` queryTypes) subscriptionRoot+     in maybe mutationTypes (`traverseObjectType` mutationTypes) subscriptionRoot   where     collect traverser typeName element foundTypes         | HashMap.member typeName foundTypes = foundTypes
src/Language/GraphQL/Validate.hs view
@@ -4,7 +4,6 @@  {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}  -- | GraphQL validator.@@ -315,9 +314,6 @@     go inputObjectType value'@(Full.Node (Full.ConstObject fields) _)         = foldMap (forEach inputObjectType) (Seq.fromList fields)         |> rule inputObjectType value'-    go listType value'@(Full.Node (Full.ConstList values) location')-        = embedListLocation go listType values location'-        |> rule listType value'     go anotherValue value' = pure $ rule anotherValue value'     forEach inputObjectType Full.ObjectField{value = value', ..} =         go (valueTypeByName name inputObjectType) value'@@ -421,20 +417,6 @@   where     valueType (In.Argument _ valueType' _) = valueType' --- valueTypeFromList :: Maybe In.Type -> Maybe In.Type-embedListLocation :: forall a m-    . (Maybe In.Type -> Full.Node a -> Seq m)-    -> Maybe In.Type-    -> [a]-    -> Full.Location-    -> Seq m-embedListLocation go listType values location'-    = foldMap (go $ valueTypeFromList listType) -    $ flip Full.Node location' <$> Seq.fromList values-  where-    valueTypeFromList (Just (In.ListBaseType baseType)) = Just baseType-    valueTypeFromList _ = Nothing- value :: forall m     . Validation.Rule m     -> Maybe In.Type@@ -445,9 +427,6 @@     go inputObjectType value'@(Full.Node (Full.Object fields) _)         = foldMap (forEach inputObjectType) (Seq.fromList fields)         |> rule inputObjectType value'-    go listType value'@(Full.Node (Full.List values) location')-        = embedListLocation go listType values location'-        |> rule listType value'     go anotherValue value' = pure $ rule anotherValue value'     forEach inputObjectType Full.ObjectField{value = value', ..} =         go (valueTypeByName name inputObjectType) value'
src/Language/GraphQL/Validate/Rules.hs view
@@ -1502,15 +1502,6 @@     hasNonNullVariableDefaultValue (Just (Full.Node Full.ConstNull _)) = False     hasNonNullVariableDefaultValue Nothing = False     hasNonNullVariableDefaultValue _ = True-    unwrapInType (In.NonNullScalarType nonNullType) =-        Just $ In.NamedScalarType nonNullType-    unwrapInType (In.NonNullEnumType nonNullType) =-        Just $ In.NamedEnumType nonNullType-    unwrapInType (In.NonNullInputObjectType nonNullType) =-        Just $ In.NamedInputObjectType nonNullType-    unwrapInType (In.NonNullListType nonNullType) =-        Just $ In.ListType nonNullType-    unwrapInType _ = Nothing     makeError variableDefinition expectedType =         let Full.VariableDefinition variableName variableType _ location' =                 variableDefinition@@ -1527,6 +1518,17 @@             , locations = [location']             } +unwrapInType :: In.Type -> Maybe In.Type+unwrapInType (In.NonNullScalarType nonNullType) =+    Just $ In.NamedScalarType nonNullType+unwrapInType (In.NonNullEnumType nonNullType) =+    Just $ In.NamedEnumType nonNullType+unwrapInType (In.NonNullInputObjectType nonNullType) =+    Just $ In.NamedInputObjectType nonNullType+unwrapInType (In.NonNullListType nonNullType) =+    Just $ In.ListType nonNullType+unwrapInType _ = Nothing+ -- | Literal values must be compatible with the type expected in the position -- they are found as per the coercion rules. --@@ -1540,7 +1542,7 @@     go (Just inputType) value         | Just constValue <- toConstNode value =             lift $ check inputType constValue-    go _ _ = lift mempty+    go _ _ = lift mempty -- This rule checks only literals.     toConstNode Full.Node{..} = flip Full.Node location <$> toConst node     toConst (Full.Variable _) = Nothing     toConst (Full.Int integer) = Just $ Full.ConstInt integer@@ -1550,7 +1552,7 @@     toConst Full.Null = Just Full.ConstNull     toConst (Full.Enum enum) = Just $ Full.ConstEnum enum     toConst (Full.List values) =-        Just $ Full.ConstList $ catMaybes $ toConst <$> values+        Just $ Full.ConstList $ catMaybes $ toConstNode <$> values     toConst (Full.Object fields) = Just $ Full.ConstObject         $ catMaybes $ constObjectField <$> fields     constObjectField Full.ObjectField{..}@@ -1582,24 +1584,37 @@         , Full.ConstEnum memberValue <- node         , HashMap.member memberValue members = mempty     check (In.InputObjectBaseType objectType) Full.Node{ node }-        | In.InputObjectType _ _ typeFields <- objectType-        , Full.ConstObject valueFields <- node =-            foldMap (checkObjectField typeFields) valueFields+        -- Skip, objects are checked recursively by the validation traverser.+        | In.InputObjectType{}  <- objectType+        , Full.ConstObject{} <- node = mempty     check (In.ListBaseType listType) constValue@Full.Node{ .. }-        | Full.ConstList listValues <- node =-            foldMap (check listType) $ flip Full.Node location <$> listValues+        | Full.ConstList values <- node =+            foldMap (checkNull listType) values         | otherwise = check listType constValue     check inputType Full.Node{ .. } = pure $ Error         { message = concat             [ "Value "-            , show node, " cannot be coerced to type \""+            , show node+            , " cannot be coerced to type \""             , show inputType             , "\"."             ]         , locations = [location]         }-    checkObjectField typeFields Full.ObjectField{..}-        | Just typeField <- HashMap.lookup name typeFields-        , In.InputField _ fieldType _ <- typeField =-            check fieldType value-    checkObjectField _ _ = mempty+    checkNull inputType constValue =+        let checkResult = check inputType constValue+         in case null checkResult of+            True+                | Just unwrappedType <- unwrapInType inputType+                , Full.Node{ node = Full.ConstNull, .. } <- constValue ->+                    pure $ Error+                        { message = concat+                            [ "List of non-null values of type \""+                            , show unwrappedType+                            , "\" cannot contain null values."+                            ]+                        , locations = [location]+                        }+                | otherwise -> mempty+            _ -> checkResult+
tests/Language/GraphQL/AST/ParserSpec.hs view
@@ -6,6 +6,7 @@  import Data.List.NonEmpty (NonEmpty(..)) import Language.GraphQL.AST.Document+import qualified Language.GraphQL.AST.DirectiveLocation as DirLoc import Language.GraphQL.AST.Parser import Test.Hspec (Spec, describe, it) import Test.Hspec.Megaparsec (shouldParse, shouldFailOn, shouldSucceedOn)@@ -118,6 +119,56 @@               | FIELD               | FRAGMENT_SPREAD         |]++    it "parses two minimal directive definitions" $+        let directive nm loc =+                TypeSystemDefinition+                    (DirectiveDefinition+                         (Description Nothing)+                         nm+                         (ArgumentsDefinition [])+                         (loc :| []))+            example1 =+                directive "example1"+                    (DirLoc.TypeSystemDirectiveLocation DirLoc.FieldDefinition)+                    (Location {line = 2, column = 17})+            example2 =+                directive "example2"+                    (DirLoc.ExecutableDirectiveLocation DirLoc.Field)+                    (Location {line = 3, column = 17})+            testSchemaExtension = example1 :| [ example2 ]+            query = [r|+                directive @example1 on FIELD_DEFINITION+                directive @example2 on FIELD+            |]+         in parse document "" query `shouldParse` testSchemaExtension++    it "parses a directive definition with a default empty list argument" $+        let directive nm loc args =+                TypeSystemDefinition+                    (DirectiveDefinition+                         (Description Nothing)+                         nm+                         (ArgumentsDefinition+                              [ InputValueDefinition+                                    (Description Nothing)+                                    argName+                                    argType+                                    argValue+                                    []+                              | (argName, argType, argValue) <- args])+                         (loc :| []))+            defn =+                directive "test"+                    (DirLoc.TypeSystemDirectiveLocation DirLoc.FieldDefinition)+                    [("foo",+                      TypeList (TypeNamed "String"),+                      Just+                          $ Node (ConstList [])+                          $ Location {line = 1, column = 33})]+                    (Location {line = 1, column = 1})+            query = [r|directive @test(foo: [String] = []) on FIELD_DEFINITION|]+         in parse document "" query `shouldParse` (defn :| [ ])      it "parses schema extension with a new directive" $         parse document "" `shouldSucceedOn`[r|
tests/Language/GraphQL/ErrorSpec.hs view
@@ -8,17 +8,29 @@     ) where  import qualified Data.Aeson as Aeson-import qualified Data.Sequence as Seq+import Data.List.NonEmpty (NonEmpty (..)) import Language.GraphQL.Error-import Test.Hspec ( Spec-                  , describe-                  , it-                  , shouldBe-                  )+import Test.Hspec+    ( Spec+    , describe+    , it+    , shouldBe+    )+import Text.Megaparsec (PosState(..))+import Text.Megaparsec.Error (ParseError(..), ParseErrorBundle(..))+import Text.Megaparsec.Pos (SourcePos(..), mkPos)  spec :: Spec-spec = describe "singleError" $-    it "constructs an error with the given message" $-        let errors'' = Seq.singleton $ Error "Message." [] []-            expected = Response Aeson.Null errors''-         in singleError "Message." `shouldBe` expected+spec = describe "parseError" $+    it "generates response with a single error" $ do+        let parseErrors = TrivialError 0 Nothing mempty :| []+            posState = PosState+                { pstateInput = ""+                , pstateOffset = 0+                , pstateSourcePos = SourcePos "" (mkPos 1) (mkPos 1)+                , pstateTabWidth = mkPos 1+                , pstateLinePrefix = ""+                }+        Response Aeson.Null actual <-+            parseError (ParseErrorBundle parseErrors posState)+        length actual `shouldBe` 1
+ tests/Language/GraphQL/Execute/OrderedMapSpec.hs view
@@ -0,0 +1,72 @@+{- 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 OverloadedStrings #-}++module Language.GraphQL.Execute.OrderedMapSpec+    ( spec+    ) where++import Language.GraphQL.Execute.OrderedMap (OrderedMap)+import qualified Language.GraphQL.Execute.OrderedMap as OrderedMap+import Test.Hspec (Spec, describe, it, shouldBe, shouldSatisfy)++spec :: Spec+spec =+    describe "OrderedMap" $ do+        it "creates an empty map" $+            (mempty :: OrderedMap String) `shouldSatisfy` null++        it "creates a singleton" $+            let value :: String+                value = "value"+             in OrderedMap.size (OrderedMap.singleton "key" value) `shouldBe` 1++        it "combines inserted vales" $+            let key = "key"+                map1 = OrderedMap.singleton key ("1" :: String)+                map2 = OrderedMap.singleton key ("2" :: String)+             in OrderedMap.lookup key (map1 <> map2) `shouldBe` Just "12"++        it "shows the map" $+            let actual = show+                    $ OrderedMap.insert "key1" "1"+                    $ OrderedMap.singleton "key2" ("2" :: String)+                expected = "fromList [(\"key2\",\"2\"),(\"key1\",\"1\")]"+             in actual `shouldBe` expected++        it "traverses a map of just values" $+            let actual = sequence+                    $ OrderedMap.insert "key1" (Just "2")+                    $ OrderedMap.singleton "key2" $ Just ("1" :: String)+                expected = Just+                    $ OrderedMap.insert "key1" "2"+                    $ OrderedMap.singleton "key2" ("1" :: String)+             in actual `shouldBe` expected++        it "traverses a map with a Nothing" $+            let actual = sequence+                    $ OrderedMap.insert "key1" Nothing+                    $ OrderedMap.singleton "key2" $ Just ("1" :: String)+                expected = Nothing+             in actual `shouldBe` expected++        it "combines two maps preserving the order of the second one" $+            let map1 :: OrderedMap String+                map1 = OrderedMap.insert "key2" "2"+                    $ OrderedMap.singleton "key1" "1"+                map2 :: OrderedMap String+                map2 = OrderedMap.insert "key4" "4"+                    $ OrderedMap.singleton "key3" "3"+                expected = OrderedMap.insert "key4" "4"+                    $ OrderedMap.insert "key3" "3"+                    $ OrderedMap.insert "key2" "2"+                    $ OrderedMap.singleton "key1" "1"+             in (map1 <> map2) `shouldBe` expected++        it "replaces existing values" $+            let key = "key"+                actual = OrderedMap.replace key ("2" :: String)+                        $ OrderedMap.singleton key ("1" :: String)+             in OrderedMap.lookup key actual `shouldBe` Just "2"
tests/Language/GraphQL/ExecuteSpec.hs view
@@ -8,35 +8,88 @@     ( spec     ) where -import Control.Exception (SomeException)+import Control.Exception (Exception(..), SomeException)+import Control.Monad.Catch (throwM) import Data.Aeson ((.=)) import qualified Data.Aeson as Aeson import Data.Aeson.Types (emptyObject) import Data.Conduit import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap-import Language.GraphQL.AST (Document, Name)+import Data.Typeable (cast)+import Language.GraphQL.AST (Document, Location(..), Name) import Language.GraphQL.AST.Parser (document) import Language.GraphQL.Error-import Language.GraphQL.Execute-import Language.GraphQL.Type as Type-import Language.GraphQL.Type.Out as Out+import Language.GraphQL.Execute (execute)+import qualified Language.GraphQL.Type.Schema as Schema+import Language.GraphQL.Type+import qualified Language.GraphQL.Type.In as In+import qualified Language.GraphQL.Type.Out as Out+import Prelude hiding (id) import Test.Hspec (Spec, context, describe, it, shouldBe) import Text.Megaparsec (parse) import Text.RawString.QQ (r) +data PhilosopherException = PhilosopherException+    deriving Show++instance Exception PhilosopherException where+    toException = toException. ResolverException+    fromException e = do+        ResolverException resolverException <- fromException e+        cast resolverException+ philosopherSchema :: Schema (Either SomeException)-philosopherSchema = schema queryType Nothing (Just subscriptionType) mempty+philosopherSchema =+    schemaWithTypes Nothing queryType Nothing subscriptionRoot extraTypes mempty+  where+    subscriptionRoot = Just subscriptionType+    extraTypes =+        [ Schema.ObjectType bookType+        , Schema.ObjectType bookCollectionType+        ]  queryType :: Out.ObjectType (Either SomeException) queryType = Out.ObjectType "Query" Nothing []-    $ HashMap.singleton "philosopher" -    $ ValueResolver philosopherField-    $ pure $ Type.Object mempty+    $ HashMap.fromList+    [ ("philosopher", ValueResolver philosopherField philosopherResolver)+    , ("genres", ValueResolver genresField genresResolver)+    ]   where     philosopherField =-        Out.Field Nothing (Out.NonNullObjectType philosopherType) HashMap.empty+        Out.Field Nothing (Out.NonNullObjectType philosopherType)+        $ HashMap.singleton "id"+        $ In.Argument Nothing (In.NamedScalarType id) Nothing+    philosopherResolver = pure $ Object mempty+    genresField =+      let fieldType = Out.ListType $ Out.NonNullScalarType string+       in Out.Field Nothing fieldType HashMap.empty+    genresResolver :: Resolve (Either SomeException)+    genresResolver = throwM PhilosopherException +musicType :: Out.ObjectType (Either SomeException)+musicType = Out.ObjectType "Music" Nothing []+    $ HashMap.fromList resolvers+  where+    resolvers =+        [ ("instrument", ValueResolver instrumentField instrumentResolver)+        ]+    instrumentResolver = pure $ String "piano"+    instrumentField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty++poetryType :: Out.ObjectType (Either SomeException)+poetryType = Out.ObjectType "Poetry" Nothing []+    $ HashMap.fromList resolvers+  where+    resolvers =+        [ ("genre", ValueResolver genreField genreResolver)+        ]+    genreResolver = pure $ String "Futurism"+    genreField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty++interestType :: Out.UnionType (Either SomeException)+interestType = Out.UnionType "Interest" Nothing [musicType, poetryType]+ philosopherType :: Out.ObjectType (Either SomeException) philosopherType = Out.ObjectType "Philosopher" Nothing []     $ HashMap.fromList resolvers@@ -44,19 +97,68 @@     resolvers =         [ ("firstName", ValueResolver firstNameField firstNameResolver)         , ("lastName", ValueResolver lastNameField lastNameResolver)+        , ("school", ValueResolver schoolField schoolResolver)+        , ("interest", ValueResolver interestField interestResolver)+        , ("majorWork", ValueResolver majorWorkField majorWorkResolver)+        , ("century", ValueResolver centuryField centuryResolver)         ]     firstNameField =         Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty-    firstNameResolver = pure $ Type.String "Friedrich"+    firstNameResolver = pure $ String "Friedrich"     lastNameField         = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty-    lastNameResolver = pure $ Type.String "Nietzsche"+    lastNameResolver = pure $ String "Nietzsche"+    schoolField+       = Out.Field Nothing (Out.NonNullEnumType schoolType) HashMap.empty+    schoolResolver = pure $ Enum "EXISTENTIALISM"+    interestField+        = Out.Field Nothing (Out.NonNullUnionType interestType) HashMap.empty+    interestResolver = pure+        $ Object+        $ HashMap.fromList [("instrument", "piano")]+    majorWorkField+        = Out.Field Nothing (Out.NonNullInterfaceType workType) HashMap.empty+    majorWorkResolver = pure+        $ Object+        $ HashMap.fromList+            [ ("title", "Also sprach Zarathustra: Ein Buch für Alle und Keinen")+            ]+    centuryField =+        Out.Field Nothing (Out.NonNullScalarType int) HashMap.empty+    centuryResolver = pure $ Float 18.5 +workType :: Out.InterfaceType (Either SomeException)+workType = Out.InterfaceType "Work" Nothing []+    $ HashMap.fromList fields+  where+    fields = [("title", titleField)]+    titleField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty++bookType :: Out.ObjectType (Either SomeException)+bookType = Out.ObjectType "Book" Nothing [workType]+    $ HashMap.fromList resolvers+  where+    resolvers =+        [ ("title", ValueResolver titleField titleResolver)+        ]+    titleField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty+    titleResolver = pure "Also sprach Zarathustra: Ein Buch für Alle und Keinen"++bookCollectionType :: Out.ObjectType (Either SomeException)+bookCollectionType = Out.ObjectType "Book" Nothing [workType]+    $ HashMap.fromList resolvers+  where+    resolvers =+        [ ("title", ValueResolver titleField titleResolver)+        ]+    titleField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty+    titleResolver = pure "The Three Critiques"+ subscriptionType :: Out.ObjectType (Either SomeException) subscriptionType = Out.ObjectType "Subscription" Nothing []     $ HashMap.singleton "newQuote"-    $ EventStreamResolver quoteField (pure $ Type.Object mempty)-    $ pure $ yield $ Type.Object mempty+    $ EventStreamResolver quoteField (pure $ Object mempty)+    $ pure $ yield $ Object mempty   where     quoteField =         Out.Field Nothing (Out.NonNullObjectType quoteType) HashMap.empty@@ -70,6 +172,13 @@     quoteField =         Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty +schoolType :: EnumType+schoolType = EnumType "School" Nothing $ HashMap.fromList+    [ ("NOMINALISM", EnumValue Nothing)+    , ("REALISM", EnumValue Nothing)+    , ("IDEALISM", EnumValue Nothing)+    ]+ type EitherStreamOrValue = Either     (ResponseEventStream (Either SomeException) Aeson.Value)     (Response Aeson.Value)@@ -118,6 +227,99 @@                     Right (Right actual) = either (pure . parseError) execute'                         $ parse document "" "{ philosopher { firstName } philosopher { lastName } }"                 in actual `shouldBe` expected++            it "errors on invalid output enum values" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.object+                            [ "school" .= Aeson.Null+                            ]+                        ]+                    executionErrors = pure $ Error+                        { message = "Enum value completion failed."+                        , locations = [Location 1 17]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher { school } }"+                in actual `shouldBe` expected++            it "gives location information for non-null unions" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.object+                            [ "interest" .= Aeson.Null+                            ]+                        ]+                    executionErrors = pure $ Error+                        { message = "Union value completion failed."+                        , locations = [Location 1 17]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher { interest } }"+                in actual `shouldBe` expected++            it "gives location information for invalid interfaces" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.object+                            [ "majorWork" .= Aeson.Null+                            ]+                        ]+                    executionErrors = pure $ Error+                        { message = "Interface value completion failed."+                        , locations = [Location 1 17]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher { majorWork { title } } }"+                in actual `shouldBe` expected++            it "gives location information for invalid scalar arguments" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.Null+                        ]+                    executionErrors = pure $ Error+                        { message = "Argument coercing failed."+                        , locations = [Location 1 15]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher(id: true) { lastName } }"+                in actual `shouldBe` expected++            it "gives location information for failed result coercion" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.object+                            [ "century" .= Aeson.Null+                            ]+                        ]+                    executionErrors = pure $ Error+                        { message = "Result coercion failed."+                        , locations = [Location 1 26]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher(id: \"1\") { century } }"+                in actual `shouldBe` expected++            it "gives location information for failed result coercion" $+                let data'' = Aeson.object+                        [ "genres" .= Aeson.Null+                        ]+                    executionErrors = pure $ Error+                        { message = "PhilosopherException"+                        , locations = [Location 1 3]+                        , path = []+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ genres }"+                in actual `shouldBe` expected+         context "Subscription" $             it "subscribes" $                 let data'' = Aeson.object
tests/Language/GraphQL/Validate/RulesSpec.hs view
@@ -49,16 +49,18 @@ catType = ObjectType "Cat" Nothing [petType] $ HashMap.fromList     [ ("name", nameResolver)     , ("nickname", nicknameResolver)-    , ("doesKnowCommand", doesKnowCommandResolver)+    , ("doesKnowCommands", doesKnowCommandsResolver)     , ("meowVolume", meowVolumeResolver)     ]   where     meowVolumeField = Field Nothing (Out.NamedScalarType int) mempty     meowVolumeResolver = ValueResolver meowVolumeField $ pure $ Int 3-    doesKnowCommandField = Field Nothing (Out.NonNullScalarType boolean)-        $ HashMap.singleton "catCommand"-        $ In.Argument Nothing (In.NonNullEnumType catCommandType) Nothing-    doesKnowCommandResolver = ValueResolver doesKnowCommandField+    doesKnowCommandsType = In.NonNullListType+        $ In.NonNullEnumType catCommandType+    doesKnowCommandsField = Field Nothing (Out.NonNullScalarType boolean)+        $ HashMap.singleton "catCommands"+        $ In.Argument Nothing doesKnowCommandsType Nothing+    doesKnowCommandsResolver = ValueResolver doesKnowCommandsField         $ pure $ Boolean True  nameResolver :: Resolver IO@@ -845,7 +847,7 @@                         }                  in validate queryString `shouldBe` [expected] -        context "providedRequiredArgumentsRule" $+        context "providedRequiredArgumentsRule" $ do             it "checks for (non-)nullable arguments" $                 let queryString = [r|                   {@@ -866,17 +868,17 @@         context "variablesInAllowedPositionRule" $ do             it "rejects wrongly typed variable arguments" $                 let queryString = [r|-                  query catCommandArgQuery($catCommandArg: CatCommand) {-                    cat {-                      doesKnowCommand(catCommand: $catCommandArg)+                  query dogCommandArgQuery($dogCommandArg: DogCommand) {+                    dog {+                      doesKnowCommand(dogCommand: $dogCommandArg)                     }                   }                 |]                     expected = Error                         { message =-                            "Variable \"$catCommandArg\" of type \-                            \\"CatCommand\" used in position expecting type \-                            \\"!CatCommand\"."+                            "Variable \"$dogCommandArg\" of type \+                            \\"DogCommand\" used in position expecting type \+                            \\"!DogCommand\"."                         , locations = [AST.Location 2 44]                         }                  in validate queryString `shouldBe` [expected]@@ -897,7 +899,7 @@                         }                  in validate queryString `shouldBe` [expected] -        context "valuesOfCorrectTypeRule" $+        context "valuesOfCorrectTypeRule" $ do             it "rejects values of incorrect types" $                 let queryString = [r|                   {@@ -910,5 +912,51 @@                         { message =                             "Value 3 cannot be coerced to type \"Boolean\"."                         , locations = [AST.Location 4 52]+                        }+                 in validate queryString `shouldBe` [expected]++            it "uses the location of a single list value" $+                let queryString = [r|+                  {+                    cat {+                      doesKnowCommands(catCommands: [3])+                    }+                  }+                |]+                    expected = Error+                        { message =+                            "Value 3 cannot be coerced to type \"!CatCommand\"."+                        , locations = [AST.Location 4 54]+                        }+                 in validate queryString `shouldBe` [expected]++            it "validates input object properties once" $+                let queryString = [r|+                  {+                    findDog(complex: { name: 3 }) {+                      name+                    }+                  }+                |]+                    expected = Error+                        { message =+                            "Value 3 cannot be coerced to type \"!String\"."+                        , locations = [AST.Location 3 46]+                        }+                 in validate queryString `shouldBe` [expected]++            it "checks for required list members" $+                let queryString = [r|+                  {+                    cat {+                      doesKnowCommands(catCommands: [null])+                    }+                  }+                |]+                    expected = Error+                        { message =+                            "List of non-null values of type \"CatCommand\" \+                            \cannot contain null values."+                        , locations = [AST.Location 4 54]                         }                  in validate queryString `shouldBe` [expected]