@@ -17,7 +17,8 @@ and this project adheres to
 | 
				
			|||||||
- Type system definition parser.
 | 
					- Type system definition parser.
 | 
				
			||||||
- `Trans.argument`.
 | 
					- `Trans.argument`.
 | 
				
			||||||
- Schema extension parser.
 | 
					- Schema extension parser.
 | 
				
			||||||
- Contributing guidelines
 | 
					- Contributing guidelines.
 | 
				
			||||||
 | 
					- `Schema.resolversToMap` (intended for to be used internally).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Changed
 | 
					### Changed
 | 
				
			||||||
- Rename `AST.Definition` into `AST.Document.ExecutableDefinition`.
 | 
					- Rename `AST.Definition` into `AST.Document.ExecutableDefinition`.
 | 
				
			||||||
@@ -31,6 +32,12 @@ and this project adheres to
 | 
				
			|||||||
  `symbol "@"` now.
 | 
					  `symbol "@"` now.
 | 
				
			||||||
- Replace `MonadIO` with a plain `Monad`. Since the tests don't use IO,
 | 
					- Replace `MonadIO` with a plain `Monad`. Since the tests don't use IO,
 | 
				
			||||||
  set the inner monad to `Identity`.
 | 
					  set the inner monad to `Identity`.
 | 
				
			||||||
 | 
					- `NonEmpty (Resolver m)` is now `HashMap Text (NonEmpty (Resolver m))`. Root
 | 
				
			||||||
 | 
					  operation type can be any type, therefore a hashmap is needed. Since types
 | 
				
			||||||
 | 
					  cannot be empty, we save the list of resolvers in the type as a non-empty
 | 
				
			||||||
 | 
					  list. Currently only "Query" and "Mutation" are supported as types. For more
 | 
				
			||||||
 | 
					  schema support is required. The executor checks now if the type in the query
 | 
				
			||||||
 | 
					  matches the type of the provided root resolvers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Removed
 | 
					### Removed
 | 
				
			||||||
- `AST.Field`, `AST.InlineFragment` and `AST.FragmentSpread`.
 | 
					- `AST.Field`, `AST.InlineFragment` and `AST.FragmentSpread`.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,8 @@ Since this file is a literate haskell file, we start by importing some dependenc
 | 
				
			|||||||
> import Control.Monad.IO.Class (liftIO)
 | 
					> import Control.Monad.IO.Class (liftIO)
 | 
				
			||||||
> import Data.Aeson (encode)
 | 
					> import Data.Aeson (encode)
 | 
				
			||||||
> import Data.ByteString.Lazy.Char8 (putStrLn)
 | 
					> import Data.ByteString.Lazy.Char8 (putStrLn)
 | 
				
			||||||
 | 
					> import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
 | 
					> import qualified Data.HashMap.Strict as HashMap
 | 
				
			||||||
> import Data.List.NonEmpty (NonEmpty(..))
 | 
					> import Data.List.NonEmpty (NonEmpty(..))
 | 
				
			||||||
> import Data.Text (Text)
 | 
					> import Data.Text (Text)
 | 
				
			||||||
> import Data.Time (getCurrentTime)
 | 
					> import Data.Time (getCurrentTime)
 | 
				
			||||||
