git.iko.soy/rowtype-yoga/purescript-graphql
on master help sign in
Add support for mutations, variables and operation

With this change the `execute` function has been adopted to be able to execute GraphQL mutations. Furthermore the `execute` function as well as the `graphql` function now accept variables in a map and an operation name. This way the query can specify multiple queries and only the one matching the operation name will be executed.

commit3746c2b99c9db922de262b3fd5e70fa82452186a authorhendrikniemann <h.neverman@web.de> dateSat 22 Feb 2020, 19:37 (7 years ago) parentcb0a970
6 files changed+85−27
README.md+3−2 src/GraphQL.purs+22−6 src/GraphQL/Execution.purs+48−11 src/GraphQL/Type.purs+1−1 test/Execution.purs+6−4 test/Readme.purs+5−3
README.md+3−2
@@ -21,6 +21,7 @@ import Prelude
2121
2222import Data.Argonaut.Core (stringify)
2323import Data.Either (Either(..))
24+import Data.Map as Map
2425import Effect (Effect)
2526import Effect.Console as Console
2627import Effect.Exception (Error)
@@ -30,13 +31,13 @@ import GraphQL.Type as GraphQL
3031import GraphQL.Type.Scalar as Scalar
3132
3233main :: Effect Unit
33−main = case graphql schema "{ hello }" (pure unit) of
34+main = case graphql schema "{ hello }" Map.empty Nothing (pure unit) of
3435 Left error -> Console.error $ show error
3536 Right result -> Console.log $ stringify result
3637-- {"data":{"hello":"world"}}
3738
3839schema :: GraphQL.Schema (Either Error) Unit
39−schema = GraphQL.Schema { query: queryType }
40+schema = GraphQL.Schema { query: queryType, mutation: Nothing }
4041
4142queryType :: GraphQL.ObjectType (Either Error) Unit
4243queryType =
src/GraphQL.purs+22−6
@@ -5,13 +5,29 @@ import Prelude
55import Control.Monad.Error.Class (class MonadError, throwError)
66import Data.Argonaut.Core (Json)
77import Data.Either (Either(..))
8−import Data.Map (empty)
8+import Data.Maybe (Maybe)
99import Effect.Exception (Error, error)
1010import GraphQL.Execution (execute)
1111import GraphQL.Language (parse)
12−import GraphQL.Type (Schema)
12+import GraphQL.Type (Schema, VariableMap)
1313
14−graphql :: ∀ m a. MonadError Error m => Schema m a -> String -> m a -> m Json
15−graphql schema query root = case parse query of
16− Left message -> throwError $ error message
17− Right document -> execute document schema empty root
14+-- | Parses a GraphQL query string into a document and then executes the query given the parameters
15+-- |
16+-- | Parameters:
17+-- | - *schema*: the schema the query is evaluated against
18+-- | - *query*: the GraphQL query string
19+-- | - *variables*: a map of variables with their corresponding JSON values
20+-- | - *operation*: an optional operation name - can be omitted if query contains only one operation
21+-- | - *root*: a root value to be passed to the query and mutation resolvers
22+graphql ::
23+ ∀ m a.
24+ MonadError Error m =>
25+ Schema m a ->
26+ String ->
27+ VariableMap ->
28+ Maybe String ->
29+ m a ->
30+ m Json
31+graphql schema query variables operation root = case parse query of
32+ Left message -> throwError $ error $ "Parsing Error: " <> message
33+ Right document -> execute document schema variables operation root
src/GraphQL/Execution.purs+48−11
@@ -2,23 +2,60 @@ module GraphQL.Execution (execute) where
22
33import Prelude
44
5−import Control.Monad.Error.Class (class MonadError)
5+import Control.Monad.Error.Class (class MonadError, throwError)
66import Data.Argonaut.Core (Json, jsonEmptyObject)
77import Data.Argonaut.Encode ((:=), (~>))
8−import Data.List (List(..))
9−import Data.Maybe (Maybe(..))
10−import Effect.Exception (Error)
8+import Data.List (List(..), find)
9+import Data.Maybe (Maybe(..), fromMaybe, maybe)
10+import Effect.Exception (Error, error)
1111import GraphQL.Execution.Result (serializeResult)
12−import GraphQL.Language.AST (DefinitionNode(..), DocumentNode(..), OperationTypeNode(..))
12+import GraphQL.Language.AST (DefinitionNode(..), DocumentNode(..), NameNode(..), OperationTypeNode(..))
1313import GraphQL.Type (Schema(..), VariableMap, output)
1414
15+
1516simpleError :: forall m. Applicative m => String -> m Json
1617simpleError e = pure $ "error" := e ~> jsonEmptyObject
1718
18−execute :: forall m a. MonadError Error m => DocumentNode -> Schema m a -> VariableMap -> m a -> m Json
19−execute (DocumentNode { definitions: Cons query Nil }) (Schema s) variables root =
20− case query of
19+
20+execute :: forall m a.
21+ MonadError Error m =>
22+ DocumentNode ->
23+ Schema m a ->
24+ VariableMap ->
25+ Maybe String ->
26+ m a ->
27+ m Json
28+execute (DocumentNode { definitions }) (Schema s) variables operation root = do
29+ -- Find the operation with the operation name given supplied. If no operation name is supplied,
30+ -- return the only operation of the document. If multiple operations are supplied without an
31+ -- operation name, or the named operation cannot be found in the document, throw an error.
32+ definitionNode <- case operation of
33+ Just operationName ->
34+ fromMaybe
35+ (throwError $ error $ "Could not find definitions with name " <> operationName <> ".")
36+ (pure <$> find (hasOperationName operationName) definitions)
37+
38+ Nothing -> case definitions of
39+ Nil -> throwError $ error "No definitions found in document."
40+ Cons query Nil -> pure query
41+ _ -> throwError $ error "Operation name is required for queries with multiple operations."
42+
43+ executionResult <- case definitionNode of
2144 OperationDefinitionNode { operation: Query, selectionSet } ->
22− serializeResult <$> output s.query (Just selectionSet) variables root
23− _ -> simpleError "Somehow received non OperationDefinitionNode for operation execution..."
24−execute _ _ _ _ = simpleError "Can only execute documents with single query"
45+ output s.query (Just selectionSet) variables root
46+
47+ OperationDefinitionNode { operation: Mutation, selectionSet } ->
48+ maybe
49+ (throwError $ error "Api does not support mutations.")
50+ (\mutation -> output mutation (Just selectionSet) variables root)
51+ s.mutation
52+
53+ _ -> throwError $ error "Unrecognised operation."
54+
55+ pure $ serializeResult executionResult
56+
57+ where
58+ hasOperationName operationName (OperationDefinitionNode { name: Just (NameNode nameNode) }) =
59+ nameNode.value == operationName
60+
61+ hasOperationName _ _ = false
src/GraphQL/Type.purs+1−1
@@ -23,7 +23,7 @@ import Type.RowList as RL
2323import Unsafe.Coerce (unsafeCoerce)
2424
2525-- | The schema contains the central entry points for GraphQL queries.
26−newtype Schema m a = Schema { query :: ObjectType m a }
26+newtype Schema m a = Schema { query :: ObjectType m a, mutation :: Maybe (ObjectType m a) }
2727
2828type VariableMap = Map String Json.Json
2929
test/Execution.purs+6−4
@@ -10,6 +10,7 @@ import Data.Generic.Rep.Bounded (genericBottom, genericTop)
1010import Data.Generic.Rep.Enum (genericPred, genericSucc)
1111import Data.Generic.Rep.Eq (genericEq)
1212import Data.Generic.Rep.Ord (genericCompare)
13+import Data.Map as Map
1314import Data.Maybe (Maybe(..))
1415import Data.Newtype (class Newtype, unwrap)
1516import Data.String (trim)
@@ -53,7 +54,7 @@ instance showUserLevel :: Show UserLevel where
5354
5455
5556testSchema :: GQL.Schema (Either Error) String
56−testSchema = GQL.Schema { query: queryType }
57+testSchema = GQL.Schema { query: queryType, mutation: Nothing }
5758
5859
5960queryType :: GQL.ObjectType (Either Error) String
@@ -106,10 +107,11 @@ userLevelType =
106107
107108
108109testQuery :: String -> String -> Aff Unit
109−testQuery query expected = case map stringify (graphql testSchema query (pure "Hendrik")) of
110− Right res -> res `shouldEqual` expected
110+testQuery query expected =
111+ case graphql testSchema query Map.empty Nothing (pure "Hendrik") of
112+ Right res -> stringify res `shouldEqual` expected
111113
112− Left _ -> fail "failed to execute query"
114+ Left message -> fail $ show message
113115
114116
115117executionSpec :: Spec Unit
test/Readme.purs+5−3
@@ -5,6 +5,8 @@ import Prelude
55import Control.Monad.Error.Class (throwError)
66import Data.Argonaut.Core (stringify)
77import Data.Either (Either(..))
8+import Data.Map as Map
9+import Data.Maybe (Maybe(..))
810import Effect (Effect)
911import Effect.Console as Console
1012import Effect.Exception (Error)
@@ -16,13 +18,13 @@ import Test.Spec (Spec, describe, it)
1618import Test.Spec.Assertions (shouldEqual)
1719
1820main :: Effect Unit
19−main = case graphql schema "{ hello }" (pure unit) of
21+main = case graphql schema "{ hello }" Map.empty Nothing (pure unit) of
2022 Left error -> Console.error $ show error
2123 Right result -> Console.log $ stringify result
2224-- {"data":{"hello":"world"}}
2325
2426schema :: GraphQL.Schema (Either Error) Unit
25−schema = GraphQL.Schema { query: queryType }
27+schema = GraphQL.Schema { query: queryType, mutation: Nothing }
2628
2729queryType :: GraphQL.ObjectType (Either Error) Unit
2830queryType =
@@ -37,6 +39,6 @@ readmeSpec :: Spec Unit
3739readmeSpec =
3840 describe "Readme" $
3941 it "should execute the given query" $
40− case graphql schema "{ hello }" (pure unit) of
42+ case graphql schema "{ hello }" Map.empty Nothing (pure unit) of
4143 Left error -> throwError error
4244 Right result -> stringify result `shouldEqual` """{"data":{"hello":"world"}}"""