forked from haskell-graphql/graphql-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExecution.hs
More file actions
116 lines (103 loc) · 4.3 KB
/
Execution.hs
File metadata and controls
116 lines (103 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PatternSynonyms #-}
{-# OPTIONS_HADDOCK not-home #-}
-- | Description: Implement the \"Execution\" part of the GraphQL spec.
--
-- Actually, most of the execution work takes place in 'GraphQL.Resolver', but
-- there's still a fair bit required to glue together the results of
-- 'GraphQL.Internal.Validation' and the processing in 'GraphQL.Resolver'.
-- This module provides that glue.
module GraphQL.Internal.Execution
( VariableValues
, ExecutionError(..)
, formatError
, getOperation
, substituteVariables
) where
import Protolude
import qualified Data.Aeson as Aeson
import qualified Data.Map as Map
import GraphQL.Value
( Name
, Value
, pattern ValueNull
, Value'(..)
, List'(..)
, Object'(..)
)
import GraphQL.Internal.Output (GraphQLError(..))
import GraphQL.Internal.Schema
( AnnotatedType (TypeNonNull)
)
import GraphQL.Internal.Validation
( Operation
, QueryDocument(..)
, VariableDefinition(..)
, VariableValue
, Variable
)
-- | Get an operation from a GraphQL document
--
-- <https://facebook.github.io/graphql/#sec-Executing-Requests>
--
-- GetOperation(document, operationName):
--
-- * If {operationName} is {null}:
-- * If {document} contains exactly one operation.
-- * Return the Operation contained in the {document}.
-- * Otherwise produce a query error requiring {operationName}.
-- * Otherwise:
-- * Let {operation} be the Operation named {operationName} in {document}.
-- * If {operation} was not found, produce a query error.
-- * Return {operation}.
getOperation :: QueryDocument value -> Maybe Name -> Either ExecutionError (Operation value)
getOperation (LoneAnonymousOperation op) Nothing = pure op
getOperation (MultipleOperations ops) (Just name) = note (NoSuchOperation name) (Map.lookup (pure name) ops)
getOperation (MultipleOperations ops) Nothing =
case toList ops of
[op] -> pure op
_ -> throwError NoAnonymousOperation
getOperation _ (Just name) = throwError (NoSuchOperation name)
-- | Substitute variables in a GraphQL document.
--
-- Once this is done, there will be no variables in the document whatsoever.
substituteVariables :: Operation VariableValue -> VariableValues -> Either ExecutionError (Operation Value)
substituteVariables op vars = traverse (replaceVariable vars) op
replaceVariable :: VariableValues -> VariableValue -> Either ExecutionError Value
replaceVariable vars value =
case value of
ValueScalar' (Left defn) -> getValue defn
ValueScalar' (Right v) -> pure (ValueScalar' v)
ValueList' (List' xs) -> ValueList' . List' <$> traverse (replaceVariable vars) xs
ValueObject' (Object' xs) -> ValueObject' . Object' <$> traverse (replaceVariable vars) xs
where
getValue :: VariableDefinition -> Either ExecutionError Value
getValue (VariableDefinition variableName variableType defaultValue) =
note (MissingValue variableName) $
Map.lookup variableName vars <|> defaultValue <|> allowNull variableType
allowNull (TypeNonNull _) = empty
allowNull _ = pure ValueNull
-- | An error that occurs while executing a query. Technically,
-- 'ResolverError' also falls into the same category, but is separate to help
-- our code be a bit better organized.
data ExecutionError
= MissingValue Variable
| NoSuchOperation Name
| NoAnonymousOperation
deriving (Eq, Show)
instance GraphQLError ExecutionError where
formatError (MissingValue name) = "Missing value for " <> show name <> " and must be non-null."
formatError (NoSuchOperation name) = "Requested operation " <> show name <> " but couldn't find it."
formatError NoAnonymousOperation = "No name supplied for opertaion, but no anonymous operation."
-- | A map of variables to their values.
--
-- In GraphQL the variable values are not part of the query itself, they are
-- instead passed in through a separate channel. Create a 'VariableValues'
-- from this other channel and pass it to 'substituteVariables'.
--
-- GraphQL allows the values of variables to be specified, but doesn't provide
-- a way for doing so in the language.
type VariableValues = Map Variable Value
-- | The raw (textual and/or aeson based) version of the 'Request' datatype.
-- See <http://graphql.org/learn/serving-over-http/#post-request>.
data RawPostRequest = RawPostRequest Text (Maybe Text) Aeson.Object deriving (Eq, Show)