@@ -33,8 +35,8 @@ example from [graphql.js](https://github.com/graphql/graphql-js).
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
First we build a GraphQL schema.
 | 
					First we build a GraphQL schema.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> schema1 :: NonEmpty (Schema.Resolver IO)
 | 
					> schema1 :: HashMap Text (NonEmpty (Schema.Resolver IO))
 | 
				
			||||||
> schema1 = hello :| []
 | 
					> schema1 = HashMap.singleton "Query" $ hello :| []
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> hello :: Schema.Resolver IO
 | 
					> hello :: Schema.Resolver IO
 | 
				
			||||||
> hello = Schema.scalar "hello" (return ("it's me" :: Text))
 | 
					> hello = Schema.scalar "hello" (return ("it's me" :: Text))
 | 
				
			||||||
@@ -63,8 +65,8 @@ returning
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
For this example, we're going to be using time.
 | 
					For this example, we're going to be using time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> schema2 :: NonEmpty (Schema.Resolver IO)
 | 
					> schema2 :: HashMap Text (NonEmpty (Schema.Resolver IO))
 | 
				
			||||||
> schema2 = time :| []
 | 
					> schema2 = HashMap.singleton "Query" $ time :| []
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> time :: Schema.Resolver IO
 | 
					> time :: Schema.Resolver IO
 | 
				
			||||||
> time = Schema.scalar "time" $ do
 | 
					> time = Schema.scalar "time" $ do
 | 
				
			||||||
@@ -122,8 +124,8 @@ This will fail
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Now that we have two resolvers, we can define a schema which uses them both.
 | 
					Now that we have two resolvers, we can define a schema which uses them both.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> schema3 :: NonEmpty (Schema.Resolver IO)
 | 
					> schema3 :: HashMap Text (NonEmpty (Schema.Resolver IO))
 | 
				
			||||||
> schema3 = hello :| [time]
 | 
					> schema3 = HashMap.singleton "Query" $ hello :| [time]
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> query3 :: Text
 | 
					> query3 :: Text
 | 
				
			||||||
> query3 = "query timeAndHello { time hello }"
 | 
					> query3 = "query timeAndHello { time hello }"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,8 @@ module Language.GraphQL
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import qualified Data.Aeson as Aeson
 | 
					import qualified Data.Aeson as Aeson
 | 
				
			||||||
import Data.List.NonEmpty (NonEmpty)
 | 
					import Data.List.NonEmpty (NonEmpty)
 | 
				
			||||||
import qualified Data.Text as T
 | 
					import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
 | 
					import Data.Text (Text)
 | 
				
			||||||
import Language.GraphQL.Error
 | 
					import Language.GraphQL.Error
 | 
				
			||||||
import Language.GraphQL.Execute
 | 
					import Language.GraphQL.Execute
 | 
				
			||||||
import Language.GraphQL.AST.Parser
 | 
					import Language.GraphQL.AST.Parser
 | 
				
			||||||
@@ -16,8 +17,8 @@ import Text.Megaparsec (parse)
 | 
				
			|||||||
-- | If the text parses correctly as a @GraphQL@ query the query is
 | 
					-- | If the text parses correctly as a @GraphQL@ query the query is
 | 
				
			||||||
-- executed using the given 'Schema.Resolver's.
 | 
					-- executed using the given 'Schema.Resolver's.
 | 
				
			||||||
graphql :: Monad m
 | 
					graphql :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m) -- ^ Resolvers.
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m)) -- ^ Resolvers.
 | 
				
			||||||
    -> T.Text -- ^ Text representing a @GraphQL@ request document.
 | 
					    -> Text -- ^ Text representing a @GraphQL@ request document.
 | 
				
			||||||
    -> m Aeson.Value -- ^ Response.
 | 
					    -> m Aeson.Value -- ^ Response.
 | 
				
			||||||
graphql = flip graphqlSubs mempty
 | 
					graphql = flip graphqlSubs mempty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,9 +26,9 @@ graphql = flip graphqlSubs mempty
 | 
				
			|||||||
-- applied to the query and the query is then executed using to the given
 | 
					-- applied to the query and the query is then executed using to the given
 | 
				
			||||||
-- 'Schema.Resolver's.
 | 
					-- 'Schema.Resolver's.
 | 
				
			||||||
graphqlSubs :: Monad m
 | 
					graphqlSubs :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m) -- ^ Resolvers.
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m)) -- ^ Resolvers.
 | 
				
			||||||
    -> Schema.Subs -- ^ Variable substitution function.
 | 
					    -> Schema.Subs -- ^ Variable substitution function.
 | 
				
			||||||
    -> T.Text -- ^ Text representing a @GraphQL@ request document.
 | 
					    -> Text -- ^ Text representing a @GraphQL@ request document.
 | 
				
			||||||
    -> m Aeson.Value -- ^ Response.
 | 
					    -> m Aeson.Value -- ^ Response.
 | 
				
			||||||
graphqlSubs schema f
 | 
					graphqlSubs schema f
 | 
				
			||||||
    = either parseError (execute schema f)
 | 
					    = either parseError (execute schema f)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,10 @@ module Language.GraphQL.Execute
 | 
				
			|||||||
    ) where
 | 
					    ) where
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import qualified Data.Aeson as Aeson
 | 
					import qualified Data.Aeson as Aeson
 | 
				
			||||||
import Data.Foldable (toList)
 | 
					 | 
				
			||||||
import Data.List.NonEmpty (NonEmpty(..))
 | 
					import Data.List.NonEmpty (NonEmpty(..))
 | 
				
			||||||
