packages feed

graphql 1.0.0.0 → 1.0.1.0

raw patch · 21 files changed

+1385/−1064 lines, 21 filesdep +template-haskelldep −raw-strings-qqdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: template-haskell

Dependencies removed: raw-strings-qq

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Language.GraphQL.Execute: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Language.GraphQL.Execute.ExecutorT m)
+ Language.GraphQL.Execute: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Language.GraphQL.Execute.ExecutorT m)
+ Language.GraphQL.Execute: instance Control.Monad.Trans.Class.MonadTrans Language.GraphQL.Execute.ExecutorT
+ Language.GraphQL.Execute: instance GHC.Base.Applicative m => GHC.Base.Applicative (Language.GraphQL.Execute.ExecutorT m)
+ Language.GraphQL.Execute: instance GHC.Base.Functor m => GHC.Base.Functor (Language.GraphQL.Execute.ExecutorT m)
+ Language.GraphQL.Execute: instance GHC.Base.Monad m => GHC.Base.Monad (Language.GraphQL.Execute.ExecutorT m)
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.FieldException
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.GraphQLException
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.InputCoercionException
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.ResultCoercionException
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.ResultException
+ Language.GraphQL.Execute: instance GHC.Exception.Type.Exception Language.GraphQL.Execute.ValueCompletionException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.FieldException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.GraphQLException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.InputCoercionException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.ResultCoercionException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.ResultException
+ Language.GraphQL.Execute: instance GHC.Show.Show Language.GraphQL.Execute.ValueCompletionException
+ Language.GraphQL.TH: gql :: QuasiQuoter

Files

