Skip to content

Commit 3f67822

Browse files
committed
Expand introspection system
1 parent 67b35c7 commit 3f67822

12 files changed

+752
-38
lines changed

graphql-api.cabal

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--
33
-- see: https://github.com/sol/hpack
44
--
5-
-- hash: 24bc26dbd1f77e90690a71683ae20372d13f4729b6073940a17679e5bc18c609
5+
-- hash: 796abb771a05858c20303db3e1ddd30cbdf11f24f02e09eed0fe8a9850c43269
66

77
name: graphql-api
88
version: 0.3.0
@@ -67,6 +67,7 @@ library
6767
GraphQL.Internal.Value
6868
GraphQL.Internal.Value.FromValue
6969
GraphQL.Internal.Value.ToValue
70+
GraphQL.Introspection
7071
GraphQL.Resolver
7172
GraphQL.Value
7273
other-modules:
@@ -145,16 +146,19 @@ test-suite graphql-api-tests
145146
, raw-strings-qq
146147
, tasty
147148
, tasty-hspec
149+
, template-haskell
148150
, text
149151
, transformers
150152
other-modules:
151153
ASTTests
152154
EndToEndTests
153155
EnumTests
154156
ExampleSchema
157+
MutationTests
155158
OrderedMapTests
156159
ResolverTests
157160
SchemaTests
161+
Utils
158162
ValidationTests
159163
ValueTests
160164
Paths_graphql_api

package.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ tests:
7474
- aeson-qq
7575
- tasty
7676
- tasty-hspec
77+
- template-haskell
7778
- text
7879
- directory
7980

src/GraphQL.hs