import qualified Data.List.NonEmpty as NE
 | 
					import qualified Data.List.NonEmpty as NonEmpty
 | 
				
			||||||
 | 
					import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
 | 
					import qualified Data.HashMap.Strict as HashMap
 | 
				
			||||||
import Data.Text (Text)
 | 
					import Data.Text (Text)
 | 
				
			||||||
import qualified Data.Text as Text
 | 
					import qualified Data.Text as Text
 | 
				
			||||||
import Language.GraphQL.AST.Document
 | 
					import Language.GraphQL.AST.Document
 | 
				
			||||||
@@ -24,12 +25,13 @@ import qualified Language.GraphQL.Schema as Schema
 | 
				
			|||||||
-- Returns the result of the query against the schema wrapped in a /data/
 | 
					-- Returns the result of the query against the schema wrapped in a /data/
 | 
				
			||||||
-- field, or errors wrapped in an /errors/ field.
 | 
					-- field, or errors wrapped in an /errors/ field.
 | 
				
			||||||
execute :: Monad m
 | 
					execute :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m) -- ^ Resolvers.
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m)) -- ^ Resolvers.
 | 
				
			||||||
    -> Schema.Subs -- ^ Variable substitution function.
 | 
					    -> Schema.Subs -- ^ Variable substitution function.
 | 
				
			||||||
    -> Document -- @GraphQL@ document.
 | 
					    -> Document -- @GraphQL@ document.
 | 
				
			||||||
    -> m Aeson.Value
 | 
					    -> m Aeson.Value
 | 
				
			||||||
execute schema subs doc =
 | 
					execute schema subs doc =
 | 
				
			||||||
    maybe transformError (document schema Nothing) $ Transform.document subs doc
 | 
					    maybe transformError (document schema Nothing)
 | 
				
			||||||
 | 
					        $ Transform.document subs doc
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    transformError = return $ singleError "Schema transformation error."
 | 
					    transformError = return $ singleError "Schema transformation error."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,23 +42,24 @@ execute schema subs doc =
 | 
				
			|||||||
-- Returns the result of the query against the schema wrapped in a /data/
 | 
					-- Returns the result of the query against the schema wrapped in a /data/
 | 
				
			||||||
-- field, or errors wrapped in an /errors/ field.
 | 
					-- field, or errors wrapped in an /errors/ field.
 | 
				
			||||||
executeWithName :: Monad m
 | 
					executeWithName :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m) -- ^ Resolvers
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m)) -- ^ Resolvers
 | 
				
			||||||
    -> Text -- ^ Operation name.
 | 
					    -> Text -- ^ Operation name.
 | 
				
			||||||
    -> Schema.Subs -- ^ Variable substitution function.
 | 
					    -> Schema.Subs -- ^ Variable substitution function.
 | 
				
			||||||
    -> Document -- ^ @GraphQL@ Document.
 | 
					    -> Document -- ^ @GraphQL@ Document.
 | 
				
			||||||
    -> m Aeson.Value
 | 
					    -> m Aeson.Value
 | 
				
			||||||
executeWithName schema name subs doc =
 | 
					executeWithName schema name subs doc =
 | 
				
			||||||
    maybe transformError (document schema $ Just name) $ Transform.document subs doc
 | 
					    maybe transformError (document schema $ Just name)
 | 
				
			||||||
 | 
					        $ Transform.document subs doc
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    transformError = return $ singleError "Schema transformation error."
 | 
					    transformError = return $ singleError "Schema transformation error."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document :: Monad m
 | 
					document :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m)
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m))
 | 
				
			||||||
    -> Maybe Text
 | 
					    -> Maybe Text
 | 
				
			||||||
    -> AST.Core.Document
 | 
					    -> AST.Core.Document
 | 
				
			||||||
    -> m Aeson.Value
 | 
					    -> m Aeson.Value
 | 
				
			||||||
document schema Nothing (op :| []) = operation schema op
 | 
					document schema Nothing (op :| []) = operation schema op
 | 
				
			||||||
document schema (Just name) operations = case NE.dropWhile matchingName operations of
 | 
					document schema (Just name) operations = case NonEmpty.dropWhile matchingName operations of
 | 
				
			||||||
    [] -> return $ singleError
 | 
					    [] -> return $ singleError
 | 
				
			||||||
        $ Text.unwords ["Operation", name, "couldn't be found in the document."]
 | 
					        $ Text.unwords ["Operation", name, "couldn't be found in the document."]
 | 
				
			||||||
    (op:_)  -> operation schema op
 | 
					    (op:_)  -> operation schema op
 | 
				
			||||||