CHANGELOG.md view
@@ -6,8 +6,24 @@ and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org/). -## [1.0.0.0]+## [1.0.1.0] - 2021-09-27 ### Added+- Custom `Show` instance for `Type.Definition.Value` (for error+  messages).+- Path information in errors (path to the field throwing the error).+- Deprecation notes in the `Error` module for `Resolution`, `CollectErrsT` and+  `runCollectErrs`. These symbols are part of the old executor and aren't used+  anymore, it will be deprecated in the future and removed.+- `TH` module with the `gql` quasi quoter.++### Fixed+- Error messages are more concrete, they also contain type information and+  wrong values, where appropriate and possible.+- If the field with an error is Non-Nullable, the error is propagated to the+  first nullable field, as required by the specification.++## [1.0.0.0] - 2021-07-04+### Added - `Language.GraphQL.Execute.OrderedMap` is a map data structure, that preserves   insertion order. - `Language.GraphQL.Schema.schemaWithTypes` constructs a complete schema,@@ -443,6 +459,7 @@ ### Added - Data types for the GraphQL language. +[1.0.1.0]: https://www.caraus.tech/projects/pub-graphql/repository/23/diff?rev=v1.0.1.0&rev_to=v1.0.0.0 [1.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
graphql.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2  name:           graphql-version:        1.0.0.0+version:        1.0.1.0 synopsis:       Haskell GraphQL implementation description:    Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation. category:       Language@@ -20,7 +20,9 @@ extra-source-files:     CHANGELOG.md     README.md-tested-with: GHC == 8.10.4+tested-with:+    GHC == 8.10.7+  , GHC == 9.0.1  source-repository head   type: git@@ -39,6 +41,7 @@       Language.GraphQL.Execute       Language.GraphQL.Execute.Coerce       Language.GraphQL.Execute.OrderedMap+      Language.GraphQL.TH       Language.GraphQL.Type       Language.GraphQL.Type.In       Language.GraphQL.Type.Out@@ -47,9 +50,6 @@       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@@ -67,6 +67,7 @@     , megaparsec >= 9.0.1 && < 9.1     , parser-combinators >= 1.3.0 && < 1.4     , scientific >= 0.3.7 && < 0.4+    , template-haskell >= 2.16 && < 2.18     , text >= 1.2.4 && < 1.3     , transformers >= 0.5.6 && < 0.6     , unordered-containers >= 0.2.14 && < 0.3@@ -96,14 +97,13 @@   build-depends:       QuickCheck >= 2.14.1 && < 2.15     , aeson-    , base >= 4.7 && < 5+    , base >= 4.8 && < 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
src/Language/GraphQL/AST.hs view
@@ -1,6 +1,4 @@-{- 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 Safe #-}  -- | Target AST for parser. module Language.GraphQL.AST
src/Language/GraphQL/AST/DirectiveLocation.hs view
@@ -1,7 +1,3 @@-{- 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 Safe #-}  -- | Various parts of a GraphQL document can be annotated with directives. 
src/Language/GraphQL/Error.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 DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RecordWildCards #-}@@ -40,12 +44,6 @@     , unPos     ) --- | Executor context.-data Resolution m = Resolution-    { errors :: Seq Error-    , types :: HashMap Name (Schema.Type m)-    }- -- | Wraps a parse error into a list of errors. parseError :: (Applicative f, Serialize a)     => ParseErrorBundle Text Void@@ -65,32 +63,6 @@             sourcePosition = pstateSourcePos newState          in (result |> errorObject x sourcePosition, newState) --- | A wrapper to pass error messages around.-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 $ 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- -- | If an error can be associated to a particular field in the GraphQL result, -- it must contain an entry with the key path that details the path of the -- response field which experienced the error. This allows clients to identify@@ -129,8 +101,13 @@  instance Exception ResolverException +-- * Deprecated+ -- | Runs the given query computation, but collects the errors into an error -- list, which is then sent back with the data.+--+-- /runCollectErrs was part of the old executor and isn't used anymore, it will+-- be deprecated in the future and removed./ runCollectErrs :: (Monad m, Serialize a)     => HashMap Name (Schema.Type m)     -> CollectErrsT m a@@ -139,3 +116,41 @@     (dat, Resolution{..}) <- runStateT res         $ Resolution{ errors = Seq.empty, types = types' }     pure $ Response dat errors++-- | Executor context.+--+-- /Resolution was part of the old executor and isn't used anymore, it will be+-- deprecated in the future and removed./+data Resolution m = Resolution+    { errors :: Seq Error+    , types :: HashMap Name (Schema.Type m)+    }++-- | A wrapper to pass error messages around.+--+-- /CollectErrsT was part of the old executor and isn't used anymore, it will be+-- deprecated in the future and removed./+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 $ 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,32 +1,224 @@-{-# LANGUAGE ExplicitForAll #-}+{- 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 DataKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE TypeApplications #-}+ -- | This module provides functions to execute a @GraphQL@ request. module Language.GraphQL.Execute     ( execute     , module Language.GraphQL.Execute.Coerce     ) where -import Control.Monad.Catch (MonadCatch)+import Conduit (mapMC, (.|))+import Control.Arrow (left)+import Control.Monad.Catch+     ( Exception(..)+     , Handler(..)+     , MonadCatch(..)+     , MonadThrow(..)+     , SomeException(..)+     , catches+     )+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Reader (ReaderT(..), ask, runReaderT)+import Control.Monad.Trans.Writer (WriterT(..), runWriterT)+import qualified Control.Monad.Trans.Writer as Writer+import Control.Monad (foldM)+import qualified Language.GraphQL.AST.Document as Full+import Data.Foldable (find) import Data.HashMap.Strict (HashMap)-import Data.Sequence (Seq(..))+import qualified Data.HashMap.Strict as HashMap+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe (fromMaybe)+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq import Data.Text (Text)-import qualified Language.GraphQL.AST.Document as Full+import qualified Data.Text as Text+import Data.Typeable (cast)+import GHC.Records (HasField(..)) import Language.GraphQL.Execute.Coerce-import Language.GraphQL.Execute.Execution-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.Execute.Subscribe as Subscribe+import qualified Language.GraphQL.Type.In as In+import qualified Language.GraphQL.Type.Out as Out+import qualified Language.GraphQL.Type as Type+import qualified Language.GraphQL.Type.Internal as Type.Internal+import Language.GraphQL.Type.Schema (Schema, Type)+import qualified Language.GraphQL.Type.Schema as Schema import Language.GraphQL.Error-    ( Error-    , ResponseEventStream+    ( Error(..)     , Response(..)-    , runCollectErrs+    , Path(..)+    , ResolverException(..)+    , ResponseEventStream     )-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) +newtype ExecutorT m a = ExecutorT+    { runExecutorT :: ReaderT (HashMap Full.Name (Type m)) (WriterT (Seq Error) m) a+    }++instance Functor m => Functor (ExecutorT m) where+    fmap f = ExecutorT . fmap f . runExecutorT++instance Applicative m => Applicative (ExecutorT m) where+    pure = ExecutorT . pure+    ExecutorT f <*> ExecutorT x = ExecutorT $ f <*> x++instance Monad m => Monad (ExecutorT m) where+    ExecutorT x >>= f = ExecutorT $ x >>= runExecutorT . f++instance MonadTrans ExecutorT where+    lift = ExecutorT . lift . lift++instance MonadThrow m => MonadThrow (ExecutorT m) where+    throwM = lift . throwM++instance MonadCatch m => MonadCatch (ExecutorT m) where+  catch (ExecutorT stack) handler =+      ExecutorT $ catch stack $ runExecutorT . handler++data GraphQLException = forall e. Exception e => GraphQLException e++instance Show GraphQLException where+    show (GraphQLException e) = show e++instance Exception GraphQLException++graphQLExceptionToException :: Exception e => e -> SomeException+graphQLExceptionToException = toException . GraphQLException++graphQLExceptionFromException :: Exception e => SomeException -> Maybe e+graphQLExceptionFromException e = do+    GraphQLException graphqlException <- fromException e+    cast graphqlException++data ResultException = forall e. Exception e => ResultException e++instance Show ResultException where+    show (ResultException e) = show e++instance Exception ResultException where+    toException = graphQLExceptionToException+    fromException = graphQLExceptionFromException++resultExceptionToException :: Exception e => e -> SomeException+resultExceptionToException = toException . ResultException++resultExceptionFromException :: Exception e => SomeException -> Maybe e+resultExceptionFromException e = do+    ResultException resultException <- fromException e+    cast resultException++data FieldException = forall e. Exception e => FieldException Full.Location [Path] e++instance Show FieldException where+    show (FieldException _ _ e) = displayException e++instance Exception FieldException where+    toException = graphQLExceptionToException+    fromException = graphQLExceptionFromException++data ValueCompletionException = ValueCompletionException String Type.Value++instance Show ValueCompletionException where+    show (ValueCompletionException typeRepresentation found) = concat+        [ "Value completion error. Expected type "+        , typeRepresentation+        , ", found: "+        , show found+        , "."+        ]++instance Exception ValueCompletionException where+    toException = resultExceptionToException+    fromException = resultExceptionFromException++data InputCoercionException =+    InputCoercionException String In.Type (Maybe (Full.Node Transform.Input))++instance Show InputCoercionException where+    show (InputCoercionException argumentName argumentType Nothing) = concat+        [ "Required argument \""+        , argumentName+        , "\" of type "+        , show argumentType+        , " not specified."+        ]+    show (InputCoercionException argumentName argumentType (Just givenValue)) = concat+        [ "Argument \""+        , argumentName+        , "\" has invalid type. Expected type "+        , show argumentType+        , ", found: "+        , show givenValue+        , "."+        ]++instance Exception InputCoercionException where+    toException = graphQLExceptionToException+    fromException = graphQLExceptionFromException++newtype ResultCoercionException = ResultCoercionException String++instance Show ResultCoercionException where+    show (ResultCoercionException typeRepresentation) = concat+        [ "Unable to coerce result to "+        , typeRepresentation+        , "."+        ]++instance Exception ResultCoercionException where+    toException = resultExceptionToException+    fromException = resultExceptionFromException++-- | Query error types.+data QueryError+    = OperationNameRequired+    | OperationNotFound String+    | CoercionError Full.VariableDefinition+    | UnknownInputType Full.VariableDefinition++tell :: Monad m => Seq Error -> ExecutorT m ()+tell = ExecutorT . lift . Writer.tell++queryError :: QueryError -> Error+queryError OperationNameRequired =+    Error{ message = "Operation name is required.", locations = [], path = [] }+queryError (OperationNotFound operationName) =+    let queryErrorMessage = Text.concat+            [ "Operation \""+            , Text.pack operationName+            , "\" not found."+            ]+     in Error{ message = queryErrorMessage, locations = [], path = [] }+queryError (CoercionError variableDefinition) =+    let Full.VariableDefinition variableName _ _ location = variableDefinition+        queryErrorMessage = Text.concat+            [ "Failed to coerce the variable \""+            , variableName+            , "\"."+            ]+     in Error{ message = queryErrorMessage, locations = [location], path = [] }+queryError (UnknownInputType variableDefinition) =+    let Full.VariableDefinition variableName variableTypeName _ location = variableDefinition+        queryErrorMessage = Text.concat+            [ "Variable \""+            , variableName+            , "\" has unknown type \""+            , Text.pack $ show variableTypeName+            , "\"."+            ]+     in Error{ message = queryErrorMessage, locations = [location], path = [] }+ -- | 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 -- defines multiple root operations.@@ -39,33 +231,492 @@     -> HashMap Full.Name a -- ^ Variable substitution function.     -> Full.Document -- @GraphQL@ document.     -> m (Either (ResponseEventStream m b) (Response b))-execute schema' operationName subs document-    = either (pure . rightErrorResponse . singleError [] . show) executeRequest-    $ Transform.document schema' operationName subs document+execute schema' operationName subs document' =+    executeRequest schema' document' (Text.unpack <$> operationName) subs -executeRequest :: (MonadCatch m, Serialize a)-    => Transform.Document m+executeRequest :: (MonadCatch m, Serialize a, VariableValue b)+    => Schema m+    -> Full.Document+    -> Maybe String+    -> HashMap Full.Name b     -> m (Either (ResponseEventStream m a) (Response a))-executeRequest (Transform.Document types' rootObjectType operation)-    | (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+executeRequest schema sourceDocument operationName variableValues = do+    operationAndVariables <- sequence buildOperation+    case operationAndVariables of+        Left queryError' -> pure+            $ Right+            $ Response null $ pure $ queryError queryError'+        Right operation+            | Transform.Operation Full.Query topSelections _operationLocation <- operation ->+                 Right <$> executeQuery topSelections schema+            | Transform.Operation Full.Mutation topSelections operationLocation <- operation ->+                Right <$> executeMutation topSelections schema operationLocation+            | Transform.Operation Full.Subscription topSelections operationLocation <- operation ->+                either rightErrorResponse Left <$> subscribe topSelections schema operationLocation+  where+    schemaTypes = Schema.types schema+    (operationDefinitions, fragmentDefinitions') =+        Transform.document sourceDocument+    buildOperation = do+        operationDefinition <- getOperation operationDefinitions operationName+        coercedVariableValues <- coerceVariableValues+            schemaTypes+            operationDefinition+            variableValues+        let replacement = Transform.Replacement+                { variableValues = coercedVariableValues+                , fragmentDefinitions = fragmentDefinitions'+                , visitedFragments = mempty+                , types = schemaTypes+                }+        pure $ flip runReaderT replacement+            $ Transform.runTransformT+            $ Transform.transform operationDefinition --- This is actually executeMutation, but we don't distinguish between queries--- and mutations yet.-executeOperation :: (MonadCatch m, Serialize a)+rightErrorResponse :: Serialize b => forall a. Error -> Either a (Response b)+rightErrorResponse = Right . Response null . pure++getOperation :: [Full.OperationDefinition] -> Maybe String -> Either QueryError Full.OperationDefinition+getOperation [operation] Nothing = Right operation+getOperation operations (Just givenOperationName)+    = maybe (Left $ OperationNotFound givenOperationName) Right+    $ find findOperationByName operations+  where+    findOperationByName (Full.OperationDefinition _ (Just operationName) _ _ _ _) =+        givenOperationName == Text.unpack operationName+    findOperationByName _ = False+getOperation _ _ = Left OperationNameRequired++executeQuery :: (MonadCatch m, Serialize a)+    => Seq (Transform.Selection m)+    -> Schema m+    -> m (Response a)+executeQuery topSelections schema = do+    let queryType = Schema.query schema+    (data', errors) <- runWriterT+        $ flip runReaderT (Schema.types schema)+        $ runExecutorT+        $ catch (executeSelectionSet topSelections queryType Type.Null [])+        handleException+    pure $ Response data' errors++handleException :: (MonadCatch m, Serialize a)+    => FieldException+    -> ExecutorT m a+handleException (FieldException fieldLocation errorPath next) =+    let newError = constructError next fieldLocation errorPath+     in tell (Seq.singleton newError) >> pure null++constructError :: Exception e => e -> Full.Location -> [Path] -> Error+constructError e fieldLocation errorPath = Error+    { message = Text.pack (displayException e)+    , path = reverse errorPath+    , locations = [fieldLocation]+    }++executeMutation :: (MonadCatch m, Serialize a)+    => Seq (Transform.Selection m)+    -> Schema m+    -> Full.Location+    -> m (Response a)+executeMutation topSelections schema operationLocation+    | Just mutationType <- Schema.mutation schema = do+        (data', errors) <- runWriterT+            $ flip runReaderT (Schema.types schema)+            $ runExecutorT+            $ catch (executeSelectionSet topSelections mutationType Type.Null [])+            handleException+        pure $ Response data' errors+    | otherwise = pure+        $ Response null+        $ Seq.singleton+        $ Error "Schema doesn't support mutations." [operationLocation] []++executeSelectionSet :: (MonadCatch m, Serialize a)+    => Seq (Transform.Selection m)+    -> Out.ObjectType m+    -> Type.Value+    -> [Path]+    -> ExecutorT m a+executeSelectionSet selections objectType objectValue errorPath = do+    let groupedFieldSet = collectFields objectType selections+    resolvedValues <- OrderedMap.traverseMaybe go groupedFieldSet+    coerceResult (Out.NonNullObjectType objectType) $ Object resolvedValues+  where+    executeField' fields resolver =+        executeField objectValue fields resolver errorPath+    Out.ObjectType _ _ _ resolvers = objectType+    go fields@(Transform.Field _ fieldName _ _ _ :| _) =+        traverse (executeField' fields) $ HashMap.lookup fieldName resolvers++fieldsSegment :: forall m. NonEmpty (Transform.Field m) -> Path+fieldsSegment (Transform.Field alias fieldName _ _ _ :| _) =+    Segment (fromMaybe fieldName alias)++viewResolver :: Out.Resolver m -> (Out.Field m, Out.Resolve m)+viewResolver (Out.ValueResolver resolverField' resolveFunction) =+    (resolverField', resolveFunction)+viewResolver (Out.EventStreamResolver resolverField' resolveFunction _) =+    (resolverField', resolveFunction)++executeField :: forall m a+    . (MonadCatch m, Serialize a)+    => Type.Value+    -> NonEmpty (Transform.Field m)+    -> Out.Resolver m+    -> [Path]+    -> ExecutorT m a+executeField objectValue fields (viewResolver -> resolverPair) errorPath =+    let Transform.Field _ fieldName inputArguments _ fieldLocation :| _ = fields+     in catches (go fieldName inputArguments)+        [ Handler nullResultHandler+        , Handler (inputCoercionHandler fieldLocation)+        , Handler (resultHandler fieldLocation)+        , Handler (resolverHandler fieldLocation)+        ]+  where+    inputCoercionHandler :: (MonadCatch m, Serialize a)+        => Full.Location+        -> InputCoercionException+        -> ExecutorT m a+    inputCoercionHandler _ e@(InputCoercionException _ _ (Just valueNode)) =+        let argumentLocation = getField @"location" valueNode+         in exceptionHandler argumentLocation e+    inputCoercionHandler fieldLocation e = exceptionHandler fieldLocation e+    resultHandler :: (MonadCatch m, Serialize a)+        => Full.Location+        -> ResultException+        -> ExecutorT m a+    resultHandler = exceptionHandler+    resolverHandler :: (MonadCatch m, Serialize a)+        => Full.Location+        -> ResolverException+        -> ExecutorT m a+    resolverHandler = exceptionHandler+    nullResultHandler :: (MonadCatch m, Serialize a)+        => FieldException+        -> ExecutorT m a+    nullResultHandler e@(FieldException fieldLocation errorPath' next) =+        let newError = constructError next fieldLocation errorPath'+         in if Out.isNonNullType fieldType+             then throwM e+             else returnError newError+    exceptionHandler errorLocation e =+        let newPath = fieldsSegment fields : errorPath+            newError = constructError e errorLocation newPath+         in if Out.isNonNullType fieldType+             then throwM $ FieldException errorLocation newPath e+             else returnError newError+    returnError newError = tell (Seq.singleton newError) >> pure null+    go fieldName inputArguments = do+        argumentValues <- coerceArgumentValues argumentTypes inputArguments+        resolvedValue <-+           resolveFieldValue resolveFunction objectValue fieldName argumentValues+        completeValue fieldType fields errorPath resolvedValue+    (resolverField, resolveFunction) = resolverPair+    Out.Field _ fieldType argumentTypes = resolverField++resolveFieldValue :: MonadCatch m+    => Out.Resolve m+    -> Type.Value+    -> Full.Name+    -> Type.Subs+    -> ExecutorT m Type.Value+resolveFieldValue resolver objectValue _fieldName argumentValues =+    lift $ runReaderT resolver context+  where+    context = Type.Context+        { Type.arguments = Type.Arguments argumentValues+        , Type.values = objectValue+        }++resolveAbstractType :: Monad m+    => Type.Internal.AbstractType m+    -> Type.Subs+    -> ExecutorT m (Maybe (Out.ObjectType m))+resolveAbstractType abstractType values'+    | Just (Type.String typeName) <- HashMap.lookup "__typename" values' = do+        types' <- ExecutorT ask+        case HashMap.lookup typeName types' of+            Just (Type.Internal.ObjectType objectType) ->+                if Type.Internal.instanceOf objectType abstractType+                    then pure $ Just objectType+                    else pure Nothing+            _ -> pure Nothing+    | otherwise = pure Nothing++completeValue :: (MonadCatch m, Serialize a)+    => Out.Type m+    -> NonEmpty (Transform.Field m)+    -> [Path]+    -> Type.Value+    -> ExecutorT m a+completeValue (Out.isNonNullType -> False) _ _ Type.Null =+    pure null+completeValue outputType@(Out.ListBaseType listType) fields errorPath (Type.List list)+    = foldM go (0, []) list >>= coerceResult outputType . List . snd+  where+    go (index, accumulator) listItem = do+        let updatedPath = Index index : errorPath+        completedValue <- completeValue listType fields updatedPath listItem+        pure (index + 1, completedValue : accumulator)+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) =+    let Type.EnumType _ _ enumMembers = enumType+     in if HashMap.member enum enumMembers+        then coerceResult outputType $ Enum enum+        else throwM+            $ ValueCompletionException (show outputType)+            $ Type.Enum enum+completeValue (Out.ObjectBaseType objectType) fields errorPath result+    = executeSelectionSet (mergeSelectionSets fields) objectType result+    $ fieldsSegment fields : errorPath+completeValue outputType@(Out.InterfaceBaseType interfaceType) fields errorPath result+    | Type.Object objectMap <- result = do+        let abstractType = Type.Internal.AbstractInterfaceType interfaceType+        concreteType <- resolveAbstractType abstractType objectMap+        case concreteType of+            Just objectType+                -> executeSelectionSet (mergeSelectionSets fields) objectType result+                $ fieldsSegment fields : errorPath+            Nothing -> throwM+                $ ValueCompletionException (show outputType) result+completeValue outputType@(Out.UnionBaseType unionType) fields errorPath result+    | Type.Object objectMap <- result = do+        let abstractType = Type.Internal.AbstractUnionType unionType+        concreteType <- resolveAbstractType abstractType objectMap+        case concreteType of+            Just objectType+                -> executeSelectionSet (mergeSelectionSets fields) objectType result+                $ fieldsSegment fields : errorPath+            Nothing -> throwM+                $ ValueCompletionException (show outputType) result+completeValue outputType _ _ result =+    throwM $ ValueCompletionException (show outputType) result++coerceResult :: (MonadCatch m, Serialize a)+    => Out.Type m+    -> Output a+    -> ExecutorT m a+coerceResult outputType result+    | Just serialized <- serialize outputType result = pure serialized+    | otherwise = throwM $ ResultCoercionException $ show outputType++mergeSelectionSets :: MonadCatch m+    => NonEmpty (Transform.Field m)+    -> Seq (Transform.Selection m)+mergeSelectionSets = foldr forEach mempty+  where+    forEach (Transform.Field _ _ _ fieldSelectionSet _) selectionSet' =+        selectionSet' <> fieldSelectionSet++coerceArgumentValues :: MonadCatch m+    => HashMap Full.Name In.Argument+    -> HashMap Full.Name (Full.Node Transform.Input)+    -> m Type.Subs+coerceArgumentValues argumentDefinitions argumentValues =+    HashMap.foldrWithKey c pure argumentDefinitions mempty+  where+    c argumentName argumentType pure' resultMap =+        forEach argumentName argumentType resultMap >>= pure'+    forEach :: MonadCatch m+         => Full.Name+         -> In.Argument+         -> Type.Subs+         -> m Type.Subs+    forEach argumentName (In.Argument _ variableType defaultValue) resultMap = do+        let matchedMap+                = matchFieldValues' argumentName variableType defaultValue+                $ Just resultMap+         in case matchedMap of+            Just matchedValues -> pure matchedValues+            Nothing+                | Just inputValue <- HashMap.lookup argumentName argumentValues+                    -> throwM+                    $ InputCoercionException (Text.unpack argumentName) variableType+                    $ Just inputValue+                | otherwise -> throwM+                    $ InputCoercionException (Text.unpack argumentName) variableType Nothing+    matchFieldValues' = matchFieldValues coerceArgumentValue+        $ Full.node <$> argumentValues+    coerceArgumentValue inputType (Transform.Int integer) =+        coerceInputLiteral inputType (Type.Int integer)+    coerceArgumentValue inputType (Transform.Boolean boolean) =+        coerceInputLiteral inputType (Type.Boolean boolean)+    coerceArgumentValue inputType (Transform.String string) =+        coerceInputLiteral inputType (Type.String string)+    coerceArgumentValue inputType (Transform.Float float) =+        coerceInputLiteral inputType (Type.Float float)+    coerceArgumentValue inputType (Transform.Enum enum) =+        coerceInputLiteral inputType (Type.Enum enum)+    coerceArgumentValue inputType Transform.Null+        | In.isNonNullType inputType = Nothing+        | otherwise = coerceInputLiteral inputType Type.Null+    coerceArgumentValue (In.ListBaseType inputType) (Transform.List list) =+        let coerceItem = coerceArgumentValue inputType+         in Type.List <$> traverse coerceItem list+    coerceArgumentValue (In.InputObjectBaseType inputType) (Transform.Object object)+        | In.InputObjectType _ _ inputFields <- inputType = +            let go = forEachField object+                resultMap = HashMap.foldrWithKey go (pure mempty) inputFields+             in Type.Object <$> resultMap+    coerceArgumentValue _ (Transform.Variable variable) = pure variable+    coerceArgumentValue _ _ = Nothing+    forEachField object variableName (In.InputField _ variableType defaultValue) =+        matchFieldValues coerceArgumentValue object variableName variableType defaultValue++collectFields :: Monad m+    => Out.ObjectType m+    -> Seq (Transform.Selection m)+    -> OrderedMap (NonEmpty (Transform.Field m))+collectFields objectType = foldl forEach OrderedMap.empty+  where+    forEach groupedFields (Transform.FieldSelection fieldSelection) =+        let Transform.Field maybeAlias fieldName _ _ _ = fieldSelection+            responseKey = fromMaybe fieldName maybeAlias+         in OrderedMap.insert responseKey (fieldSelection :| []) groupedFields+    forEach groupedFields (Transform.FragmentSelection selectionFragment)+        | Transform.Fragment fragmentType fragmentSelectionSet _ <- selectionFragment+        , Type.Internal.doesFragmentTypeApply fragmentType objectType =+            let fragmentGroupedFieldSet =+                    collectFields objectType fragmentSelectionSet+             in groupedFields <> fragmentGroupedFieldSet+        | otherwise = groupedFields++coerceVariableValues :: (Monad m, VariableValue b)+    => HashMap Full.Name (Schema.Type m)+    -> Full.OperationDefinition+    -> HashMap Full.Name b+    -> Either QueryError Type.Subs+coerceVariableValues types operationDefinition' variableValues+    | Full.OperationDefinition _ _ variableDefinitions _ _ _ <-+        operationDefinition'+    = foldr forEach (Right HashMap.empty) variableDefinitions+    | otherwise = pure mempty+  where+    forEach variableDefinition (Right coercedValues) =+        let Full.VariableDefinition variableName variableTypeName defaultValue _ =+                variableDefinition+            defaultValue' = constValue . Full.node <$> defaultValue+         in case Type.Internal.lookupInputType variableTypeName types of+            Just variableType ->+                maybe (Left $ CoercionError variableDefinition) Right+                    $ matchFieldValues+                        coerceVariableValue'+                        variableValues+                        variableName+                        variableType+                        defaultValue'+                    $ Just coercedValues+            Nothing -> Left $ UnknownInputType variableDefinition+    forEach _ coercedValuesOrError = coercedValuesOrError+    coerceVariableValue' variableType value'+        = coerceVariableValue variableType value'+        >>= coerceInputLiteral variableType++constValue :: Full.ConstValue -> Type.Value+constValue (Full.ConstInt i) = Type.Int i+constValue (Full.ConstFloat f) = Type.Float f+constValue (Full.ConstString x) = Type.String x+constValue (Full.ConstBoolean b) = Type.Boolean b+constValue Full.ConstNull = Type.Null+constValue (Full.ConstEnum e) = Type.Enum e+constValue (Full.ConstList list) = Type.List $ constValue . Full.node <$> list+constValue (Full.ConstObject o) =+    Type.Object $ HashMap.fromList $ constObjectField <$> o+  where+    constObjectField Full.ObjectField{value = value', ..} =+        (name, constValue $ Full.node value')++subscribe :: (MonadCatch m, Serialize a)+    => Seq (Transform.Selection m)+    -> Schema m+    -> Full.Location+    -> m (Either Error (ResponseEventStream m a))+subscribe fields schema objectLocation+    | Just objectType <- Schema.subscription schema = do+        let types' = Schema.types schema+        sourceStream <-+            createSourceEventStream types' objectType objectLocation fields+        let traverser =+                mapSourceToResponseEvent types' objectType fields+        traverse traverser sourceStream+    | otherwise = pure $ Left+        $ Error "Schema doesn't support subscriptions." [] []++mapSourceToResponseEvent :: (MonadCatch m, Serialize a)     => HashMap Full.Name (Type m)     -> Out.ObjectType m+    -> Seq (Transform.Selection m)+    -> Out.SourceEventStream m+    -> m (ResponseEventStream m a)+mapSourceToResponseEvent types' subscriptionType fields sourceStream+    = pure+    $ sourceStream+    .| mapMC (executeSubscriptionEvent types' subscriptionType fields)++createSourceEventStream :: MonadCatch m+    => HashMap Full.Name (Type m)+    -> Out.ObjectType m     -> Full.Location     -> Seq (Transform.Selection m)-    -> m (Response a)-executeOperation types' objectType objectLocation fields-    = runCollectErrs types'-    $ executeSelectionSet Definition.Null objectType objectLocation fields+    -> 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+            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+    singleError :: [Full.Location] -> String -> Error+    singleError errorLocations message = Error (Text.pack message) errorLocations [] -rightErrorResponse :: Serialize b => forall a. Error -> Either a (Response b)-rightErrorResponse = Right . Response null . pure+resolveFieldEventStream :: MonadCatch m+    => Type.Value+    -> Type.Subs+    -> Out.Subscribe m+    -> m (Either String (Out.SourceEventStream m))+resolveFieldEventStream result args resolver =+    catch (Right <$> runReaderT resolver context) handleEventStreamError+  where+    handleEventStreamError :: MonadCatch m+        => ResolverException+        -> m (Either String (Out.SourceEventStream m))+    handleEventStreamError = pure . Left . displayException+    context = Type.Context+        { Type.arguments = Type.Arguments args+        , Type.values = result+        }++executeSubscriptionEvent :: (MonadCatch m, Serialize a)+    => HashMap Full.Name (Type m)+    -> Out.ObjectType m+    -> Seq (Transform.Selection m)+    -> Type.Value+    -> m (Response a)+executeSubscriptionEvent types' objectType fields initialValue = do+    (data', errors) <- runWriterT+        $ flip runReaderT types'+        $ runExecutorT+        $ catch (executeSelectionSet fields objectType initialValue [])+        handleException+    pure $ Response data' errors
− src/Language/GraphQL/Execute/Execution.hs
@@ -1,253 +0,0 @@-{-# LANGUAGE ExplicitForAll #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-}--module Language.GraphQL.Execute.Execution-    ( coerceArgumentValues-    , collectFields-    , executeSelectionSet-    ) where--import Control.Monad.Catch (Exception(..), MonadCatch(..))-import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.Reader (runReaderT)-import Control.Monad.Trans.State (gets)-import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap-import Data.Maybe (fromMaybe)-import Data.Sequence (Seq(..))-import qualified Data.Text as Text-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-import qualified Language.GraphQL.Type.Out as Out-import qualified Language.GraphQL.Type.Internal as Internal-import Prelude hiding (null)--resolveFieldValue :: MonadCatch m-    => Type.Value-    -> Type.Subs-    -> Type.Resolve m-    -> Full.Location-    -> CollectErrsT m Type.Value-resolveFieldValue result args resolver location' =-    catch (lift $ runReaderT resolver context) handleFieldError-  where-    handleFieldError :: MonadCatch m-        => ResolverException-        -> CollectErrsT m Type.Value-    handleFieldError e-        = addError Type.Null-        $ Error (Text.pack $ displayException e) [location'] []-    context = Type.Context-        { Type.arguments = Type.Arguments args-        , Type.values = result-        }--collectFields :: Monad m-    => Out.ObjectType m-    -> Seq (Transform.Selection m)-    -> OrderedMap (NonEmpty (Transform.Field m))-collectFields objectType = foldl forEach OrderedMap.empty-  where-    forEach groupedFields (Transform.SelectionField field) =-        let responseKey = aliasOrName field-         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 groupedFields <> fragmentGroupedFieldSet-        | otherwise = groupedFields--aliasOrName :: forall m. Transform.Field m -> Full.Name-aliasOrName (Transform.Field alias name _ _ _) = fromMaybe name alias--resolveAbstractType :: Monad m-    => Internal.AbstractType m-    -> Type.Subs-    -> CollectErrsT m (Maybe (Out.ObjectType m))-resolveAbstractType abstractType values'-    | Just (Type.String typeName) <- HashMap.lookup "__typename" values' = do-        types' <- gets types-        case HashMap.lookup typeName types' of-            Just (Internal.ObjectType objectType) ->-                if Internal.instanceOf objectType abstractType-                    then pure $ Just objectType-                    else pure Nothing-            _ -> pure Nothing-    | otherwise = pure Nothing--executeField :: (MonadCatch m, Serialize a)-    => Out.Resolver m-    -> Type.Value-    -> NonEmpty (Transform.Field m)-    -> CollectErrsT m a-executeField fieldResolver prev fields-    | Out.ValueResolver fieldDefinition resolver <- fieldResolver =-        executeField' fieldDefinition resolver-    | Out.EventStreamResolver fieldDefinition resolver _ <- fieldResolver =-        executeField' fieldDefinition resolver-  where-    executeField' fieldDefinition resolver = do-        let Out.Field _ fieldType argumentDefinitions = fieldDefinition-        let Transform.Field _ _ arguments' _ location' = NonEmpty.head fields-        case coerceArgumentValues argumentDefinitions arguments' of-            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)-    => Out.Type m-    -> NonEmpty (Transform.Field m)-    -> Type.Value-    -> CollectErrsT m a-completeValue (Out.isNonNullType -> False) _ Type.Null = pure null-completeValue outputType@(Out.ListBaseType listType) fields (Type.List list)-    = traverse (completeValue listType fields) list-    >>= 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 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 location-                $ mergeSelectionSets fields-            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-                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 =-        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 parentLocation result-    | Just serialized <- serialize outputType result = pure serialized-    | 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--- the resolved 'Transform.Selection', or a null value and error information.-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) objectLocation selectionSet = do-    let fields = collectFields objectType selectionSet-    resolvedValues <- OrderedMap.traverseMaybe forEach fields-    coerceResult (Out.NonNullObjectType objectType) objectLocation-        $ Object resolvedValues-  where-    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 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 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) =-        coerceInputLiteral inputType (Type.Boolean boolean)-    coerceArgumentValue inputType (Transform.String string) =-        coerceInputLiteral inputType (Type.String string)-    coerceArgumentValue inputType (Transform.Float float) =-        coerceInputLiteral inputType (Type.Float float)-    coerceArgumentValue inputType (Transform.Enum enum) =-        coerceInputLiteral inputType (Type.Enum enum)-    coerceArgumentValue inputType Transform.Null-        | In.isNonNullType inputType = Nothing-        | otherwise = coerceInputLiteral inputType Type.Null-    coerceArgumentValue (In.ListBaseType inputType) (Transform.List list) =-        let coerceItem = coerceInputLiteral inputType-         in Type.List <$> traverse coerceItem list-    coerceArgumentValue (In.InputObjectBaseType inputType) (Transform.Object object)-        | In.InputObjectType _ _ inputFields <- inputType = -            let go = forEachField object-                resultMap = HashMap.foldrWithKey go (pure mempty) inputFields-             in Type.Object <$> resultMap-    coerceArgumentValue _ (Transform.Variable variable) = pure variable-    coerceArgumentValue _ _ = Nothing-    forEachField object variableName (In.InputField _ variableType defaultValue) =-        matchFieldValues coerceArgumentValue object variableName variableType defaultValue
− src/Language/GraphQL/Execute/Internal.hs
@@ -1,31 +0,0 @@-{- This Source Code Form is subject to the terms of the Mozilla Public License,-   v. 2.0. If a copy of the MPL was not distributed with this file, You can-   obtain one at https://mozilla.org/MPL/2.0/. -}--{-# LANGUAGE 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/Subscribe.hs
@@ -1,113 +0,0 @@-{- This Source Code Form is subject to the terms of the Mozilla Public License,-   v. 2.0. If a copy of the MPL was not distributed with this file, You can-   obtain one at https://mozilla.org/MPL/2.0/. -}--{-# LANGUAGE ExplicitForAll #-}-{-# LANGUAGE OverloadedStrings #-}-module Language.GraphQL.Execute.Subscribe-    ( subscribe-    ) 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.List.NonEmpty as NonEmpty-import Data.Sequence (Seq(..))-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--subscribe :: (MonadCatch m, Serialize a)-    => HashMap Full.Name (Type m)-    -> Out.ObjectType m-    -> Full.Location-    -> Seq (Transform.Selection m)-    -> 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 Full.Name (Type m)-    -> Out.ObjectType m-    -> Full.Location-    -> Seq (Transform.Selection m)-    -> Out.SourceEventStream m-    -> m (ResponseEventStream m a)-mapSourceToResponseEvent types' subscriptionType objectLocation fields sourceStream-    = pure-    $ sourceStream-    .| mapMC (executeSubscriptionEvent types' subscriptionType objectLocation fields)--createSourceEventStream :: MonadCatch m-    => HashMap Full.Name (Type m)-    -> Out.ObjectType m-    -> Full.Location-    -> Seq (Transform.Selection m)-    -> 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-            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--resolveFieldEventStream :: MonadCatch m-    => Type.Value-    -> Type.Subs-    -> Out.Subscribe m-    -> m (Either String (Out.SourceEventStream m))-resolveFieldEventStream result args resolver =-    catch (Right <$> runReaderT resolver context) handleEventStreamError-  where-    handleEventStreamError :: MonadCatch m-        => ResolverException-        -> m (Either String (Out.SourceEventStream m))-    handleEventStreamError = pure . Left . displayException-    context = Type.Context-        { Type.arguments = Type.Arguments args-        , Type.values = result-        }--executeSubscriptionEvent :: (MonadCatch m, Serialize a)-    => HashMap Full.Name (Type m)-    -> Out.ObjectType m-    -> Full.Location-    -> Seq (Transform.Selection m)-    -> Definition.Value-    -> m (Response a)-executeSubscriptionEvent types' objectType objectLocation fields initialValue-    = runCollectErrs types'-    $ executeSelectionSet initialValue objectType objectLocation fields
src/Language/GraphQL/Execute/Transform.hs view
@@ -6,7 +6,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-}+{-# LANGUAGE NamedFieldPuns #-}  -- | After the document is parsed, before getting executed, the AST is -- transformed into a similar, simpler AST. Performed transformations include:@@ -21,65 +21,87 @@ -- This module is also responsible for smaller rewrites that touch only parts of -- the original AST. module Language.GraphQL.Execute.Transform-    ( Document(..)-    , Field(..)+    ( Field(..)     , Fragment(..)     , Input(..)     , Operation(..)-    , QueryError(..)+    , Replacement(..)     , Selection(..)+    , TransformT(..)     , document+    , transform     ) where -import Control.Monad (foldM, unless)-import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.State (State, evalStateT, gets, modify)-import Data.Foldable (find)-import Data.Functor.Identity (Identity(..))+import Control.Monad (foldM)+import Control.Monad.Catch (MonadCatch(..), MonadThrow(..))+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Reader (ReaderT(..), local)+import qualified Control.Monad.Trans.Reader as Reader+import Data.Bifunctor (first)+import Data.Functor ((<&>)) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap+import Data.HashSet (HashSet)+import qualified Data.HashSet as HashSet import Data.Int (Int32)-import Data.Maybe (fromMaybe)-import Data.List.NonEmpty (NonEmpty(..))+import Data.List (intercalate) import qualified Data.List.NonEmpty as NonEmpty-import Data.Sequence (Seq, (<|), (><))+import Data.Maybe (fromMaybe, isJust)+import Data.Sequence (Seq, (><))+import qualified Data.Sequence as Seq import Data.Text (Text) import qualified Data.Text as Text-import qualified Language.GraphQL.AST as Full-import Language.GraphQL.AST (Name)-import qualified Language.GraphQL.Execute.Coerce as Coerce-import qualified Language.GraphQL.Type.Definition as Definition+import qualified Language.GraphQL.AST.Document as Full+import Language.GraphQL.Type.Schema (Type) import qualified Language.GraphQL.Type as Type+import qualified Language.GraphQL.Type.Definition as Definition import qualified Language.GraphQL.Type.Internal as Type-import qualified Language.GraphQL.Type.Out as Out-import qualified Language.GraphQL.Type.Schema as Schema+import Numeric (showFloat)  -- | Associates a fragment name with a list of 'Field's. data Replacement m = Replacement-    { fragments :: HashMap Full.Name (Fragment m)-    , fragmentDefinitions :: FragmentDefinitions-    , variableValues :: Type.Subs-    , types :: HashMap Full.Name (Schema.Type m)+    { variableValues :: Type.Subs+    , fragmentDefinitions :: HashMap Full.Name Full.FragmentDefinition+    , visitedFragments :: HashSet Full.Name+    , types :: HashMap Full.Name (Type m)     } -type FragmentDefinitions = HashMap Full.Name Full.FragmentDefinition+newtype TransformT m a = TransformT+    { runTransformT :: ReaderT (Replacement m) m a+    } --- | Represents fragments and inline fragments.-data Fragment m-    = Fragment (Type.CompositeType m) (Seq (Selection m))+instance Functor m => Functor (TransformT m) where+    fmap f = TransformT . fmap f . runTransformT --- | Single selection element.-data Selection m-    = SelectionFragment (Fragment m)-    | SelectionField (Field m)+instance Applicative m => Applicative (TransformT m) where+    pure = TransformT . pure+    TransformT f <*> TransformT x = TransformT $ f <*> x +instance Monad m => Monad (TransformT m) where+    TransformT x >>= f = TransformT $ x >>= runTransformT . f++instance MonadTrans TransformT where+    lift = TransformT . lift++instance MonadThrow m => MonadThrow (TransformT m) where+    throwM = lift . throwM++instance MonadCatch m => MonadCatch (TransformT m) where+  catch (TransformT stack) handler =+      TransformT $ catch stack $ runTransformT . handler++asks :: Monad m => forall a. (Replacement m -> a) -> TransformT m a+asks = TransformT . Reader.asks+ -- | GraphQL has 3 operation types: queries, mutations and subscribtions. data Operation 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+    = Operation Full.OperationType (Seq (Selection m)) Full.Location --- | Single GraphQL field.+-- | Field or inlined fragment.+data Selection m+    = FieldSelection (Field m)+    | FragmentSelection (Fragment m)+ data Field m = Field     (Maybe Full.Name)     Full.Name@@ -87,339 +109,217 @@     (Seq (Selection m))     Full.Location --- | Contains the operation to be executed along with its root type.-data Document m = Document-    (HashMap Full.Name (Schema.Type m)) (Out.ObjectType m) (Operation m)--data OperationDefinition = OperationDefinition-    Full.OperationType-    (Maybe Full.Name)-    [Full.VariableDefinition]-    [Full.Directive]-    Full.SelectionSet-    Full.Location---- | Query error types.-data QueryError-    = OperationNotFound Text-    | OperationNameRequired-    | CoercionError-    | 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 Fragment m = Fragment+    (Type.CompositeType m) (Seq (Selection m)) Full.Location  data Input-    = Int Int32+    = Variable Type.Value+    | Int Int32     | Float Double     | String Text     | Boolean Bool     | Null-    | Enum Name-    | List [Type.Value]-    | Object (HashMap Name Input)-    | Variable Type.Value-    deriving (Eq, Show)--getOperation-    :: Maybe Full.Name-    -> NonEmpty OperationDefinition-    -> Either QueryError OperationDefinition-getOperation Nothing (operation' :| []) = pure operation'-getOperation Nothing _ = Left OperationNameRequired-getOperation (Just operationName) operations-    | Just operation' <- find matchingName operations = pure operation'-    | otherwise = Left $ OperationNotFound operationName-  where-    matchingName (OperationDefinition _ name _ _ _ _) =-        name == Just operationName--coerceVariableValues :: Coerce.VariableValue a-    => forall m-    . HashMap Full.Name (Schema.Type m)-    -> OperationDefinition-    -> HashMap.HashMap Full.Name a-    -> Either QueryError Type.Subs-coerceVariableValues types operationDefinition variableValues =-    let OperationDefinition _ _ variableDefinitions _ _ _ = operationDefinition-     in maybe (Left CoercionError) Right-        $ foldr forEach (Just HashMap.empty) variableDefinitions-  where-    forEach variableDefinition coercedValues = do-        let Full.VariableDefinition variableName variableTypeName defaultValue _ =-                variableDefinition-        let defaultValue' = constValue . Full.node <$> defaultValue-        variableType <- Type.lookupInputType variableTypeName types+    | Enum Full.Name+    | List [Input]+    | Object (HashMap Full.Name Input)+    deriving Eq -        Coerce.matchFieldValues-            coerceVariableValue'-            variableValues-            variableName-            variableType-            defaultValue'-            coercedValues-    coerceVariableValue' variableType value'-        = Coerce.coerceVariableValue variableType value'-        >>= Coerce.coerceInputLiteral variableType+instance Show Input where+    showList = mappend . showList'+      where+        showList' list = "[" ++ intercalate ", " (show <$> list) ++ "]"+    show (Int integer) = show integer+    show (Float float') = showFloat float' mempty+    show (String text) = "\"" <> Text.foldr (mappend . Full.escape) "\"" text+    show (Boolean boolean') = show boolean'+    show Null = "null"+    show (Enum name) = Text.unpack name+    show (List list) = show list+    show (Object fields) = unwords+        [ "{"+        , intercalate ", " (HashMap.foldrWithKey showObject [] fields)+        , "}"+        ]+      where+        showObject key value accumulator =+            concat [Text.unpack key, ": ", show value] : accumulator+    show variableValue = show variableValue -constValue :: Full.ConstValue -> Type.Value-constValue (Full.ConstInt i) = Type.Int i-constValue (Full.ConstFloat f) = Type.Float f-constValue (Full.ConstString x) = Type.String x-constValue (Full.ConstBoolean b) = Type.Boolean b-constValue Full.ConstNull = Type.Null-constValue (Full.ConstEnum e) = Type.Enum e-constValue (Full.ConstList list) = Type.List $ constValue . Full.node <$> list-constValue (Full.ConstObject o) =-    Type.Object $ HashMap.fromList $ constObjectField <$> o+-- | Extracts operations and fragment definitions of the document.+document :: Full.Document+    -> ([Full.OperationDefinition], HashMap Full.Name Full.FragmentDefinition)+document = foldr filterOperation ([], HashMap.empty)   where-    constObjectField Full.ObjectField{value = value', ..} =-        (name, constValue $ Full.node value')+    filterOperation (Full.ExecutableDefinition executableDefinition) accumulator+        | Full.DefinitionOperation operationDefinition' <- executableDefinition =+            first (operationDefinition' :) accumulator+        | Full.DefinitionFragment fragmentDefinition <- executableDefinition+        , Full.FragmentDefinition fragmentName _ _ _ _ <- fragmentDefinition =+            HashMap.insert fragmentName fragmentDefinition <$> accumulator+    filterOperation _ accumulator = accumulator -- Type system definitions.  -- | Rewrites the original syntax tree into an intermediate representation used--- for query execution.-document :: Coerce.VariableValue a-    => forall m-    . Type.Schema m-    -> Maybe Full.Name-    -> HashMap Full.Name a-    -> Full.Document-    -> Either QueryError (Document m)-document schema operationName subs ast = do-    let referencedTypes = Schema.types schema--    (operations, fragmentTable) <- defragment ast-    chosenOperation <- getOperation operationName operations-    coercedValues <- coerceVariableValues referencedTypes chosenOperation subs+-- for the query execution.+transform :: Monad m => Full.OperationDefinition -> TransformT m (Operation m)+transform (Full.OperationDefinition operationType _ _ _ selectionSet' operationLocation) = do+    transformedSelections <- selectionSet selectionSet'+    pure $ Operation operationType transformedSelections operationLocation+transform (Full.SelectionSet selectionSet' operationLocation) = do+    transformedSelections <- selectionSet selectionSet'+    pure $ Operation Full.Query transformedSelections operationLocation -    let replacement = Replacement-            { fragments = HashMap.empty-            , fragmentDefinitions = fragmentTable-            , variableValues = coercedValues-            , types = referencedTypes-            }-    case chosenOperation of-        OperationDefinition Full.Query _ _ _ _ _ ->-            pure $ Document referencedTypes (Schema.query schema)-                $ operation chosenOperation replacement-        OperationDefinition Full.Mutation _ _ _ _ _-            | Just mutationType <- Schema.mutation schema ->-                pure $ Document referencedTypes mutationType-                    $ operation chosenOperation replacement-        OperationDefinition Full.Subscription _ _ _ _ _-            | Just subscriptionType <- Schema.subscription schema ->-                pure $ Document referencedTypes subscriptionType-                    $ operation chosenOperation replacement-        _ -> Left UnsupportedRootOperation+selectionSet :: Monad m => Full.SelectionSet -> TransformT m (Seq (Selection m))+selectionSet = selectionSetOpt . NonEmpty.toList -defragment-    :: Full.Document-    -> Either QueryError (NonEmpty OperationDefinition, FragmentDefinitions)-defragment ast =-    let (operations, fragmentTable) = foldr defragment' ([], HashMap.empty) ast-        nonEmptyOperations = NonEmpty.nonEmpty operations-        emptyDocument = Left EmptyDocument-     in (, fragmentTable) <$> maybe emptyDocument Right nonEmptyOperations+selectionSetOpt :: Monad m => Full.SelectionSetOpt -> TransformT m (Seq (Selection m))+selectionSetOpt = foldM go Seq.empty   where-    defragment' definition (operations, fragments')-        | (Full.ExecutableDefinition executable) <- definition-        , (Full.DefinitionOperation operation') <- executable =-            (transform operation' : operations, fragments')-        | (Full.ExecutableDefinition executable) <- definition-        , (Full.DefinitionFragment fragment) <- executable-        , (Full.FragmentDefinition name _ _ _ _) <- fragment =-            (operations, HashMap.insert name fragment fragments')-    defragment' _ acc = acc-    transform = \case-        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+    go accumulatedSelections currentSelection =+        selection currentSelection <&> (accumulatedSelections ><) --- * Operation+selection :: Monad m => Full.Selection -> TransformT m (Seq (Selection m))+selection (Full.FieldSelection field') =+    maybeToSelectionSet FieldSelection $ field field'+selection (Full.FragmentSpreadSelection fragmentSpread') =+    maybeToSelectionSet FragmentSelection $ fragmentSpread fragmentSpread'+selection (Full.InlineFragmentSelection inlineFragment') =+    either id (pure . FragmentSelection) <$> inlineFragment inlineFragment' -operation :: OperationDefinition -> Replacement m -> Operation m-operation operationDefinition replacement-    = runIdentity-    $ evalStateT (collectFragments >> transform operationDefinition) replacement-  where-    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+maybeToSelectionSet :: Monad m+    => forall a+    . (a -> Selection m)+    -> TransformT m (Maybe a)+    -> TransformT m (Seq (Selection m))+maybeToSelectionSet selectionType = fmap (maybe Seq.empty $ pure . selectionType) --- * Selection+directives :: Monad m => [Full.Directive] -> TransformT m (Maybe [Definition.Directive])+directives = fmap Type.selection . traverse directive -selection-    :: Full.Selection-    ->  State (Replacement m) (Either (Seq (Selection m)) (Selection m))-selection (Full.FieldSelection fieldSelection) =-    maybe (Left mempty) (Right . SelectionField) <$> field fieldSelection-selection (Full.FragmentSpreadSelection fragmentSelection)-    = maybe (Left mempty) (Right . SelectionFragment)-    <$> fragmentSpread fragmentSelection-selection (Full.InlineFragmentSelection fragmentSelection) =-    inlineFragment fragmentSelection+inlineFragment :: Monad m+    => Full.InlineFragment+    -> TransformT m (Either (Seq (Selection m)) (Fragment m))+inlineFragment (Full.InlineFragment maybeCondition directives' selectionSet' location)+    | Just typeCondition <- maybeCondition = do+        transformedSelections <- selectionSet selectionSet'+        transformedDirectives <- directives directives'+        maybeFragmentType <- asks+            $ Type.lookupTypeCondition typeCondition+            . types+        pure $ case transformedDirectives >> maybeFragmentType of+            Just fragmentType -> Right+                $ Fragment fragmentType transformedSelections location+            Nothing -> Left Seq.empty+    | otherwise = do+        transformedSelections <- selectionSet selectionSet'+        transformedDirectives <- directives directives'+        pure $ if isJust transformedDirectives+            then Left transformedSelections+            else Left Seq.empty -field :: Full.Field ->  State (Replacement m) (Maybe (Field m))-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 location-    pure $ field' <$ fieldDirectives+fragmentSpread :: Monad m => Full.FragmentSpread -> TransformT m (Maybe (Fragment m))+fragmentSpread (Full.FragmentSpread spreadName directives' location) = do+    transformedDirectives <- directives directives'+    visitedFragment <- asks $ HashSet.member spreadName . visitedFragments+    possibleFragmentDefinition <- asks+        $ HashMap.lookup spreadName+        . fragmentDefinitions+    case transformedDirectives >> possibleFragmentDefinition of+        Just (Full.FragmentDefinition _ typeCondition _ selections _)+            | visitedFragment -> pure Nothing+            | otherwise -> do+                fragmentType <- asks+                    $ Type.lookupTypeCondition typeCondition+                    . types+                traverse (traverseSelections selections) fragmentType+        Nothing -> pure Nothing   where-    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-    -> State (Replacement m) (Maybe (Fragment m))-fragmentSpread (Full.FragmentSpread name directives' _) = do-    spreadDirectives <- Definition.selection <$> directives directives'-    fragments' <- gets fragments--    fragmentDefinitions' <- gets fragmentDefinitions-    case HashMap.lookup name fragments' of-        Just definition -> lift $ pure $ definition <$ spreadDirectives-        Nothing-            | Just definition <- HashMap.lookup name fragmentDefinitions' -> do-                fragDef <- fragmentDefinition definition-                case fragDef of-                    Just fragment -> lift $ pure $ fragment <$ spreadDirectives-                    _ -> lift $ pure  Nothing-            | otherwise -> lift $ pure  Nothing+    traverseSelections selections typeCondition = do+        transformedSelections <- TransformT+            $ local fragmentInserter+            $ runTransformT+            $ selectionSet selections+        pure $ Fragment typeCondition transformedSelections location+    fragmentInserter replacement@Replacement{ visitedFragments } = replacement+        { visitedFragments = HashSet.insert spreadName visitedFragments } -inlineFragment-    :: Full.InlineFragment-    ->  State (Replacement m) (Either (Seq (Selection m)) (Selection m))-inlineFragment (Full.InlineFragment type' directives' selections _) = do-    fragmentDirectives <- Definition.selection <$> directives directives'-    case fragmentDirectives of-        Nothing -> pure $ Left mempty-        _ -> do-            fragmentSelectionSet <- appendSelection selections+field :: Monad m => Full.Field -> TransformT m (Maybe (Field m))+field (Full.Field alias' name' arguments' directives' selectionSet' location') = do+    transformedSelections <- selectionSetOpt selectionSet'+    transformedDirectives <- directives directives'+    transformedArguments <- arguments arguments'+    let transformedField = Field+            alias'+            name'+            transformedArguments+            transformedSelections+            location'+    pure $ transformedDirectives >> pure transformedField -            case type' of-                Nothing -> pure $ Left fragmentSelectionSet-                Just typeName -> do-                    types' <- gets types-                    case Type.lookupTypeCondition typeName types' of-                        Just typeCondition -> pure $-                            selectionFragment typeCondition fragmentSelectionSet-                        Nothing -> pure $ Left mempty+arguments :: Monad m => [Full.Argument] -> TransformT m (HashMap Full.Name (Full.Node Input))+arguments = foldM go HashMap.empty   where-    selectionFragment typeName = Right-        . SelectionFragment-        . Fragment typeName+    go accumulator (Full.Argument name' valueNode argumentLocation) = do+        let replaceLocation = flip Full.Node argumentLocation . Full.node+        argumentValue <- fmap replaceLocation <$> node valueNode+        pure $ insertIfGiven name' argumentValue accumulator -appendSelection :: Traversable t-    => t Full.Selection-    ->  State (Replacement m) (Seq (Selection m))-appendSelection = foldM go mempty+directive :: Monad m => Full.Directive -> TransformT m Definition.Directive+directive (Full.Directive name' arguments' _)+    = Definition.Directive name'+    . Type.Arguments+    <$> foldM go HashMap.empty arguments'   where-    go acc sel = append acc <$> selection sel-    append acc (Left list) = list >< acc-    append acc (Right one) = one <| acc+    go accumulator (Full.Argument argumentName Full.Node{ node = node' } _) = do+        transformedValue <- directiveValue node'+        pure $ HashMap.insert argumentName transformedValue accumulator -directives :: [Full.Directive] ->  State (Replacement m) [Definition.Directive]-directives = traverse directive+directiveValue :: Monad m => Full.Value -> TransformT m Type.Value+directiveValue = \case+    (Full.Variable name') -> asks+        $ HashMap.lookupDefault Type.Null name'+        . variableValues+    (Full.Int integer) -> pure $ Type.Int integer+    (Full.Float double) -> pure $ Type.Float double+    (Full.String string) -> pure $ Type.String string+    (Full.Boolean boolean) -> pure $ Type.Boolean boolean+    Full.Null -> pure Type.Null+    (Full.Enum enum) -> pure $ Type.Enum enum+    (Full.List list) -> Type.List <$> traverse directiveNode list+    (Full.Object objectFields) ->+        Type.Object <$> foldM objectField HashMap.empty objectFields   where-    directive (Full.Directive directiveName directiveArguments _)-        = Definition.Directive directiveName . Type.Arguments-        <$> foldM go HashMap.empty directiveArguments-    go arguments (Full.Argument name (Full.Node value' _) _) = do-        substitutedValue <- value value'-        return $ HashMap.insert name substitutedValue arguments---- * Fragment replacement---- | Extract fragment definitions into a single 'HashMap'.-collectFragments ::  State (Replacement m) ()-collectFragments = do-    fragDefs <- gets fragmentDefinitions-    let nextValue = head $ HashMap.elems fragDefs-    unless (HashMap.null fragDefs) $ do-        _ <- fragmentDefinition nextValue-        collectFragments--fragmentDefinition-    :: Full.FragmentDefinition-    ->  State (Replacement m) (Maybe (Fragment m))-fragmentDefinition (Full.FragmentDefinition name type' _ selections _) = do-    modify deleteFragmentDefinition-    fragmentSelection <- appendSelection selections-    types' <- gets types+    directiveNode Full.Node{ node = node'} = directiveValue node'+    objectField accumulator Full.ObjectField{ name, value } = do+        transformedValue <- directiveNode value+        pure $ HashMap.insert name transformedValue accumulator -    case Type.lookupTypeCondition type' types' of-        Just compositeType -> do-            let newValue = Fragment compositeType fragmentSelection-            modify $ insertFragment newValue-            lift $ pure $ Just newValue-        _ -> lift $ pure Nothing+input :: Monad m => Full.Value -> TransformT m (Maybe Input)+input (Full.Variable name') =+    asks (HashMap.lookup name' . variableValues) <&> fmap Variable+input (Full.Int integer) = pure $ Just $ Int integer+input (Full.Float double) = pure $ Just $ Float double+input (Full.String string) = pure $ Just $ String string+input (Full.Boolean boolean) = pure $ Just $ Boolean boolean+input Full.Null = pure $ Just Null+input (Full.Enum enum) = pure $ Just $ Enum enum+input (Full.List list) = Just . List+    <$> traverse (fmap (fromMaybe Null) . input . Full.node) list+input (Full.Object objectFields) = Just . Object+    <$> foldM objectField HashMap.empty objectFields   where-    deleteFragmentDefinition replacement@Replacement{..} =-        let newDefinitions = HashMap.delete name fragmentDefinitions-         in replacement{ fragmentDefinitions = newDefinitions }-    insertFragment newValue replacement@Replacement{..} =-        let newFragments = HashMap.insert name newValue fragments-         in replacement{ fragments = newFragments }+    objectField accumulator Full.ObjectField{..} = do+        objectFieldValue <- fmap Full.node <$> node value+        pure $ insertIfGiven name objectFieldValue accumulator -value :: forall m. Full.Value -> State (Replacement m) Type.Value-value (Full.Variable name) =-    gets (fromMaybe Type.Null . HashMap.lookup name . variableValues)-value (Full.Int int) = pure $ Type.Int int-value (Full.Float float) = pure $ Type.Float float-value (Full.String string) = pure $ Type.String string-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 . Full.node) list-value (Full.Object object) =-    Type.Object . HashMap.fromList <$> traverse objectField object-  where-    objectField Full.ObjectField{value = value', ..} =-        (name,) <$> value (Full.node value')+insertIfGiven :: forall a+    . Full.Name+    -> Maybe a+    -> HashMap Full.Name a+    -> HashMap Full.Name a+insertIfGiven name (Just v) = HashMap.insert name v+insertIfGiven _ _ = id -input :: forall m. Full.Value -> State (Replacement m) (Maybe Input)-input (Full.Variable name) =-    gets (fmap Variable . HashMap.lookup name . variableValues)-input (Full.Int int) = pure $ pure $ Int int-input (Full.Float float) = pure $ pure $ Float float-input (Full.String string) = pure $ pure $ String string-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 . Full.node) list-input (Full.Object object) = do-    objectFields <- foldM objectField HashMap.empty object-    pure $ pure $ Object objectFields-  where-    objectField resultMap Full.ObjectField{value = value', ..} =-        inputField resultMap name $ Full.node value'+node :: Monad m => Full.Node Full.Value -> TransformT m (Maybe (Full.Node Input))+node Full.Node{node = node', ..} =+    traverse Full.Node <$> input node' <*> pure location -inputField :: forall m-    . HashMap Full.Name Input-    -> Full.Name-    -> Full.Value-    -> State (Replacement m) (HashMap Full.Name Input)-inputField resultMap name value' = do-    objectFieldValue <-  input value'-    case objectFieldValue of-        Just fieldValue -> pure $ HashMap.insert name fieldValue resultMap-        Nothing -> pure resultMap
+ src/Language/GraphQL/TH.hs view
@@ -0,0 +1,38 @@+{- 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/. -}++-- | Template Haskell helpers.+module Language.GraphQL.TH+    ( gql+    ) where++import Language.Haskell.TH.Quote (QuasiQuoter(..))+import Language.Haskell.TH (Exp(..), Lit(..))++stripIndentation :: String -> String+stripIndentation code = reverse+    $ dropNewlines+    $ reverse+    $ unlines+    $ indent spaces <$> lines withoutLeadingNewlines+  where+    indent 0 xs = xs+    indent count (' ' : xs) = indent (count - 1) xs+    indent _ xs = xs+    withoutLeadingNewlines = dropNewlines code+    dropNewlines = dropWhile (== '\n')+    spaces = length $ takeWhile (== ' ') withoutLeadingNewlines++-- | Removes leading and trailing newlines. Indentation of the first line is+-- removed from each line of the string.+gql :: QuasiQuoter+gql = QuasiQuoter+    { quoteExp = pure . LitE . StringL . stripIndentation+    , quotePat = const+        $ fail "Illegal gql QuasiQuote (allowed as expression only, used as a pattern)"+    , quoteType = const+        $ fail "Illegal gql QuasiQuote (allowed as expression only, used as a type)"+    , quoteDec = const+        $ fail "Illegal gql QuasiQuote (allowed as expression only, used as a declaration)"+    }
src/Language/GraphQL/Type/Definition.hs view
@@ -1,4 +1,9 @@+{- 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 #-}+{-# LANGUAGE Safe #-}  -- | Types that can be used as both input and output types. module Language.GraphQL.Type.Definition@@ -20,10 +25,12 @@ import Data.Int (Int32) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap+import Data.List (intercalate) import Data.String (IsString(..)) import Data.Text (Text) import qualified Data.Text as Text-import Language.GraphQL.AST (Name)+import Language.GraphQL.AST (Name, escape)+import Numeric (showFloat) import Prelude hiding (id)  -- | Represents accordingly typed GraphQL values.@@ -36,7 +43,27 @@     | Enum Name     | List [Value] -- ^ Arbitrary nested list.     | Object (HashMap Name Value)-    deriving (Eq, Show)+    deriving Eq++instance Show Value where+    showList = mappend . showList'+      where+        showList' list = "[" ++ intercalate ", " (show <$> list) ++ "]"+    show (Int integer) = show integer+    show (Float float') = showFloat float' mempty+    show (String text) = "\"" <> Text.foldr (mappend . escape) "\"" text+    show (Boolean boolean') = show boolean'+    show Null = "null"+    show (Enum name) = Text.unpack name+    show (List list) = show list+    show (Object fields) = unwords+        [ "{"+        , intercalate ", " (HashMap.foldrWithKey showObject [] fields)+        , "}"+        ]+      where+        showObject key value accumulator =+            concat [Text.unpack key, ": ", show value] : accumulator  instance IsString Value where     fromString = String . fromString
src/Language/GraphQL/Type/In.hs view
@@ -3,6 +3,7 @@    obtain one at https://mozilla.org/MPL/2.0/. -}  {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE Safe #-} {-# LANGUAGE ViewPatterns #-}  -- | Input types and values.
tests/Language/GraphQL/AST/EncoderSpec.hs view
@@ -6,10 +6,10 @@  import qualified Language.GraphQL.AST.Document as Full import Language.GraphQL.AST.Encoder+import Language.GraphQL.TH import Test.Hspec (Spec, context, describe, it, shouldBe, shouldStartWith, shouldEndWith, shouldNotContain) import Test.QuickCheck (choose, oneof, forAll)-import Text.RawString.QQ (r)-import Data.Text.Lazy (cons, toStrict, unpack)+import qualified Data.Text.Lazy as Text.Lazy  spec :: Spec spec = do@@ -48,23 +48,32 @@             it "uses strings for short string values" $                 value pretty (Full.String "Short text") `shouldBe` "\"Short text\""             it "uses block strings for text with new lines, with newline symbol" $-                value pretty (Full.String "Line 1\nLine 2")-                    `shouldBe` [r|"""-  Line 1-  Line 2-"""|]+                let expected = [gql|+                  """+                    Line 1+                    Line 2+                  """+                |]+                    actual = value pretty $ Full.String "Line 1\nLine 2"+                 in actual `shouldBe` expected             it "uses block strings for text with new lines, with CR symbol" $-                value pretty (Full.String "Line 1\rLine 2")-                    `shouldBe` [r|"""-  Line 1-  Line 2-"""|]+                let expected = [gql|+                  """+                    Line 1+                    Line 2+                  """+                |]+                    actual = value pretty $ Full.String "Line 1\rLine 2"+                 in actual `shouldBe` expected             it "uses block strings for text with new lines, with CR symbol followed by newline" $-                value pretty (Full.String "Line 1\r\nLine 2")-                    `shouldBe` [r|"""-  Line 1-  Line 2-"""|]+                let expected = [gql|+                  """+                    Line 1+                    Line 2+                  """+                |]+                    actual = value pretty $ Full.String "Line 1\r\nLine 2"+                 in actual `shouldBe` expected             it "encodes as one line string if has escaped symbols" $ do                 let                   genNotAllowedSymbol = oneof@@ -76,48 +85,74 @@                  forAll genNotAllowedSymbol $ \x -> do                     let-                      rawValue = "Short \n" <> cons x "text"-                      encoded = value pretty (Full.String $ toStrict rawValue)-                    shouldStartWith (unpack encoded) "\""-                    shouldEndWith (unpack encoded) "\""-                    shouldNotContain (unpack encoded) "\"\"\""+                      rawValue = "Short \n" <> Text.Lazy.cons x "text"+                      encoded = value pretty+                          $ Full.String $ Text.Lazy.toStrict rawValue+                    shouldStartWith (Text.Lazy.unpack encoded) "\""+                    shouldEndWith (Text.Lazy.unpack encoded) "\""+                    shouldNotContain (Text.Lazy.unpack encoded) "\"\"\"" -            it "Hello world" $ value pretty (Full.String "Hello,\n  World!\n\nYours,\n  GraphQL.")-              `shouldBe` [r|"""-  Hello,-    World!+            it "Hello world" $+                let actual = value pretty+                        $ Full.String "Hello,\n  World!\n\nYours,\n  GraphQL."+                    expected = [gql|+                      """+                        Hello,+                          World! -  Yours,-    GraphQL.-"""|]+                        Yours,+                          GraphQL.+                      """+                    |]+                  in actual `shouldBe` expected -            it "has only newlines" $ value pretty (Full.String "\n") `shouldBe` [r|"""+            it "has only newlines" $+                let actual = value pretty $ Full.String "\n"+                    expected = [gql|+                      """  -"""|]+                      """+                    |]+                 in actual `shouldBe` expected             it "has newlines and one symbol at the begining" $-              value pretty (Full.String "a\n\n") `shouldBe` [r|"""-  a+                let actual = value pretty $ Full.String "a\n\n"+                    expected = [gql|+                      """+                        a  -"""|]+                      """|]+                 in actual `shouldBe` expected             it "has newlines and one symbol at the end" $-              value pretty (Full.String "\n\na") `shouldBe` [r|"""+                let actual = value pretty $ Full.String "\n\na"+                    expected = [gql|+                      """  -  a-"""|]+                        a+                      """+                    |]+                 in actual `shouldBe` expected             it "has newlines and one symbol in the middle" $-              value pretty (Full.String "\na\n") `shouldBe` [r|"""+                let actual = value pretty $ Full.String "\na\n"+                    expected = [gql|+                      """ -  a+                        a -"""|]-            it "skip trailing whitespaces" $ value pretty (Full.String "  Short\ntext    ")-              `shouldBe` [r|"""-  Short-  text-"""|]+                      """+                    |]+                 in actual `shouldBe` expected+            it "skip trailing whitespaces" $+                let actual = value pretty $ Full.String "  Short\ntext    "+                    expected = [gql|+                      """+                        Short+                        text+                      """+                    |]+                 in actual `shouldBe` expected      describe "definition" $         it "indents block strings in arguments" $@@ -128,10 +163,13 @@                 fieldSelection = pure $ Full.FieldSelection field                 operation = Full.DefinitionOperation                     $ Full.SelectionSet fieldSelection location-             in definition pretty operation `shouldBe` [r|{-  field(message: """-    line1-    line2-  """)-}-|]+                expected = Text.Lazy.snoc [gql|+                  {+                    field(message: """+                      line1+                      line2+                    """)+                  }+                |] '\n'+                actual = definition pretty operation+             in actual `shouldBe` expected
tests/Language/GraphQL/AST/LexerSpec.hs view
@@ -7,10 +7,10 @@ import Data.Text (Text) import Data.Void (Void) import Language.GraphQL.AST.Lexer+import Language.GraphQL.TH import Test.Hspec (Spec, context, describe, it) import Test.Hspec.Megaparsec (shouldParse, shouldFailOn, shouldSucceedOn) import Text.Megaparsec (ParseErrorBundle, parse)-import Text.RawString.QQ (r)  spec :: Spec spec = describe "Lexer" $ do@@ -19,32 +19,32 @@             parse unicodeBOM "" `shouldSucceedOn` "\xfeff"          it "lexes strings" $ do-            parse string "" [r|"simple"|] `shouldParse` "simple"-            parse string "" [r|" white space "|] `shouldParse` " white space "-            parse string "" [r|"quote \""|] `shouldParse` [r|quote "|]-            parse string "" [r|"escaped \n"|] `shouldParse` "escaped \n"-            parse string "" [r|"slashes \\ \/"|] `shouldParse` [r|slashes \ /|]-            parse string "" [r|"unicode \u1234\u5678\u90AB\uCDEF"|]+            parse string "" [gql|"simple"|] `shouldParse` "simple"+            parse string "" [gql|" white space "|] `shouldParse` " white space "+            parse string "" [gql|"quote \""|] `shouldParse` [gql|quote "|]+            parse string "" [gql|"escaped \n"|] `shouldParse` "escaped \n"+            parse string "" [gql|"slashes \\ \/"|] `shouldParse` [gql|slashes \ /|]+            parse string "" [gql|"unicode \u1234\u5678\u90AB\uCDEF"|]                 `shouldParse` "unicode ሴ噸邫췯"          it "lexes block string" $ do-            parse blockString "" [r|"""simple"""|] `shouldParse` "simple"-            parse blockString "" [r|""" white space """|]+            parse blockString "" [gql|"""simple"""|] `shouldParse` "simple"+            parse blockString "" [gql|""" white space """|]                 `shouldParse` " white space "-            parse blockString "" [r|"""contains " quote"""|]-                `shouldParse` [r|contains " quote|]-            parse blockString "" [r|"""contains \""" triplequote"""|]-                `shouldParse` [r|contains """ triplequote|]+            parse blockString "" [gql|"""contains " quote"""|]+                `shouldParse` [gql|contains " quote|]+            parse blockString "" [gql|"""contains \""" triplequote"""|]+                `shouldParse` [gql|contains """ triplequote|]             parse blockString "" "\"\"\"multi\nline\"\"\"" `shouldParse` "multi\nline"             parse blockString "" "\"\"\"multi\rline\r\nnormalized\"\"\""                 `shouldParse` "multi\nline\nnormalized"             parse blockString "" "\"\"\"multi\rline\r\nnormalized\"\"\""                 `shouldParse` "multi\nline\nnormalized"-            parse blockString "" [r|"""unescaped \n\r\b\t\f\u1234"""|]-                `shouldParse` [r|unescaped \n\r\b\t\f\u1234|]-            parse blockString "" [r|"""slashes \\ \/"""|]-                `shouldParse` [r|slashes \\ \/|]-            parse blockString "" [r|"""+            parse blockString "" [gql|"""unescaped \n\r\b\t\f\u1234"""|]+                `shouldParse` [gql|unescaped \n\r\b\t\f\u1234|]+            parse blockString "" [gql|"""slashes \\ \/"""|]+                `shouldParse` [gql|slashes \\ \/|]+            parse blockString "" [gql|"""                  spans                   multiple@@ -84,7 +84,7 @@      context "Implementation tests" $ do         it "lexes empty block strings" $-            parse blockString "" [r|""""""|] `shouldParse` ""+            parse blockString "" [gql|""""""|] `shouldParse` ""         it "lexes ampersand" $             parse amp "" "&" `shouldParse` "&"         it "lexes schema extensions" $
tests/Language/GraphQL/AST/ParserSpec.hs view
@@ -8,10 +8,10 @@ import Language.GraphQL.AST.Document import qualified Language.GraphQL.AST.DirectiveLocation as DirLoc import Language.GraphQL.AST.Parser+import Language.GraphQL.TH import Test.Hspec (Spec, describe, it) import Test.Hspec.Megaparsec (shouldParse, shouldFailOn, shouldSucceedOn) import Text.Megaparsec (parse)-import Text.RawString.QQ (r)  spec :: Spec spec = describe "Parser" $ do@@ -19,74 +19,74 @@         parse document "" `shouldSucceedOn` "\xfeff{foo}"      it "accepts block strings as argument" $-        parse document "" `shouldSucceedOn` [r|{+        parse document "" `shouldSucceedOn` [gql|{               hello(text: """Argument""")             }|]      it "accepts strings as argument" $-        parse document "" `shouldSucceedOn` [r|{+        parse document "" `shouldSucceedOn` [gql|{               hello(text: "Argument")             }|]      it "accepts two required arguments" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             mutation auth($username: String!, $password: String!){               test             }|]      it "accepts two string arguments" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             mutation auth{               test(username: "username", password: "password")             }|]      it "accepts two block string arguments" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             mutation auth{               test(username: """username""", password: """password""")             }|]      it "parses minimal schema definition" $-        parse document "" `shouldSucceedOn` [r|schema { query: Query }|]+        parse document "" `shouldSucceedOn` [gql|schema { query: Query }|]      it "parses minimal scalar definition" $-        parse document "" `shouldSucceedOn` [r|scalar Time|]+        parse document "" `shouldSucceedOn` [gql|scalar Time|]      it "parses ImplementsInterfaces" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             type Person implements NamedEntity & ValuedEntity {               name: String             }         |]      it "parses a  type without ImplementsInterfaces" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             type Person {               name: String             }         |]      it "parses ArgumentsDefinition in an ObjectDefinition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             type Person {               name(first: String, last: String): String             }         |]      it "parses minimal union type definition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             union SearchResult = Photo | Person         |]      it "parses minimal interface type definition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             interface NamedEntity {               name: String             }         |]      it "parses minimal enum type definition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             enum Direction {               NORTH               EAST@@ -96,7 +96,7 @@         |]      it "parses minimal enum type definition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             enum Direction {               NORTH               EAST@@ -106,7 +106,7 @@         |]      it "parses minimal input object type definition" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             input Point2D {               x: Float               y: Float@@ -114,7 +114,7 @@         |]      it "parses minimal input enum definition with an optional pipe" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             directive @example on               | FIELD               | FRAGMENT_SPREAD@@ -131,15 +131,15 @@             example1 =                 directive "example1"                     (DirLoc.TypeSystemDirectiveLocation DirLoc.FieldDefinition)-                    (Location {line = 2, column = 17})+                    (Location {line = 1, column = 1})             example2 =                 directive "example2"                     (DirLoc.ExecutableDirectiveLocation DirLoc.Field)-                    (Location {line = 3, column = 17})+                    (Location {line = 2, column = 1})             testSchemaExtension = example1 :| [ example2 ]-            query = [r|-                directive @example1 on FIELD_DEFINITION-                directive @example2 on FIELD+            query = [gql|+              directive @example1 on FIELD_DEFINITION+              directive @example2 on FIELD             |]          in parse document "" query `shouldParse` testSchemaExtension @@ -167,16 +167,16 @@                           $ Node (ConstList [])                           $ Location {line = 1, column = 33})]                     (Location {line = 1, column = 1})-            query = [r|directive @test(foo: [String] = []) on FIELD_DEFINITION|]+            query = [gql|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|+        parse document "" `shouldSucceedOn`[gql|             extend schema @newDirective         |]      it "parses schema extension with an operation type definition" $-        parse document "" `shouldSucceedOn` [r|extend schema { query: Query }|]+        parse document "" `shouldSucceedOn` [gql|extend schema { query: Query }|]      it "parses schema extension with an operation type and directive" $         let newDirective = Directive "newDirective" [] $ Location 1 15@@ -185,25 +185,25 @@                 $ OperationTypeDefinition Query "Query" :| []             testSchemaExtension = TypeSystemExtension schemaExtension                 $ Location 1 1-            query = [r|extend schema @newDirective { query: Query }|]+            query = [gql|extend schema @newDirective { query: Query }|]          in parse document "" query `shouldParse` (testSchemaExtension :| [])      it "parses an object extension" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             extend type Story {               isHiddenLocally: Boolean             }         |]      it "rejects variables in DefaultValue" $-        parse document "" `shouldFailOn` [r|+        parse document "" `shouldFailOn` [gql|             query ($book: String = "Zarathustra", $author: String = $book) {               title             }         |]      it "parses documents beginning with a comment" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             """             Query             """@@ -213,7 +213,7 @@         |]      it "parses subscriptions" $-        parse document "" `shouldSucceedOn` [r|+        parse document "" `shouldSucceedOn` [gql|             subscription NewMessages {               newMessage(roomId: 123) {                 sender
tests/Language/GraphQL/ExecuteSpec.hs view
@@ -21,6 +21,7 @@ import Language.GraphQL.AST.Parser (document) import Language.GraphQL.Error import Language.GraphQL.Execute (execute)+import Language.GraphQL.TH import qualified Language.GraphQL.Type.Schema as Schema import Language.GraphQL.Type import qualified Language.GraphQL.Type.In as In@@ -28,7 +29,6 @@ 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@@ -54,18 +54,23 @@     $ HashMap.fromList     [ ("philosopher", ValueResolver philosopherField philosopherResolver)     , ("genres", ValueResolver genresField genresResolver)+    , ("count", ValueResolver countField countResolver)     ]   where     philosopherField =-        Out.Field Nothing (Out.NonNullObjectType philosopherType)+        Out.Field Nothing (Out.NamedObjectType 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+        let fieldType = Out.ListType $ Out.NonNullScalarType string+         in Out.Field Nothing fieldType HashMap.empty     genresResolver :: Resolve (Either SomeException)     genresResolver = throwM PhilosopherException+    countField =+        let fieldType = Out.NonNullScalarType int+         in Out.Field Nothing fieldType HashMap.empty+    countResolver = pure ""  musicType :: Out.ObjectType (Either SomeException) musicType = Out.ObjectType "Music" Nothing []@@ -101,6 +106,7 @@         , ("interest", ValueResolver interestField interestResolver)         , ("majorWork", ValueResolver majorWorkField majorWorkResolver)         , ("century", ValueResolver centuryField centuryResolver)+        , ("firstLanguage", ValueResolver firstLanguageField firstLanguageResolver)         ]     firstNameField =         Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty@@ -126,6 +132,9 @@     centuryField =         Out.Field Nothing (Out.NonNullScalarType int) HashMap.empty     centuryResolver = pure $ Float 18.5+    firstLanguageField+        = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty+    firstLanguageResolver = pure Null  workType :: Out.InterfaceType (Either SomeException) workType = Out.InterfaceType "Work" Nothing []@@ -191,7 +200,7 @@ spec =     describe "execute" $ do         it "rejects recursive fragments" $-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 ...cyclicFragment               }@@ -230,14 +239,13 @@              it "errors on invalid output enum values" $                 let data'' = Aeson.object-                        [ "philosopher" .= Aeson.object-                            [ "school" .= Aeson.Null-                            ]+                        [ "philosopher" .= Aeson.Null                         ]                     executionErrors = pure $ Error-                        { message = "Enum value completion failed."+                        { message =+                            "Value completion error. Expected type !School, found: EXISTENTIALISM."                         , locations = [Location 1 17]-                        , path = []+                        , path = [Segment "philosopher", Segment "school"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'@@ -246,14 +254,13 @@              it "gives location information for non-null unions" $                 let data'' = Aeson.object-                        [ "philosopher" .= Aeson.object-                            [ "interest" .= Aeson.Null-                            ]+                        [ "philosopher" .= Aeson.Null                         ]                     executionErrors = pure $ Error-                        { message = "Union value completion failed."+                        { message =+                            "Value completion error. Expected type !Interest, found: { instrument: \"piano\" }."                         , locations = [Location 1 17]-                        , path = []+                        , path = [Segment "philosopher", Segment "interest"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'@@ -262,14 +269,14 @@              it "gives location information for invalid interfaces" $                 let data'' = Aeson.object-                        [ "philosopher" .= Aeson.object-                            [ "majorWork" .= Aeson.Null-                            ]+                        [ "philosopher" .= Aeson.Null                         ]                     executionErrors = pure $ Error-                        { message = "Interface value completion failed."+                        { message+                            = "Value completion error. Expected type !Work, found:\+                            \ { title: \"Also sprach Zarathustra: Ein Buch f\252r Alle und Keinen\" }."                         , locations = [Location 1 17]-                        , path = []+                        , path = [Segment "philosopher", Segment "majorWork"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'@@ -281,9 +288,10 @@                         [ "philosopher" .= Aeson.Null                         ]                     executionErrors = pure $ Error-                        { message = "Argument coercing failed."+                        { message =+                            "Argument \"id\" has invalid type. Expected type ID, found: True."                         , locations = [Location 1 15]-                        , path = []+                        , path = [Segment "philosopher"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'@@ -292,14 +300,12 @@              it "gives location information for failed result coercion" $                 let data'' = Aeson.object-                        [ "philosopher" .= Aeson.object-                            [ "century" .= Aeson.Null-                            ]+                        [ "philosopher" .= Aeson.Null                         ]                     executionErrors = pure $ Error-                        { message = "Result coercion failed."+                        { message = "Unable to coerce result to !Int."                         , locations = [Location 1 26]-                        , path = []+                        , path = [Segment "philosopher", Segment "century"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'@@ -313,11 +319,36 @@                     executionErrors = pure $ Error                         { message = "PhilosopherException"                         , locations = [Location 1 3]-                        , path = []+                        , path = [Segment "genres"]                         }                     expected = Response data'' executionErrors                     Right (Right actual) = either (pure . parseError) execute'                         $ parse document "" "{ genres }"+                in actual `shouldBe` expected++            it "sets data to null if a root field isn't nullable" $+                let executionErrors = pure $ Error+                        { message = "Unable to coerce result to !Int."+                        , locations = [Location 1 3]+                        , path = [Segment "count"]+                        }+                    expected = Response Aeson.Null executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ count }"+                in actual `shouldBe` expected++            it "detects nullability errors" $+                let data'' = Aeson.object+                        [ "philosopher" .= Aeson.Null+                        ]+                    executionErrors = pure $ Error+                        { message = "Value completion error. Expected type !String, found: null."+                        , locations = [Location 1 26]+                        , path = [Segment "philosopher", Segment "firstLanguage"]+                        }+                    expected = Response data'' executionErrors+                    Right (Right actual) = either (pure . parseError) execute'+                        $ parse document "" "{ philosopher(id: \"1\") { firstLanguage } }"                 in actual `shouldBe` expected          context "Subscription" $
tests/Language/GraphQL/Validate/RulesSpec.hs view
@@ -13,13 +13,13 @@ import qualified Data.HashMap.Strict as HashMap import Data.Text (Text) import qualified Language.GraphQL.AST as AST+import Language.GraphQL.TH import Language.GraphQL.Type import qualified Language.GraphQL.Type.In as In import qualified Language.GraphQL.Type.Out as Out import Language.GraphQL.Validate import Test.Hspec (Spec, context, describe, it, shouldBe, shouldContain) import Text.Megaparsec (parse, errorBundlePretty)-import Text.RawString.QQ (r)  petSchema :: Schema IO petSchema = schema queryType Nothing (Just subscriptionType) mempty@@ -163,7 +163,7 @@     describe "document" $ do         context "executableDefinitionsRule" $             it "rejects type definitions" $-                let queryString = [r|+                let queryString = [gql|                   query getDogName {                     dog {                       name@@ -179,13 +179,13 @@                         { message =                             "Definition must be OperationDefinition or \                             \FragmentDefinition."-                        , locations = [AST.Location 9 19]+                        , locations = [AST.Location 8 1]                         }                  in validate queryString `shouldContain` [expected]          context "singleFieldSubscriptionsRule" $ do             it "rejects multiple subscription root fields" $-                let queryString = [r|+                let queryString = [gql|                   subscription sub {                     newMessage {                       body@@ -198,12 +198,12 @@                         { message =                             "Subscription \"sub\" must select only one top \                             \level field."-                        , locations = [AST.Location 2 19]+                        , locations = [AST.Location 1 1]                         }                  in validate queryString `shouldContain` [expected]              it "rejects multiple subscription root fields coming from a fragment" $-                let queryString = [r|+                let queryString = [gql|                   subscription sub {                     ...multipleSubscriptions                   }@@ -220,12 +220,12 @@                         { message =                             "Subscription \"sub\" must select only one top \                             \level field."-                        , locations = [AST.Location 2 19]+                        , locations = [AST.Location 1 1]                         }                  in validate queryString `shouldContain` [expected]              it "finds corresponding subscription fragment" $-                let queryString = [r|+                let queryString = [gql|                   subscription sub {                     ...anotherSubscription                     ...multipleSubscriptions@@ -249,13 +249,13 @@                         { message =                             "Subscription \"sub\" must select only one top \                             \level field."-                        , locations = [AST.Location 2 19]+                        , locations = [AST.Location 1 1]                         }                  in validate queryString `shouldBe` [expected]          context "loneAnonymousOperationRule" $             it "rejects multiple anonymous operations" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       name@@ -274,13 +274,13 @@                         { message =                             "This anonymous operation must be the only defined \                             \operation."-                        , locations = [AST.Location 2 19]+                        , locations = [AST.Location 1 1]                         }                  in validate queryString `shouldBe` [expected]          context "uniqueOperationNamesRule" $             it "rejects operations with the same name" $-                let queryString = [r|+                let queryString = [gql|                   query dogOperation {                     dog {                       name@@ -297,13 +297,13 @@                         { message =                             "There can be only one operation named \                             \\"dogOperation\"."-                        , locations = [AST.Location 2 19, AST.Location 8 19]+                        , locations = [AST.Location 1 1, AST.Location 7 1]                         }                  in validate queryString `shouldBe` [expected]          context "uniqueFragmentNamesRule" $             it "rejects fragments with the same name" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ...fragmentOne@@ -324,13 +324,13 @@                         { message =                             "There can be only one fragment named \                             \\"fragmentOne\"."-                        , locations = [AST.Location 8 19, AST.Location 12 19]+                        , locations = [AST.Location 7 1, AST.Location 11 1]                         }                  in validate queryString `shouldBe` [expected]          context "fragmentSpreadTargetDefinedRule" $             it "rejects the fragment spread without a target" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ...undefinedFragment@@ -341,13 +341,13 @@                         { message =                             "Fragment target \"undefinedFragment\" is \                             \undefined."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]          context "fragmentSpreadTypeExistenceRule" $ do             it "rejects fragment spreads without an unknown target type" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ...notOnExistingType@@ -362,12 +362,12 @@                             "Fragment \"notOnExistingType\" is specified on \                             \type \"NotInSchema\" which doesn't exist in the \                             \schema."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]              it "rejects inline fragments without a target" $-                let queryString = [r|+                let queryString = [gql|                   {                     ... on NotInSchema {                       name@@ -378,13 +378,13 @@                         { message =                             "Inline fragment is specified on type \                             \\"NotInSchema\" which doesn't exist in the schema."-                        , locations = [AST.Location 3 21]+                        , locations = [AST.Location 2 3]                         }                  in validate queryString `shouldBe` [expected]          context "fragmentsOnCompositeTypesRule" $ do             it "rejects fragments on scalar types" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ...fragOnScalar@@ -398,12 +398,12 @@                         { message =                             "Fragment cannot condition on non composite type \                             \\"Int\"."-                        , locations = [AST.Location 7 19]+                        , locations = [AST.Location 6 1]                         }                  in validate queryString `shouldContain` [expected]              it "rejects inline fragments on scalar types" $-                let queryString = [r|+                let queryString = [gql|                   {                     ... on Boolean {                       name@@ -414,13 +414,13 @@                         { message =                             "Fragment cannot condition on non composite type \                             \\"Boolean\"."-                        , locations = [AST.Location 3 21]+                        , locations = [AST.Location 2 3]                         }                 in validate queryString `shouldContain` [expected]          context "noUnusedFragmentsRule" $             it "rejects unused fragments" $-                let queryString = [r|+                let queryString = [gql|                   fragment nameFragment on Dog { # unused                     name                   }@@ -434,13 +434,13 @@                     expected = Error                         { message =                             "Fragment \"nameFragment\" is never used."-                        , locations = [AST.Location 2 19]+                        , locations = [AST.Location 1 1]                         }                  in validate queryString `shouldBe` [expected]          context "noFragmentCyclesRule" $             it "rejects spreads that form cycles" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ...nameFragment@@ -460,20 +460,20 @@                             "Cannot spread fragment \"barkVolumeFragment\" \                             \within itself (via barkVolumeFragment -> \                             \nameFragment -> barkVolumeFragment)."-                        , locations = [AST.Location 11 19]+                        , locations = [AST.Location 10 1]                         }                     error2 = Error                         { message =                             "Cannot spread fragment \"nameFragment\" within \                             \itself (via nameFragment -> barkVolumeFragment -> \                             \nameFragment)."-                        , locations = [AST.Location 7 19]+                        , locations = [AST.Location 6 1]                         }                  in validate queryString `shouldBe` [error1, error2]          context "uniqueArgumentNamesRule" $             it "rejects duplicate field arguments" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       isHousetrained(atOtherHomes: true, atOtherHomes: true)@@ -484,13 +484,13 @@                         { message =                             "There can be only one argument named \                             \\"atOtherHomes\"."-                        , locations = [AST.Location 4 38, AST.Location 4 58]+                        , locations = [AST.Location 3 20, AST.Location 3 40]                         }                  in validate queryString `shouldBe` [expected]          context "uniqueDirectiveNamesRule" $             it "rejects more than one directive per location" $-                let queryString = [r|+                let queryString = [gql|                   query ($foo: Boolean = true, $bar: Boolean = false) {                     dog @skip(if: $foo) @skip(if: $bar) {                       name@@ -500,13 +500,13 @@                     expected = Error                         { message =                             "There can be only one directive named \"skip\"."-                        , locations = [AST.Location 3 25, AST.Location 3 41]+                        , locations = [AST.Location 2 7, AST.Location 2 23]                         }                  in validate queryString `shouldBe` [expected]          context "uniqueVariableNamesRule" $             it "rejects duplicate variables" $-                let queryString = [r|+                let queryString = [gql|                   query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {                     dog {                       isHousetrained(atOtherHomes: $atOtherHomes)@@ -517,13 +517,13 @@                         { message =                             "There can be only one variable named \                             \\"atOtherHomes\"."-                        , locations = [AST.Location 2 43, AST.Location 2 67]+                        , locations = [AST.Location 1 25, AST.Location 1 49]                         }                  in validate queryString `shouldBe` [expected]          context "variablesAreInputTypesRule" $             it "rejects non-input types as variables" $-                let queryString = [r|+                let queryString = [gql|                   query takesDogBang($dog: Dog!) {                     dog {                       isHousetrained(atOtherHomes: $dog)@@ -534,13 +534,13 @@                         { message =                             "Variable \"$dog\" cannot be non-input type \                             \\"Dog\"."-                        , locations = [AST.Location 2 38]+                        , locations = [AST.Location 1 20]                         }                  in validate queryString `shouldContain` [expected]          context "noUndefinedVariablesRule" $             it "rejects undefined variables" $-                let queryString = [r|+                let queryString = [gql|                   query variableIsNotDefinedUsedInSingleFragment {                     dog {                       ...isHousetrainedFragment@@ -556,13 +556,13 @@                             "Variable \"$atOtherHomes\" is not defined by \                             \operation \                             \\"variableIsNotDefinedUsedInSingleFragment\"."-                        , locations = [AST.Location 9 50]+                        , locations = [AST.Location 8 32]                         }                  in validate queryString `shouldBe` [expected]          context "noUnusedVariablesRule" $             it "rejects unused variables" $-                let queryString = [r|+                let queryString = [gql|                   query variableUnused($atOtherHomes: Boolean) {                     dog {                       isHousetrained@@ -573,13 +573,13 @@                         { message =                             "Variable \"$atOtherHomes\" is never used in \                             \operation \"variableUnused\"."-                        , locations = [AST.Location 2 40]+                        , locations = [AST.Location 1 22]                         }                  in validate queryString `shouldBe` [expected]          context "uniqueInputFieldNamesRule" $             it "rejects duplicate fields in input objects" $-                let queryString = [r|+                let queryString = [gql|                   {                     findDog(complex: { name: "Fido", name: "Jack" }) {                       name@@ -589,13 +589,13 @@                     expected = Error                         { message =                             "There can be only one input field named \"name\"."-                        , locations = [AST.Location 3 40, AST.Location 3 54]+                        , locations = [AST.Location 2 22, AST.Location 2 36]                         }                  in validate queryString `shouldBe` [expected]          context "fieldsOnCorrectTypeRule" $             it "rejects undefined fields" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       meowVolume@@ -605,13 +605,13 @@                     expected = Error                         { message =                             "Cannot query field \"meowVolume\" on type \"Dog\"."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]          context "scalarLeafsRule" $             it "rejects scalar fields with not empty selection set" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       barkVolume {@@ -624,13 +624,13 @@                         { message =                             "Field \"barkVolume\" must not have a selection \                             \since type \"Int\" has no subfields."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]          context "knownArgumentNamesRule" $ do             it "rejects field arguments missing in the type" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       doesKnowCommand(command: CLEAN_UP_HOUSE, dogCommand: SIT)@@ -641,12 +641,12 @@                         { message =                             "Unknown argument \"command\" on field \                             \\"Dog.doesKnowCommand\"."-                        , locations = [AST.Location 4 39]+                        , locations = [AST.Location 3 21]                         }                  in validate queryString `shouldBe` [expected]              it "rejects directive arguments missing in the definition" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       isHousetrained(atOtherHomes: true) @include(unless: false, if: true)@@ -657,13 +657,13 @@                         { message =                             "Unknown argument \"unless\" on directive \                             \\"@include\"."-                        , locations = [AST.Location 4 67]+                        , locations = [AST.Location 3 49]                         }                  in validate queryString `shouldBe` [expected]          context "knownDirectiveNamesRule" $             it "rejects undefined directives" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       isHousetrained(atOtherHomes: true) @ignore(if: true)@@ -672,13 +672,13 @@                 |]                     expected = Error                         { message = "Unknown directive \"@ignore\"."-                        , locations = [AST.Location 4 58]+                        , locations = [AST.Location 3 40]                         }                  in validate queryString `shouldBe` [expected]          context "knownInputFieldNamesRule" $             it "rejects undefined input object fields" $-                let queryString = [r|+                let queryString = [gql|                   {                     findDog(complex: { favoriteCookieFlavor: "Bacon", name: "Jack" }) {                       name@@ -689,13 +689,13 @@                         { message =                             "Field \"favoriteCookieFlavor\" is not defined \                             \by type \"DogData\"."-                        , locations = [AST.Location 3 40]+                        , locations = [AST.Location 2 22]                         }                  in validate queryString `shouldBe` [expected]          context "directivesInValidLocationsRule" $             it "rejects directives in invalid locations" $-                let queryString = [r|+                let queryString = [gql|                   query @skip(if: $foo) {                     dog {                       name@@ -705,13 +705,13 @@                     expected = Error                         { message =                             "Directive \"@skip\" may not be used on QUERY."-                        , locations = [AST.Location 2 25]+                        , locations = [AST.Location 1 7]                         }                  in validate queryString `shouldBe` [expected]          context "overlappingFieldsCanBeMergedRule" $ do             it "fails to merge fields of mismatching types" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       name: nickname@@ -725,12 +725,12 @@                             \\"name\" are different fields. Use different \                             \aliases on the fields to fetch both if this was \                             \intentional."-                        , locations = [AST.Location 4 23, AST.Location 5 23]+                        , locations = [AST.Location 3 5, AST.Location 4 5]                         }                  in validate queryString `shouldBe` [expected]              it "fails if the arguments of the same field don't match" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       doesKnowCommand(dogCommand: SIT)@@ -744,12 +744,12 @@                             \have different arguments. Use different aliases \                             \on the fields to fetch both if this was \                             \intentional."-                        , locations = [AST.Location 4 23, AST.Location 5 23]+                        , locations = [AST.Location 3 5, AST.Location 4 5]                         }                  in validate queryString `shouldBe` [expected]              it "fails to merge same-named field and alias" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       doesKnowCommand(dogCommand: SIT)@@ -763,12 +763,12 @@                             \\"doesKnowCommand\" and \"isHousetrained\" are \                             \different fields. Use different aliases on the \                             \fields to fetch both if this was intentional."-                        , locations = [AST.Location 4 23, AST.Location 5 23]+                        , locations = [AST.Location 3 5, AST.Location 4 5]                         }                  in validate queryString `shouldBe` [expected]              it "looks for fields after a successfully merged field pair" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       name@@ -786,13 +786,13 @@                             \\"doesKnowCommand\" and \"isHousetrained\" are \                             \different fields. Use different aliases on the \                             \fields to fetch both if this was intentional."-                        , locations = [AST.Location 5 23, AST.Location 9 23]+                        , locations = [AST.Location 4 5, AST.Location 8 5]                         }                  in validate queryString `shouldBe` [expected]          context "possibleFragmentSpreadsRule" $ do             it "rejects object inline spreads outside object scope" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ... on Cat {@@ -805,12 +805,12 @@                         { message =                             "Fragment cannot be spread here as objects of type \                             \\"Dog\" can never be of type \"Cat\"."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]              it "rejects object named spreads outside object scope" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       ... catInDogFragmentInvalid@@ -826,13 +826,13 @@                             "Fragment \"catInDogFragmentInvalid\" cannot be \                             \spread here as objects of type \"Dog\" can never \                             \be of type \"Cat\"."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]          context "providedRequiredInputFieldsRule" $             it "rejects missing required input fields" $-                let queryString = [r|+                let queryString = [gql|                   {                     findDog(complex: { name: null }) {                       name@@ -843,13 +843,13 @@                         { message =                             "Input field \"name\" of type \"DogData\" is \                             \required, but it was not provided."-                        , locations = [AST.Location 3 38]+                        , locations = [AST.Location 2 20]                         }                  in validate queryString `shouldBe` [expected]          context "providedRequiredArgumentsRule" $ do             it "checks for (non-)nullable arguments" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       doesKnowCommand(dogCommand: null)@@ -861,13 +861,13 @@                             "Field \"doesKnowCommand\" argument \"dogCommand\" \                             \of type \"DogCommand\" is required, but it was \                             \not provided."-                        , locations = [AST.Location 4 23]+                        , locations = [AST.Location 3 5]                         }                  in validate queryString `shouldBe` [expected]          context "variablesInAllowedPositionRule" $ do             it "rejects wrongly typed variable arguments" $-                let queryString = [r|+                let queryString = [gql|                   query dogCommandArgQuery($dogCommandArg: DogCommand) {                     dog {                       doesKnowCommand(dogCommand: $dogCommandArg)@@ -879,12 +879,12 @@                             "Variable \"$dogCommandArg\" of type \                             \\"DogCommand\" used in position expecting type \                             \\"!DogCommand\"."-                        , locations = [AST.Location 2 44]+                        , locations = [AST.Location 1 26]                         }                  in validate queryString `shouldBe` [expected]              it "rejects wrongly typed variable arguments" $-                let queryString = [r|+                let queryString = [gql|                   query intCannotGoIntoBoolean($intArg: Int) {                     dog {                       isHousetrained(atOtherHomes: $intArg)@@ -895,13 +895,13 @@                         { message =                             "Variable \"$intArg\" of type \"Int\" used in \                             \position expecting type \"Boolean\"."-                        , locations = [AST.Location 2 48]+                        , locations = [AST.Location 1 30]                         }                  in validate queryString `shouldBe` [expected]          context "valuesOfCorrectTypeRule" $ do             it "rejects values of incorrect types" $-                let queryString = [r|+                let queryString = [gql|                   {                     dog {                       isHousetrained(atOtherHomes: 3)@@ -911,12 +911,12 @@                     expected = Error                         { message =                             "Value 3 cannot be coerced to type \"Boolean\"."-                        , locations = [AST.Location 4 52]+                        , locations = [AST.Location 3 34]                         }                  in validate queryString `shouldBe` [expected]              it "uses the location of a single list value" $-                let queryString = [r|+                let queryString = [gql|                   {                     cat {                       doesKnowCommands(catCommands: [3])@@ -926,12 +926,12 @@                     expected = Error                         { message =                             "Value 3 cannot be coerced to type \"!CatCommand\"."-                        , locations = [AST.Location 4 54]+                        , locations = [AST.Location 3 36]                         }                  in validate queryString `shouldBe` [expected]              it "validates input object properties once" $-                let queryString = [r|+                let queryString = [gql|                   {                     findDog(complex: { name: 3 }) {                       name@@ -941,12 +941,12 @@                     expected = Error                         { message =                             "Value 3 cannot be coerced to type \"!String\"."-                        , locations = [AST.Location 3 46]+                        , locations = [AST.Location 2 28]                         }                  in validate queryString `shouldBe` [expected]              it "checks for required list members" $-                let queryString = [r|+                let queryString = [gql|                   {                     cat {                       doesKnowCommands(catCommands: [null])@@ -957,6 +957,6 @@                         { message =                             "List of non-null values of type \"CatCommand\" \                             \cannot contain null values."-                        , locations = [AST.Location 4 54]+                        , locations = [AST.Location 3 36]                         }                  in validate queryString `shouldBe` [expected]
tests/Test/DirectiveSpec.hs view
@@ -12,11 +12,11 @@ import qualified Data.Aeson as Aeson import qualified Data.HashMap.Strict as HashMap import Language.GraphQL+import Language.GraphQL.TH import Language.GraphQL.Type import qualified Language.GraphQL.Type.Out as Out import Test.Hspec (Spec, describe, it) import Test.Hspec.GraphQL-import Text.RawString.QQ (r)  experimentalResolver :: Schema IO experimentalResolver = schema queryType Nothing Nothing mempty@@ -33,7 +33,7 @@ spec =     describe "Directive executor" $ do         it "should be able to @skip fields" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 experimentalField @skip(if: true)               }@@ -43,7 +43,7 @@             actual `shouldResolveTo` emptyObject          it "should not skip fields if @skip is false" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 experimentalField @skip(if: false)               }@@ -56,7 +56,7 @@             actual `shouldResolveTo` expected          it "should skip fields if @include is false" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 experimentalField @include(if: false)               }@@ -66,7 +66,7 @@             actual `shouldResolveTo` emptyObject          it "should be able to @skip a fragment spread" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 ...experimentalFragment @skip(if: true)               }@@ -80,7 +80,7 @@             actual `shouldResolveTo` emptyObject          it "should be able to @skip an inline fragment" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 ... on Query @skip(if: true) {                   experimentalField
tests/Test/FragmentSpec.hs view
@@ -15,9 +15,9 @@ import Language.GraphQL import Language.GraphQL.Type import qualified Language.GraphQL.Type.Out as Out+import Language.GraphQL.TH import Test.Hspec (Spec, describe, it) import Test.Hspec.GraphQL-import Text.RawString.QQ (r)  size :: (Text, Value) size = ("size", String "L")@@ -34,16 +34,18 @@     )  inlineQuery :: Text-inlineQuery = [r|{-  garment {-    ... on Hat {-      circumference-    }-    ... on Shirt {-      size+inlineQuery = [gql|+  {+    garment {+      ... on Hat {+        circumference+      }+      ... on Shirt {+        size+      }     }   }-}|]+|]  shirtType :: Out.ObjectType IO shirtType = Out.ObjectType "Shirt" Nothing [] $ HashMap.fromList@@ -106,12 +108,14 @@              in actual `shouldResolveTo` expected          it "embeds inline fragments without type" $ do-            let sourceQuery = [r|{-              circumference-              ... {-                size+            let sourceQuery = [gql|+              {+                circumference+                ... {+                  size+                }               }-            }|]+            |]             actual <- graphql (toSchema "circumference" circumference) sourceQuery             let expected = HashMap.singleton "data"                     $ Aeson.object@@ -121,16 +125,18 @@              in actual `shouldResolveTo` expected          it "evaluates fragments on Query" $ do-            let sourceQuery = [r|{-              ... {-                size+            let sourceQuery = [gql|+              {+                ... {+                  size+                }               }-            }|]+            |]              in graphql (toSchema "size" size) `shouldResolve` sourceQuery      describe "Fragment spread executor" $ do         it "evaluates fragment spreads" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 ...circumferenceFragment               }@@ -148,7 +154,7 @@              in actual `shouldResolveTo` expected          it "evaluates nested fragments" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 garment {                   ...circumferenceFragment@@ -174,7 +180,7 @@              in actual `shouldResolveTo` expected          it "considers type condition" $ do-            let sourceQuery = [r|+            let sourceQuery = [gql|               {                 garment {                   ...circumferenceFragment
tests/Test/RootOperationSpec.hs view
@@ -12,7 +12,7 @@ import qualified Data.HashMap.Strict as HashMap import Language.GraphQL import Test.Hspec (Spec, describe, it)-import Text.RawString.QQ (r)+import Language.GraphQL.TH import Language.GraphQL.Type import qualified Language.GraphQL.Type.Out as Out import Test.Hspec.GraphQL@@ -42,7 +42,7 @@ spec =     describe "Root operation type" $ do         it "returns objects from the root resolvers" $ do-            let querySource = [r|+            let querySource = [gql|               {                 garment {                   circumference@@ -59,7 +59,7 @@             actual `shouldResolveTo` expected          it "chooses Mutation" $ do-            let querySource = [r|+            let querySource = [gql|               mutation {                 incrementCircumference               }