+90-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE PatternSynonyms #-}
33
{-# LANGUAGE RankNTypes #-}
44
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE TypeFamilies #-}
56
-- | Interface for GraphQL API.
67
--
78
-- __Note__: This module is highly subject to change. We're still figuring
@@ -10,14 +11,17 @@ module GraphQL
1011
(
1112
-- * Running queries
1213
interpretQuery
14+
, interpretRequest
1315
, interpretAnonymousQuery
1416
, Response(..)
1517
-- * Preparing queries then running them
1618
, makeSchema
1719
, compileQuery
1820
, executeQuery
21+
, executeRequest
1922
, QueryError
2023
, Schema
24+
, SchemaRoot(..)
2125
, VariableValues
2226
, Value
2327
) where
@@ -30,7 +34,7 @@ import qualified Data.List.NonEmpty as NonEmpty
3034
import GraphQL.API (HasObjectDefinition(..), SchemaError(..))
3135
import GraphQL.Internal.Execution
3236
( VariableValues
33-
, ExecutionError
37+
, ExecutionError(..)
3438
, substituteVariables
3539
)
3640
import qualified GraphQL.Internal.Execution as Execution
@@ -43,6 +47,9 @@ import GraphQL.Internal.Validation
4347
, validate
4448
, getSelectionSet
4549
, VariableValue
50+
, Operation(..)
51+
, DefinitionType(..)
52+
, getDefinitionType
4653
)
4754
import GraphQL.Internal.Output
4855
( GraphQLError(..)
@@ -83,6 +90,16 @@ instance GraphQLError QueryError where
8390
formatError (NonObjectResult v) =
8491
"Query returned a value that is not an object: " <> show v
8592

93+
toResult :: Result Value -> Response
94+
toResult (Result errors result) = case result of
95+
-- TODO: Prevent this at compile time. Particularly frustrating since
96+
-- we *know* that queries and mutations have object definitions
97+
ValueObject object ->
98+
case NonEmpty.nonEmpty errors of
99+
Nothing -> Success object
100+
Just errs -> PartialSuccess object (map toError errs)
101+
v -> ExecutionFailure (singleError (NonObjectResult v))
102+
86103
-- | Execute a GraphQL query.
87104
executeQuery
88105
:: forall api m. (HasResolver m api, Applicative m, HasObjectDefinition api)
@@ -94,17 +111,7 @@ executeQuery
94111
executeQuery handler document name variables =
95112
case getOperation document name variables of
96113
Left e -> pure (ExecutionFailure (singleError e))
97-
Right operation -> toResult <$> resolve @m @api handler (Just operation)
98-
where
99-
toResult (Result errors result) =
100-
case result of
101-
-- TODO: Prevent this at compile time. Particularly frustrating since
102-
-- we *know* that api has an object definition.
103-
ValueObject object ->
104-
case NonEmpty.nonEmpty errors of
105-
Nothing -> Success object
106-
Just errs -> PartialSuccess object (map toError errs)
107-
v -> ExecutionFailure (singleError (NonObjectResult v))
114+
Right (_, ss) -> toResult <$> resolve @m @api handler (Just ss)
108115

109116
-- | Create a GraphQL schema.
110117
makeSchema :: forall api. HasObjectDefinition api => Either QueryError Schema
@@ -135,6 +142,75 @@ interpretAnonymousQuery
135142
-> m Response -- ^ The result of running the query.
136143
interpretAnonymousQuery handler query = interpretQuery @api @m handler query Nothing mempty
137144

145+
data SchemaRoot m query mutation = SchemaRoot
146+
{ queries :: Handler m query
147+
, mutations :: Handler m mutation
148+
}
149+
150+
-- | Execute a query or mutation
151+
--
152+
-- Similar to executeQuery, execept requests are dispatched against the
153+
-- SchemaRoot depending on whether they are a query or mutation
154+
executeRequest
155+
:: forall schema queries mutations m.
156+
( schema ~ SchemaRoot m queries mutations
157+
, HasResolver m queries
158+
, HasObjectDefinition queries
159+
, HasResolver m mutations
160+
, HasObjectDefinition mutations
161+
, Monad m
162+
)
163+
=> SchemaRoot m queries mutations
164+
-> QueryDocument VariableValue
165+
-> Maybe Name
166+
-> VariableValues
167+
-> m Response
168+
executeRequest (SchemaRoot qh mh) document name variables =
169+
case getOperation document name variables of
170+
Left e -> pure (ExecutionFailure (singleError e))
171+
Right (operation, ss) -> do
172+
toResult <$> case operation of
173+
Query _ _ _ -> resolve @m @queries qh (Just ss)
174+
Mutation _ _ _ -> resolve @m @mutations mh (Just ss)
175+
176+
-- | Interpret a query or mutation against a SchemaRoot
177+
interpretRequest
178+
:: forall schema queries mutations m.
179+
( schema ~ SchemaRoot m queries mutations
180+
, HasResolver m queries
181+
, HasObjectDefinition queries
182+
, HasResolver m mutations
183+
, HasObjectDefinition mutations
184+
, Monad m
185+
)
186+
=> SchemaRoot m queries mutations
187+
-> Text
188+
-> Maybe Name
189+
-> VariableValues
190+
-> m Response
191+
interpretRequest (SchemaRoot qh mh) text name variables = case parseQuery text of
192+
Left err -> pure (PreExecutionFailure (toError (ParseError err) :| []))
193+
Right document ->
194+
case getDefinitionType document name of
195+
Just operation -> case operation of
196+
QueryDefinition -> run @m @queries qh document
197+
MutationDefinition -> run @m @mutations mh document
198+
_ ->
199+
let err = maybe NoAnonymousOperation NoSuchOperation name
200+
in pure (ExecutionFailure (toError err :| []))
201+
where
202+
run :: forall n api.
203+
( HasObjectDefinition api
204+
, HasResolver n api
205+
, Applicative n
206+
)
207+
=> Handler n api -> AST.QueryDocument -> n Response
208+
run h doc = case makeSchema @api of
209+
Left e -> pure (PreExecutionFailure (toError e :| []))
210+
Right schema -> case validate schema doc of
211+
Left e -> pure (PreExecutionFailure (toError (ValidationError e) :| []))
212+
Right validated -> executeQuery @api h validated name variables
213+
138214
-- | Turn some text into a valid query document.
139215
compileQuery :: Schema -> Text -> Either QueryError (QueryDocument VariableValue)
140216
compileQuery schema query = do
@@ -146,8 +222,8 @@ parseQuery :: Text -> Either Text AST.QueryDocument
146222
parseQuery query = first toS (parseOnly (Parser.queryDocument <* endOfInput) query)
147223

148224
-- | Get an operation from a query document ready to be processed.
149-
getOperation :: QueryDocument VariableValue -> Maybe Name -> VariableValues -> Either QueryError (SelectionSetByType Value)
225+
getOperation :: QueryDocument VariableValue -> Maybe Name -> VariableValues -> Either QueryError (Operation VariableValue, SelectionSetByType Value)
150226
getOperation document name vars = first ExecutionError $ do
151227
op <- Execution.getOperation document name
152228
resolved <- substituteVariables op vars
153-
pure (getSelectionSet resolved)
229+
pure (op, getSelectionSet resolved)

src/GraphQL/Internal/API.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ module GraphQL.Internal.API
2626
, HasAnnotatedType(..)
2727
, HasAnnotatedInputType
2828
, HasObjectDefinition(..)
29+
, HasFieldDefinitions(..)
30+
, HasInterfaceDefinitions(..)
2931
, getArgumentDefinition
3032
, SchemaError(..)
3133
, nameFromSymbol
@@ -75,7 +77,6 @@ import GraphQL.Internal.Output (GraphQLError(..))
7577
data a :> b = a :> b
7678
infixr 8 :>
7779

78-
7980
data Object (name :: Symbol) (interfaces :: [Type]) (fields :: [Type])
8081
data Enum (name :: Symbol) (values :: Type)
8182
data Union (name :: Symbol) (types :: [Type])
@@ -87,7 +88,6 @@ data Interface (name :: Symbol) (fields :: [Type])
8788
data Field (name :: Symbol) (fieldType :: Type)
8889
data Argument (name :: Symbol) (argType :: Type)
8990

90-
9191
-- | The type-level schema was somehow invalid.
9292
data SchemaError
9393
= NameError NameError

src/GraphQL/Internal/Schema.hs

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module GraphQL.Internal.Schema
2323
, ObjectTypeDefinition(..)
2424
, UnionTypeDefinition(..)
2525
, ScalarTypeDefinition(..)
26+
, TypeExtensionDefinition(..)
2627
-- ** Input types
2728
, InputType(..)
2829
, InputTypeDefinition(..)
@@ -143,7 +144,7 @@ instance DefinesTypes TypeDefinition where
143144
TypeDefinitionUnion x -> getDefinedTypes x
144145
TypeDefinitionScalar x -> getDefinedTypes x
145146
TypeDefinitionEnum x -> getDefinedTypes x
146-
TypeDefinitionInputObject _ -> mempty
147+
TypeDefinitionInputObject x -> getDefinedTypes x
147148
TypeDefinitionTypeExtension _ ->
148149
panic "TODO: we should remove the 'extend' behaviour entirely"
149150

@@ -254,12 +255,20 @@ data InputObjectTypeDefinition = InputObjectTypeDefinition Name (NonEmpty InputO
254255
instance HasName InputObjectTypeDefinition where
255256
getName (InputObjectTypeDefinition name _) = name
256257

258+
instance DefinesTypes InputObjectTypeDefinition where
259+
getDefinedTypes obj@(InputObjectTypeDefinition name fields) =
260+
Map.singleton name (TypeDefinitionInputObject obj) <>
261+
foldMap getDefinedTypes fields
262+
257263
data InputObjectFieldDefinition = InputObjectFieldDefinition Name (AnnotatedType InputType) (Maybe DefaultValue)
258264
deriving (Eq, Ord, Show) -- XXX: spec is unclear about default value for input object field definitions
259265

260266
instance HasName InputObjectFieldDefinition where
261267
getName (InputObjectFieldDefinition name _ _) = name
262268

269+
instance DefinesTypes InputObjectFieldDefinition where
270+
getDefinedTypes (InputObjectFieldDefinition _ annotatedInput _) = getDefinedTypes $ getAnnotatedType annotatedInput
271+
263272
newtype TypeExtensionDefinition = TypeExtensionDefinition ObjectTypeDefinition
264273
deriving (Eq, Ord, Show)
265274

src/GraphQL/Internal/Validation.hs

+29-1
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ module GraphQL.Internal.Validation
3939
, QueryDocument(..)
4040
, validate
4141
, getErrors
42+
, DefinitionType(..)
43+
, getDefinitionType
4244
-- * Operating on validated documents
43-
, Operation
45+
, Operation(..)
4446
, getSelectionSet
4547
-- * Executing validated documents
4648
, VariableDefinition(..)
@@ -926,3 +928,29 @@ instance Applicative (Validator e) where
926928
Validator (Left e) <*> _ = Validator (Left e)
927929
Validator _ <*> (Validator (Left e)) = Validator (Left e)
928930
Validator (Right f) <*> Validator (Right x) = Validator (Right (f x))
931+
932+
data DefinitionType = QueryDefinition | MutationDefinition deriving (Eq, Show)
933+
934+
getDefinitionType :: AST.QueryDocument -> Maybe Name -> Maybe DefinitionType
935+
getDefinitionType doc (Just name) =
936+
case find (operationNamed name) $ getOperationDefinitions doc of
937+
Just (AST.Mutation _) -> Just MutationDefinition
938+
Just _ -> Just QueryDefinition
939+
_ -> Nothing
940+
getDefinitionType doc Nothing =
941+
case getOperationDefinitions doc of
942+
[op] -> case op of
943+
(AST.Mutation _) -> Just MutationDefinition
944+
_ -> Just QueryDefinition
945+
_ -> Nothing
946+
947+
getOperationDefinitions :: AST.QueryDocument -> [AST.OperationDefinition]
948+
getOperationDefinitions = mapMaybe extract . AST.getDefinitions
949+
where
950+
extract (AST.DefinitionOperation op) = Just op
951+
extract _ = Nothing
952+
953+
operationNamed :: Name -> AST.OperationDefinition -> Bool
954+
operationNamed n (AST.Query (AST.Node n' _ _ _)) = Just n == n'
955+
operationNamed n (AST.Mutation (AST.Node n' _ _ _)) = Just n == n'
956+
operationNamed _ _ = False

0 commit comments

Comments
 (0)