@@ -67,10 +70,17 @@ document schema (Just name) operations = case NE.dropWhile matchingName operatio
 | 
				
			|||||||
document _ _ _ = return $ singleError "Missing operation name."
 | 
					document _ _ _ = return $ singleError "Missing operation name."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
operation :: Monad m
 | 
					operation :: Monad m
 | 
				
			||||||
    => NonEmpty (Schema.Resolver m)
 | 
					    => HashMap Text (NonEmpty (Schema.Resolver m))
 | 
				
			||||||
    -> AST.Core.Operation
 | 
					    -> AST.Core.Operation
 | 
				
			||||||
    -> m Aeson.Value
 | 
					    -> m Aeson.Value
 | 
				
			||||||
operation schema (AST.Core.Query _ flds)
 | 
					operation schema = schemaOperation
 | 
				
			||||||
    = runCollectErrs (Schema.resolve (toList schema) flds)
 | 
					  where
 | 
				
			||||||
operation schema (AST.Core.Mutation _ flds)
 | 
					    runResolver fields = runCollectErrs
 | 
				
			||||||
    = runCollectErrs (Schema.resolve (toList schema) flds)
 | 
					        . flip Schema.resolve fields
 | 
				
			||||||
 | 
					        . Schema.resolversToMap
 | 
				
			||||||
 | 
					    resolve fields queryType = maybe lookupError (runResolver fields)
 | 
				
			||||||
 | 
					        $ HashMap.lookup queryType schema
 | 
				
			||||||
 | 
					    lookupError = pure
 | 
				
			||||||
 | 
					        $ singleError "Root operation type couldn't be found in the schema."
 | 
				
			||||||
 | 
					    schemaOperation (AST.Core.Query _ fields) = resolve fields "Query"
 | 
				
			||||||
 | 
					    schemaOperation (AST.Core.Mutation _ fields) = resolve fields "Mutation"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,12 @@
 | 
				
			|||||||
-- | This module provides a representation of a @GraphQL@ Schema in addition to
 | 
					-- | This module provides a representation of a @GraphQL@ Schema in addition to
 | 
				
			||||||
-- functions for defining and manipulating schemas.
 | 
					-- functions for defining and manipulating schemas.
 | 
				
			||||||
module Language.GraphQL.Schema
 | 
					module Language.GraphQL.Schema
 | 
				
			||||||
    ( Resolver
 | 
					    ( Resolver(..)
 | 
				
			||||||
    , Subs
 | 
					    , Subs
 | 
				
			||||||
    , object
 | 
					    , object
 | 
				
			||||||
    , scalar
 | 
					 | 
				
			||||||
    , resolve
 | 
					    , resolve
 | 
				
			||||||
 | 
					    , resolversToMap
 | 
				
			||||||
 | 
					    , scalar
 | 
				
			||||||
    , wrappedObject
 | 
					    , wrappedObject
 | 
				
			||||||
    , wrappedScalar
 | 
					    , wrappedScalar
 | 
				
			||||||
    -- * AST Reexports
 | 
					    -- * AST Reexports
 | 
				
			||||||
@@ -18,7 +19,7 @@ module Language.GraphQL.Schema
 | 
				
			|||||||
import Control.Monad.Trans.Class (lift)
 | 
					import Control.Monad.Trans.Class (lift)
 | 
				
			||||||
import Control.Monad.Trans.Except (runExceptT)
 | 
					import Control.Monad.Trans.Except (runExceptT)
 | 
				
			||||||
import Control.Monad.Trans.Reader (runReaderT)
 | 
					import Control.Monad.Trans.Reader (runReaderT)
 | 
				
			||||||
import Data.Foldable (find, fold)
 | 
					import Data.Foldable (fold, toList)
 | 
				
			||||||
import Data.Maybe (fromMaybe)
 | 
					import Data.Maybe (fromMaybe)
 | 
				
			||||||
import qualified Data.Aeson as Aeson
 | 
					import qualified Data.Aeson as Aeson
 | 
				
			||||||
import Data.HashMap.Strict (HashMap)
 | 
					import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
@@ -38,6 +39,15 @@ data Resolver m = Resolver
 | 
				
			|||||||
    Text -- ^ Name
 | 
					    Text -- ^ Name
 | 
				
			||||||
    (Field -> CollectErrsT m Aeson.Object) -- ^ Resolver
 | 
					    (Field -> CollectErrsT m Aeson.Object) -- ^ Resolver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- | Converts resolvers to a map.
 | 
				
			||||||
 | 
					resolversToMap
 | 
				
			||||||
 | 
					    :: (Foldable f, Functor f)
 | 
				
			||||||
 | 
					    => f (Resolver m)
 | 
				
			||||||
 | 
					    -> HashMap Text (Field -> CollectErrsT m Aeson.Object)
 | 
				
			||||||
 | 
					resolversToMap = HashMap.fromList . toList . fmap toKV
 | 
				
			||||||
 | 
					  where
 | 
				
			||||||
 | 
					    toKV (Resolver name f) = (name, f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | Contains variables for the query. The key of the map is a variable name,
 | 
					-- | Contains variables for the query. The key of the map is a variable name,
 | 
				
			||||||
--   and the value is the variable value.
 | 
					--   and the value is the variable value.
 | 
				
			||||||
type Subs = HashMap Name Value
 | 
					type Subs = HashMap Name Value
 | 
				
			||||||
@@ -46,7 +56,8 @@ type Subs = HashMap Name Value
 | 
				
			|||||||
object :: Monad m => Name -> ActionT m [Resolver m] -> Resolver m
 | 
					object :: Monad m => Name -> ActionT m [Resolver m] -> Resolver m
 | 
				
			||||||
object name f = Resolver name $ resolveFieldValue f resolveRight
 | 
					object name f = Resolver name $ resolveFieldValue f resolveRight
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    resolveRight fld@(Field _ _ _ flds) resolver = withField (resolve resolver flds) fld
 | 
					    resolveRight fld@(Field _ _ _ flds) resolver
 | 
				
			||||||
 | 
					        = withField (resolve (resolversToMap resolver) flds) fld
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | Like 'object' but can be null or a list of objects.
 | 
					-- | Like 'object' but can be null or a list of objects.
 | 
				
			||||||
wrappedObject ::
 | 
					wrappedObject ::
 | 
				
			||||||
@@ -57,7 +68,8 @@ wrappedObject ::
 | 
				
			|||||||
wrappedObject name f = Resolver name $ resolveFieldValue f resolveRight
 | 
					wrappedObject name f = Resolver name $ resolveFieldValue f resolveRight
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    resolveRight fld@(Field _ _ _ sels) resolver
 | 
					    resolveRight fld@(Field _ _ _ sels) resolver
 | 
				
			||||||
        = withField (traverse (`resolve` sels) resolver) fld
 | 
					        = withField (traverse (resolveMap sels) resolver) fld
 | 
				
			||||||
 | 
					    resolveMap = flip (resolve . resolversToMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | A scalar represents a primitive value, like a string or an integer.
 | 
					-- | A scalar represents a primitive value, like a string or an integer.
 | 
				
			||||||
scalar :: (Monad m, Aeson.ToJSON a) => Name -> ActionT m a -> Resolver m
 | 
					scalar :: (Monad m, Aeson.ToJSON a) => Name -> ActionT m a -> Resolver m
 | 
				
			||||||
@@ -81,7 +93,7 @@ wrappedScalar name f = Resolver name $ resolveFieldValue f resolveRight
 | 
				
			|||||||
resolveFieldValue ::
 | 
					resolveFieldValue ::
 | 
				
			||||||
    Monad m =>
 | 
					    Monad m =>
 | 
				
			||||||
    ActionT m a ->
 | 
					    ActionT m a ->
 | 
				
			||||||
    (Field -> a -> CollectErrsT m (HashMap Text Aeson.Value)) ->
 | 
					    (Field -> a -> CollectErrsT m Aeson.Object) ->
 | 
				
			||||||
    Field ->
 | 
					    Field ->
 | 
				
			||||||
    CollectErrsT m (HashMap Text Aeson.Value)
 | 
					    CollectErrsT m (HashMap Text Aeson.Value)
 | 
				
			||||||
resolveFieldValue f resolveRight fld@(Field _ _ args _) = do
 | 
					resolveFieldValue f resolveRight fld@(Field _ _ args _) = do
 | 
				
			||||||
@@ -103,22 +115,21 @@ withField v fld
 | 
				
			|||||||
--   'Resolver' to each 'Field'. Resolves into a value containing the
 | 
					--   'Resolver' to each 'Field'. Resolves into a value containing the
 | 
				
			||||||
--   resolved 'Field', or a null value and error information.
 | 
					--   resolved 'Field', or a null value and error information.
 | 
				
			||||||
resolve :: Monad m
 | 
					resolve :: Monad m
 | 
				
			||||||
    => [Resolver m] -> Seq Selection -> CollectErrsT m Aeson.Value
 | 
					    => HashMap Text (Field -> CollectErrsT m Aeson.Object)
 | 
				
			||||||
 | 
					    -> Seq Selection
 | 
				
			||||||
 | 
					    -> CollectErrsT m Aeson.Value
 | 
				
			||||||
resolve resolvers = fmap (Aeson.toJSON . fold) . traverse tryResolvers
 | 
					resolve resolvers = fmap (Aeson.toJSON . fold) . traverse tryResolvers
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    resolveTypeName (Resolver "__typename" f) = do
 | 
					    resolveTypeName f = do
 | 
				
			||||||
        value <- f $ Field Nothing "__typename" mempty mempty
 | 
					        value <- f $ Field Nothing "__typename" mempty mempty
 | 
				
			||||||
        return $ HashMap.lookupDefault "" "__typename" value
 | 
					        return $ HashMap.lookupDefault "" "__typename" value
 | 
				
			||||||
    resolveTypeName _ = return ""
 | 
					 | 
				
			||||||
    tryResolvers (SelectionField fld@(Field _ name _ _))
 | 
					    tryResolvers (SelectionField fld@(Field _ name _ _))
 | 
				
			||||||
        = maybe (errmsg fld) (tryResolver fld) $ find (compareResolvers name) resolvers
 | 
					        = fromMaybe (errmsg fld) $ HashMap.lookup name resolvers <*> Just fld
 | 
				
			||||||
    tryResolvers (SelectionFragment (Fragment typeCondition selections')) = do
 | 
					    tryResolvers (SelectionFragment (Fragment typeCondition selections')) = do
 | 
				
			||||||
        that <- traverse resolveTypeName (find (compareResolvers "__typename") resolvers)
 | 
					        that <- traverse resolveTypeName $ HashMap.lookup "__typename" resolvers
 | 
				
			||||||
        if maybe True (Aeson.String typeCondition ==) that
 | 
					        if maybe True (Aeson.String typeCondition ==) that
 | 
				
			||||||
            then fmap fold . traverse tryResolvers $ selections'
 | 
					            then fmap fold . traverse tryResolvers $ selections'
 | 
				
			||||||
            else return mempty
 | 
					            else return mempty
 | 
				
			||||||
    compareResolvers name (Resolver name' _) = name == name'
 | 
					 | 
				
			||||||
    tryResolver fld (Resolver _ resolver)  = resolver fld
 | 
					 | 
				
			||||||
    errmsg fld@(Field _ name _ _) = do
 | 
					    errmsg fld@(Field _ name _ _) = do
 | 
				
			||||||
        addErrMsg $ T.unwords ["field", name, "not resolved."]
 | 
					        addErrMsg $ T.unwords ["field", name, "not resolved."]
 | 
				
			||||||
        return $ HashMap.singleton (aliasOrName fld) Aeson.Null
 | 
					        return $ HashMap.singleton (aliasOrName fld) Aeson.Null
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
resolver: lts-15.7
 | 
					resolver: lts-15.11
 | 
				
			||||||
 | 
					
 | 
				
			||||||
packages:
 | 
					packages:
 | 
				
			||||||
- .
 | 
					- .
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,14 +5,18 @@ module Test.DirectiveSpec
 | 
				
			|||||||
    ) where
 | 
					    ) where
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Data.Aeson (Value, object, (.=))
 | 
					import Data.Aeson (Value, object, (.=))
 | 
				
			||||||
 | 
					import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
 | 
					import qualified Data.HashMap.Strict as HashMap
 | 
				
			||||||
import Data.List.NonEmpty (NonEmpty(..))
 | 
					import Data.List.NonEmpty (NonEmpty(..))
 | 
				
			||||||
 | 
					import Data.Text (Text)
 | 
				
			||||||
import Language.GraphQL
 | 
					import Language.GraphQL
 | 
				
			||||||
import qualified Language.GraphQL.Schema as Schema
 | 
					import qualified Language.GraphQL.Schema as Schema
 | 
				
			||||||
import Test.Hspec (Spec, describe, it, shouldBe)
 | 
					import Test.Hspec (Spec, describe, it, shouldBe)
 | 
				
			||||||
import Text.RawString.QQ (r)
 | 
					import Text.RawString.QQ (r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
experimentalResolver :: Schema.Resolver IO
 | 
					experimentalResolver :: HashMap Text (NonEmpty (Schema.Resolver IO))
 | 
				
			||||||
experimentalResolver = Schema.scalar "experimentalField" $ pure (5 :: Int) 
 | 
					experimentalResolver = HashMap.singleton "Query"
 | 
				
			||||||
 | 
					    $ Schema.scalar "experimentalField" (pure (5 :: Int)) :| []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
emptyObject :: Value
 | 
					emptyObject :: Value
 | 
				
			||||||
emptyObject = object
 | 
					emptyObject = object
 | 
				
			||||||
@@ -29,7 +33,7 @@ spec =
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (experimentalResolver :| []) query
 | 
					            actual <- graphql experimentalResolver query
 | 
				
			||||||
            actual `shouldBe` emptyObject
 | 
					            actual `shouldBe` emptyObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "should not skip fields if @skip is false" $ do
 | 
					        it "should not skip fields if @skip is false" $ do
 | 
				
			||||||
@@ -44,7 +48,7 @@ spec =
 | 
				
			|||||||
                        ]
 | 
					                        ]
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (experimentalResolver :| []) query
 | 
					            actual <- graphql experimentalResolver query
 | 
				
			||||||
            actual `shouldBe` expected
 | 
					            actual `shouldBe` expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "should skip fields if @include is false" $ do
 | 
					        it "should skip fields if @include is false" $ do
 | 
				
			||||||
@@ -54,7 +58,7 @@ spec =
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (experimentalResolver :| []) query
 | 
					            actual <- graphql experimentalResolver query
 | 
				
			||||||
            actual `shouldBe` emptyObject
 | 
					            actual `shouldBe` emptyObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "should be able to @skip a fragment spread" $ do
 | 
					        it "should be able to @skip a fragment spread" $ do
 | 
				
			||||||
@@ -68,7 +72,7 @@ spec =
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (experimentalResolver :| []) query
 | 
					            actual <- graphql experimentalResolver query
 | 
				
			||||||
            actual `shouldBe` emptyObject
 | 
					            actual `shouldBe` emptyObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "should be able to @skip an inline fragment" $ do
 | 
					        it "should be able to @skip an inline fragment" $ do
 | 
				
			||||||
@@ -80,5 +84,5 @@ spec =
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (experimentalResolver :| []) query
 | 
					            actual <- graphql experimentalResolver query
 | 
				
			||||||
            actual `shouldBe` emptyObject
 | 
					            actual `shouldBe` emptyObject
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ spec :: Spec
 | 
				
			|||||||
spec = do
 | 
					spec = do
 | 
				
			||||||
    describe "Inline fragment executor" $ do
 | 
					    describe "Inline fragment executor" $ do
 | 
				
			||||||
        it "chooses the first selection if the type matches" $ do
 | 
					        it "chooses the first selection if the type matches" $ do
 | 
				
			||||||
            actual <- graphql (garment "Hat" :| []) inlineQuery
 | 
					            actual <- graphql (HashMap.singleton "Query" $ garment "Hat" :| []) inlineQuery
 | 
				
			||||||
            let expected = object
 | 
					            let expected = object
 | 
				
			||||||
                    [ "data" .= object
 | 
					                    [ "data" .= object
 | 
				
			||||||
                        [ "garment" .= object
 | 
					                        [ "garment" .= object
 | 
				
			||||||
@@ -62,7 +62,7 @@ spec = do
 | 
				
			|||||||
             in actual `shouldBe` expected
 | 
					             in actual `shouldBe` expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "chooses the last selection if the type matches" $ do
 | 
					        it "chooses the last selection if the type matches" $ do
 | 
				
			||||||
            actual <- graphql (garment "Shirt" :| []) inlineQuery
 | 
					            actual <- graphql (HashMap.singleton "Query" $ garment "Shirt" :| []) inlineQuery
 | 
				
			||||||
            let expected = object
 | 
					            let expected = object
 | 
				
			||||||
                    [ "data" .= object
 | 
					                    [ "data" .= object
 | 
				
			||||||
                        [ "garment" .= object
 | 
					                        [ "garment" .= object
 | 
				
			||||||
@@ -83,7 +83,7 @@ spec = do
 | 
				
			|||||||
            }|]
 | 
					            }|]
 | 
				
			||||||
                resolvers = Schema.object "garment" $ return [circumference,  size]
 | 
					                resolvers = Schema.object "garment" $ return [circumference,  size]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (resolvers :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ resolvers :| []) query
 | 
				
			||||||
            let expected = object
 | 
					            let expected = object
 | 
				
			||||||
                    [ "data" .= object
 | 
					                    [ "data" .= object
 | 
				
			||||||
                        [ "garment" .= object
 | 
					                        [ "garment" .= object
 | 
				
			||||||
@@ -101,7 +101,7 @@ spec = do
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            }|]
 | 
					            }|]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (size :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ size :| []) query
 | 
				
			||||||
            actual `shouldNotSatisfy` hasErrors
 | 
					            actual `shouldNotSatisfy` hasErrors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe "Fragment spread executor" $ do
 | 
					    describe "Fragment spread executor" $ do
 | 
				
			||||||
@@ -116,7 +116,7 @@ spec = do
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (circumference :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ circumference :| []) query
 | 
				
			||||||
            let expected = object
 | 
					            let expected = object
 | 
				
			||||||
                    [ "data" .= object
 | 
					                    [ "data" .= object
 | 
				
			||||||
                        [ "circumference" .= (60 :: Int)
 | 
					                        [ "circumference" .= (60 :: Int)
 | 
				
			||||||
@@ -141,7 +141,7 @@ spec = do
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (garment "Hat" :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ garment "Hat" :| []) query
 | 
				
			||||||
            let expected = object
 | 
					            let expected = object
 | 
				
			||||||
                    [ "data" .= object
 | 
					                    [ "data" .= object
 | 
				
			||||||
                        [ "garment" .= object
 | 
					                        [ "garment" .= object
 | 
				
			||||||
@@ -162,7 +162,7 @@ spec = do
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            |]
 | 
					            |]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            actual <- graphql (circumference :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ circumference :| []) query
 | 
				
			||||||
            actual `shouldSatisfy` hasErrors
 | 
					            actual `shouldSatisfy` hasErrors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it "considers type condition" $ do
 | 
					        it "considers type condition" $ do
 | 
				
			||||||
@@ -187,5 +187,5 @@ spec = do
 | 
				
			|||||||
                            ]
 | 
					                            ]
 | 
				
			||||||
                        ]
 | 
					                        ]
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
            actual <- graphql (garment "Hat" :| []) query
 | 
					            actual <- graphql (HashMap.singleton "Query" $ garment "Hat" :| []) query
 | 
				
			||||||
            actual `shouldBe` expected
 | 
					            actual `shouldBe` expected
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,11 @@ module Test.StarWars.Schema
 | 
				
			|||||||
import Control.Monad.Trans.Except (throwE)
 | 
					import Control.Monad.Trans.Except (throwE)
 | 
				
			||||||
import Control.Monad.Trans.Class (lift)
 | 
					import Control.Monad.Trans.Class (lift)
 | 
				
			||||||
import Data.Functor.Identity (Identity)
 | 
					import Data.Functor.Identity (Identity)
 | 
				
			||||||
 | 
					import Data.HashMap.Strict (HashMap)
 | 
				
			||||||
 | 
					import qualified Data.HashMap.Strict as HashMap
 | 
				
			||||||
import Data.List.NonEmpty (NonEmpty(..))
 | 
					import Data.List.NonEmpty (NonEmpty(..))
 | 
				
			||||||
import Data.Maybe (catMaybes)
 | 
					import Data.Maybe (catMaybes)
 | 
				
			||||||
 | 
					import Data.Text (Text)
 | 
				
			||||||
import qualified Language.GraphQL.Schema as Schema
 | 
					import qualified Language.GraphQL.Schema as Schema
 | 
				
			||||||
import Language.GraphQL.Trans
 | 
					import Language.GraphQL.Trans
 | 
				
			||||||
import qualified Language.GraphQL.Type as Type
 | 
					import qualified Language.GraphQL.Type as Type
 | 
				
			||||||
@@ -19,8 +22,8 @@ import Test.StarWars.Data
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsSchema.js
 | 
					-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsSchema.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
schema :: NonEmpty (Schema.Resolver Identity)
 | 
					schema :: HashMap Text (NonEmpty (Schema.Resolver Identity))
 | 
				
			||||||
schema = hero :| [human, droid]
 | 
					schema = HashMap.singleton "Query" $ hero :| [human, droid]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hero :: Schema.Resolver Identity
 | 
					hero :: Schema.Resolver Identity
 | 
				
			||||||
hero = Schema.object "hero" $ do
 | 
					hero = Schema.object "hero" $ do
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user