git.iko.soy/rowtype-yoga/purescript-graphql
on master help sign in
Create introspection capabilities

The itnrospection is almost done. Only nullable fields and list fields must return their types correctly. Introspection adds a meta field "__schema" to the root query object. GraphQL clients can query this field and receive information about the structure of the schema.

commit577fbd84e72f9410f8bbabfd3da253dae22b4da4 authorhendrikniemann <h.neverman@web.de> dateWed 18 Mar 2020, 20:09 (7 years ago) parent7efff36
5 files changed+533−40
src/GraphQL/Execution.purs+14−2 src/GraphQL/Type.purs+183−38 src/GraphQL/Type/Introspection.purs+190 src/GraphQL/Type/Introspection/Datatypes.purs+117 src/GraphQL/Type/Introspection/Util.purs+29
src/GraphQL/Execution.purs+14−2
@@ -10,7 +10,9 @@ import Data.Maybe (Maybe(..), fromMaybe, maybe)
1010import Effect.Exception (Error, error)
1111import GraphQL.Execution.Result (serializeResult)
1212import GraphQL.Language.AST (DefinitionNode(..), DocumentNode(..), NameNode(..), OperationTypeNode(..))
13−import GraphQL.Type (Schema(..), VariableMap, output)
13+import GraphQL.Type (ObjectType, Schema(..), VariableMap, field, output, (!#>), (:>), introspect)
14+import GraphQL.Type.Introspection (schemaType)
15+import GraphQL.Type.Introspection.Datatypes (SchemaIntrospection(..))
1416
1517
1618simpleError :: forall m. Applicative m => String -> m Json
@@ -42,7 +44,17 @@ execute (DocumentNode { definitions }) (Schema s) variables operation root = do
4244
4345 executionResult <- case definitionNode of
4446 OperationDefinitionNode { operation: Query, selectionSet } ->
45− output s.query (Just selectionSet) variables root
47+ let
48+ schemaIntrospection =
49+ SchemaIntrospection
50+ { queryType: introspect s.query
51+ , mutationType: map introspect s.mutation
52+ }
53+ metaQuery = s.query
54+ :> field "__schema" (schemaType :: ObjectType m SchemaIntrospection)
55+ !#> const schemaIntrospection
56+ in
57+ output metaQuery (Just selectionSet) variables root
4658
4759 OperationDefinitionNode { operation: Mutation, selectionSet } ->
4860 maybe
src/GraphQL/Type.purs+183−38
@@ -5,19 +5,21 @@ import Prelude
55import Control.Lazy (class Lazy)
66import Control.Monad.Error.Class (class MonadError, catchError, throwError)
77import Data.Argonaut.Core as Json
8+import Data.Array as Array
89import Data.Bifunctor (lmap)
910import Data.Either (Either(..), note)
1011import Data.Enum (class Enum, enumFromTo)
1112import Data.List (List, fromFoldable)
1213import Data.Map (Map, empty, insert, lookup)
1314import Data.Maybe (Maybe(..), fromMaybe)
14−import Data.Newtype (class Newtype, unwrap)
15+import Data.Newtype (class Newtype, unwrap, wrap)
1516import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
1617import Data.Traversable (class Traversable, find, traverse)
1718import Data.Tuple (Tuple(..))
1819import Effect.Exception (Error, error, message)
1920import GraphQL.Execution.Result (Result(..))
2021import GraphQL.Language.AST as AST
22+import GraphQL.Type.Introspection.Datatypes as IntrospectionTypes
2123import Record as Record
2224import Type.Data.RowList (RLProxy(..))
2325import Type.Row as Row
@@ -29,13 +31,17 @@ newtype Schema m a = Schema { query :: ObjectType m a, mutation :: Maybe (Object
2931
3032type VariableMap = Map String Json.Json
3133
34+-- | The type class for all GraphQL types
35+class GraphQLType t where
36+ introspect :: forall a. t a -> IntrospectionTypes.TypeIntrospection
37+
3238-- | The input type class is used for all GraphQL types that can be used as input types.
33−class InputType t where
39+class GraphQLType t <= InputType t where
3440 input :: forall a. t a -> Maybe AST.ValueNode -> VariableMap -> Either String a
3541
3642-- | The output type class is used for all GraphQL types that can be used as output types.
3743-- | It has instances for GraphQL types that can resolve a value within a certain monad error.
38−class MonadError Error m <= OutputType m t where
44+class (MonadError Error m, GraphQLType t) <= OutputType m t where
3945 output :: forall a. t a -> Maybe AST.SelectionSetNode -> VariableMap -> m a -> m Result
4046
4147-- | Scalar types are the leaf nodes of a GraphQL schema. Scalar types can be serialised into JSON
@@ -56,6 +62,16 @@ newtype ScalarType a =
5662 , serialize :: a -> Json.Json
5763 }
5864
65+instance graphqlTypeScalarType :: GraphQLType ScalarType where
66+ introspect (ScalarType { name, description }) =
67+ IntrospectionTypes.TypeIntrospection
68+ { kind: IntrospectionTypes.Scalar
69+ , name
70+ , description
71+ , fields: Nothing
72+ , enumValues: Nothing
73+ }
74+
5975instance inputTypeScalarType :: InputType ScalarType where
6076 input (ScalarType config) (Just node) variables = case node of
6177 (AST.VariableNode { name: AST.NameNode { value }}) -> do
@@ -73,22 +89,25 @@ instance showScalarType :: Show (ScalarType a) where
7389 show (ScalarType { name }) = "scalar " <> name
7490
7591newtype ObjectType m a =
76− ObjectType
77− (Unit -> { name :: String
78− , description :: Maybe String
92+ ObjectType (Unit ->
93+ { name :: String
7994 , fields :: Map String (ExecutableField m a)
80− })
95+ , introspection :: IntrospectionTypes.TypeIntrospection
96+ }
97+ )
98+
99+instance graphqlTypeObjectType :: GraphQLType (ObjectType m) where
100+ introspect (ObjectType configFn) = (configFn unit).introspection
81101
82102instance outputTypeObjectType :: (MonadError Error m) => OutputType m (ObjectType m) where
83103 output (ObjectType fno) (Just (AST.SelectionSetNode { selections })) variables val =
84104 ResultObject <$> traverse serializeField selections
85105 where
86− o = fno unit
87−
88106 serializeField :: AST.SelectionNode -> m (Tuple String Result)
89107 serializeField node@(AST.FieldNode fld) =
90108 let (AST.NameNode { value: name }) = fld.name
91109 (AST.NameNode { value: alias }) = fromMaybe fld.name fld.alias
110+ o = fno unit
92111 in case lookup name o.fields of
93112 Just (ExecutableField { execute }) ->
94113 Tuple alias <$>
@@ -129,6 +148,8 @@ newtype Field m a argsd argsp =
129148 Field
130149 { name :: String
131150 , description :: Maybe String
151+ , typeIntrospection :: Unit -> IntrospectionTypes.TypeIntrospection
152+ , argumentIntrospections :: Array IntrospectionTypes.InputValueIntrospection
132153 , args :: Record argsd
133154 , serialize :: AST.SelectionNode -> VariableMap -> Record argsp -> m a -> m Result
134155 }
@@ -138,9 +159,12 @@ newtype Field m a argsd argsp =
138159newtype Argument a =
139160 Argument
140161 { description :: Maybe String
162+ , typeIntrospection :: Unit -> IntrospectionTypes.TypeIntrospection
141163 , resolveValue :: Maybe AST.ValueNode -> VariableMap -> Either String a
142164 }
143165
166+derive instance newtypeArgument :: Newtype (Argument a) _
167+
144168newtype EnumType a =
145169 EnumType
146170 { name :: String
@@ -155,6 +179,21 @@ newtype EnumValue a =
155179 , value :: a
156180 , isValue :: a -> Boolean }
157181
182+instance graphqlTypeEnumType :: GraphQLType EnumType where
183+ introspect (EnumType { name, description, values }) =
184+ IntrospectionTypes.TypeIntrospection
185+ { kind: IntrospectionTypes.Enum
186+ , name
187+ , description
188+ , fields: Nothing
189+ , enumValues: pure $ values <#> \(EnumValue val) ->
190+ IntrospectionTypes.EnumValueIntrospection
191+ { name: val.name
192+ , description: val.description
193+ , deprecationReason: Nothing
194+ }
195+ }
196+
158197instance inputTypeEnumType :: InputType EnumType where
159198 input (EnumType config) (Just node) variables =
160199 let lookupName name =
@@ -192,15 +231,31 @@ instance outputTypeEnumType :: MonadError Error m => OutputType m EnumType where
192231-- | the `:>` operator from this module.
193232objectType :: forall m a. String -> ObjectType m a
194233objectType name =
195− ObjectType (\_ -> { name, description: Nothing, fields: empty })
234+ let
235+ introspection = IntrospectionTypes.TypeIntrospection
236+ { kind: IntrospectionTypes.Object
237+ , name
238+ , description: Nothing
239+ , fields: Just []
240+ , enumValues: Nothing
241+ }
242+ in
243+ ObjectType (\_ -> { name, fields: empty, introspection })
196244
197245-- | Create a new field for an object type. This function is typically used together with the
198246-- | `:>` operator that automatically converts the field into an executable field.
199247field :: forall m t a. OutputType m t => MonadError Error m => String -> t a -> Field m a () ()
200−field name t = Field { name, description: Nothing, args: {}, serialize }
201− where
202− serialize (AST.FieldNode node) variables _ val = output t node.selectionSet variables val
203− serialize _ _ _ _ = pure $ ResultError "Obtained non FieldNode for field serialisation."
248+field name t =
249+ Field
250+ { name
251+ , description: Nothing
252+ , args: {}
253+ , serialize
254+ , argumentIntrospections: []
255+ , typeIntrospection: \_ -> introspect t }
256+ where
257+ serialize (AST.FieldNode node) variables _ val = output t node.selectionSet variables val
258+ serialize _ _ _ _ = pure $ ResultError "Obtained non FieldNode for field serialisation."
204259
205260-- | Create a new field for an object type that is a list. You can return any `Foldable` in the
206261-- | resolver.
@@ -211,14 +266,21 @@ listField :: forall m f t a.
211266 String ->
212267 t a ->
213268 Field m (f a) () ()
214−listField name t = Field { name, description: Nothing, args: {}, serialize }
215− where
216− serialize (AST.FieldNode node) variables _ mVals = do
217− vals <- mVals
218− ResultList <$> fromFoldable <$> traverse (output t node.selectionSet variables) (pure <$> vals)
269+listField name t =
270+ Field
271+ { name
272+ , description: Nothing
273+ , args: {}
274+ , serialize
275+ , argumentIntrospections: []
276+ , typeIntrospection: \_ -> introspect t }
277+ where
278+ serialize (AST.FieldNode node) variables _ mVals = do
279+ vals <- mVals
280+ ResultList <$> fromFoldable <$> traverse (output t node.selectionSet variables) (pure <$> vals)
219281
220− serialize _ _ _ _ =
221− pure $ ResultError "Obtained non FieldNode for field serialisation."
282+ serialize _ _ _ _ =
283+ pure $ ResultError "Obtained non FieldNode for field serialisation."
222284
223285-- | Create a new field for an object type that is optional (i.e. it can be null). The resolver
224286-- | must now return a `Maybe`.
@@ -228,26 +290,71 @@ nullableField :: forall m t a.
228290 String ->
229291 t a ->
230292 Field m (Maybe a) () ()
231−nullableField name t = Field { name, description: Nothing, args: {}, serialize }
232− where
233− serialize (AST.FieldNode node) variables _ mValue = do
234− value <- mValue
235− case value of
236− Nothing ->
237− pure $ ResultNullable Nothing
293+nullableField name t =
294+ Field
295+ { name
296+ , description: Nothing
297+ , args: {}
298+ , serialize
299+ , argumentIntrospections: []
300+ , typeIntrospection: \_ -> introspect t }
301+ where
302+ serialize (AST.FieldNode node) variables _ mValue = do
303+ value <- mValue
304+ case value of
305+ Nothing ->
306+ pure $ ResultNullable Nothing
307+
308+ Just val ->
309+ ResultNullable <$> Just <$> output t node.selectionSet variables (pure val)
238310
239− Just val ->
240− ResultNullable <$> Just <$> output t node.selectionSet variables (pure val)
311+ serialize _ _ _ _ =
312+ pure $ ResultError "Obtained non FieldNode for field serialisation."
241313
242− serialize _ _ _ _ =
243− pure $ ResultError "Obtained non FieldNode for field serialisation."
314+
315+-- | Create a new field for an object type that is optional (i.e. it can be null) and returns a
316+-- | list. The resolver must now return a `Maybe`.
317+nullableListField :: forall m f t a.
318+ MonadError Error m =>
319+ Traversable f =>
320+ OutputType m t =>
321+ String ->
322+ t a ->
323+ Field m (Maybe (f a)) () ()
324+nullableListField name t =
325+ Field
326+ { name
327+ , description: Nothing
328+ , args: {}
329+ , serialize
330+ , argumentIntrospections: []
331+ , typeIntrospection: \_ -> introspect t }
332+ where
333+ serialize (AST.FieldNode node) variables _ mVals = do
334+ values <- mVals
335+ case values of
336+ Nothing ->
337+ pure $ ResultNullable Nothing
338+
339+ Just vals ->
340+ ResultNullable <$>
341+ Just <$>
342+ ResultList <$>
343+ fromFoldable <$>
344+ traverse (output t node.selectionSet variables) (pure <$> vals)
345+ serialize _ _ _ _ =
346+ pure $ ResultError "Obtained non FieldNode for field serialisation."
244347
245348-- | Create a tuple with a given name and a plain argument of the given type.
246349-- | The name argument tuple can then be used to be added to a field using the `?>` operator.
247350arg :: forall t a n. InputType t => IsSymbol n => t a -> SProxy n -> Tuple (SProxy n) (Argument a)
248351arg t name =
249352 Tuple (SProxy :: _ n) $
250− Argument { description: Nothing, resolveValue: input t }
353+ Argument
354+ { description: Nothing
355+ , resolveValue: input t
356+ , typeIntrospection: \_ -> introspect t
357+ }
251358
252359-- * Combinator operators
253360
@@ -268,7 +375,13 @@ class Describe a where
268375infixl 8 describe as .>
269376
270377instance describeObjectType :: Describe (ObjectType m a) where
271− describe (ObjectType config) s = ObjectType $ \_ -> ((config unit) { description = Just s })
378+ describe (ObjectType configFn) s =
379+ ObjectType $ \_ ->
380+ let
381+ config = configFn unit
382+ newIntrospection = wrap $ (unwrap config.introspection) { description = Just s }
383+ in
384+ config { introspection = newIntrospection }
272385
273386instance describeField :: Describe (Field m a argsd argsp) where
274387 describe (Field config) s = Field (config { description = Just s })
@@ -295,10 +408,31 @@ withField :: forall m a argsd argsp.
295408 ObjectType m a ->
296409 Field m a argsd argsp ->
297410 ObjectType m a
298−withField (ObjectType objectConfigFn) fld@(Field { name }) =
299− ObjectType $ \_ -> objectConfig { fields = insert name (makeExecutable fld) objectConfig.fields }
411+withField (ObjectType objectConfigFn) fld@(
412+ Field { name, description, typeIntrospection, argumentIntrospections }
413+) =
414+ ObjectType updated
300415 where
301− objectConfig = objectConfigFn unit
416+ updated _ =
417+ let
418+ objectConfig = objectConfigFn unit
419+ updatedFields = insert name (makeExecutable fld) objectConfig.fields
420+
421+ fieldIntrospection = IntrospectionTypes.FieldIntrospection
422+ { name
423+ , description
424+ , args: argumentIntrospections
425+ , type: typeIntrospection
426+ , deprecationReason: Nothing
427+ }
428+
429+ introspection = unwrap objectConfig.introspection
430+
431+ updatedIntrospection =
432+ IntrospectionTypes.TypeIntrospection $
433+ introspection { fields = map (flip Array.snoc fieldIntrospection) (introspection.fields) }
434+ in
435+ objectConfig { fields = updatedFields, introspection = updatedIntrospection }
302436
303437 makeExecutable :: Field m a argsd argsp -> ExecutableField m a
304438 makeExecutable (Field { args, serialize: serialize' }) = ExecutableField { execute: execute' }
@@ -405,7 +539,7 @@ withArgument :: forall m a arg n argsdold argsdnew argspold argspnew.
405539 Tuple (SProxy n) (Argument arg) ->
406540 Field m a argsdnew argspnew
407541withArgument (Field fieldConfig) (Tuple proxy argument) =
408− Field $ fieldConfig { args = args', serialize = serialize' }
542+ Field $ fieldConfig { args = args', serialize = serialize', argumentIntrospections = intros' }
409543 where
410544 args' :: Record argsdnew
411545 args' = Record.insert proxy argument fieldConfig.args
@@ -419,6 +553,17 @@ withArgument (Field fieldConfig) (Tuple proxy argument) =
419553 serialize' node variables argsnew val =
420554 fieldConfig.serialize node variables (Record.delete proxy argsnew) val
421555
556+ intros' :: Array IntrospectionTypes.InputValueIntrospection
557+ intros' = Array.snoc fieldConfig.argumentIntrospections
558+ (
559+ IntrospectionTypes.InputValueIntrospection
560+ { name: reflectSymbol proxy
561+ , description: _.description $ unwrap argument
562+ , type: _.typeIntrospection $ unwrap argument
563+ , defaultValue: Nothing
564+ }
565+ )
566+
422567-- | Adds an argument to a field that can be used in the resolver.
423568-- |
424569-- | *Example:*
src/GraphQL/Type/Introspection.pursnew file+190
@@ -0,0 +1,190 @@
1+module GraphQL.Type.Introspection where
2+
3+import Prelude
4+
5+import Control.Lazy (defer)
6+import Control.Monad.Error.Class (class MonadError)
7+import Data.Array (fromFoldable)
8+import Data.Function (applyFlipped)
9+import Data.Maybe (Maybe(..), isJust)
10+import Data.Newtype (unwrap)
11+import Effect.Exception (Error)
12+import GraphQL.Type ((!#>), (.>), (:>))
13+import GraphQL.Type as GQL
14+import GraphQL.Type.Introspection.Datatypes (EnumValueIntrospection, FieldIntrospection, InputValueIntrospection, SchemaIntrospection, TypeIntrospection, TypeKind)
15+import GraphQL.Type.Introspection.Util (collectTypes)
16+import GraphQL.Type.Scalar as Scalar
17+
18+
19+schemaType :: forall m. (MonadError Error m) => GQL.ObjectType m SchemaIntrospection
20+schemaType = schemaType'
21+ where
22+ schemaType' :: GQL.ObjectType m SchemaIntrospection
23+ schemaType' = GQL.objectType "__Schema"
24+ .> """
25+ A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available
26+ types and directives on the server, as well as the entry points for query, mutation, and
27+ subscription operations.
28+ """
29+ :> GQL.listField "types" (typeType :: GQL.ObjectType m TypeIntrospection)
30+ .> "A list of all types supported by this server."
31+ !#> collectTypes >>> fromFoldable
32+ :> GQL.field "queryType" (typeType :: GQL.ObjectType m TypeIntrospection)
33+ .> "The type that query operations will be rooted at."
34+ !#> unwrap >>> _.queryType
35+ :> GQL.nullableField "mutationType" (typeType :: GQL.ObjectType m TypeIntrospection)
36+ .> """
37+ If this server supports mutation, the type that mutation operations will be rooted at.
38+ """
39+ !#> unwrap >>> _.mutationType
40+
41+ typeType :: GQL.ObjectType m TypeIntrospection
42+ typeType = GQL.objectType "__Type"
43+ .> """
44+ The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in
45+ GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain
46+ fields describe information about that type. Scalar types provide no information beyond a
47+ name and description, while Enum types provide their values. Object and Interface types
48+ provide the fields they describe. Abstract types, Union and Interface, provide the Object
49+ types possible at runtime. List and NonNull types compose other types.
50+ """
51+ :> GQL.field "kind" typeKindType
52+ !#> unwrap >>> _.kind
53+ :> GQL.nullableField "name" Scalar.string
54+ !#> unwrap >>> _.name >>> pure
55+ :> GQL.nullableField "description" Scalar.string
56+ !#> unwrap >>> _.description
57+ :> GQL.nullableListField "fields" (defer \_ -> fieldType) -- TODO: Make nullable
58+ !#> unwrap >>> _.fields
59+ -- For spec compliance; this implementation does not support interfaces or unions yet
60+ :> GQL.nullableListField "interfaces" (defer \_ -> typeType)
61+ !#> const (Nothing :: Maybe (Array TypeIntrospection))
62+ :> GQL.nullableListField "possibleTypes" (defer \_ -> typeType)
63+ !#> const (Nothing :: Maybe (Array TypeIntrospection))
64+ :> GQL.nullableListField "enumValue" enumValueType
65+ !#> unwrap >>> _.enumValues
66+
67+ typeKindType :: GQL.EnumType TypeKind
68+ typeKindType = GQL.enumType "__TypeKind"
69+ .> "An enum describing what kind of type a given `__Type` is."
70+
71+ fieldType :: GQL.ObjectType m FieldIntrospection
72+ fieldType = GQL.objectType "__Field"
73+ .> """
74+ Object and Interface types are described by a list of Fields, each of which has a name,
75+ potentially a list of arguments, and a return type.
76+ """
77+ :> GQL.field "name" Scalar.string
78+ !#> unwrap >>> _.name
79+ :> GQL.nullableField "description" Scalar.string
80+ !#> unwrap >>> _.description
81+ :> GQL.field "type" (defer \_ -> typeType)
82+ !#> unwrap >>> _.type >>> applyFlipped unit
83+ :> GQL.listField "args" (defer \_ -> inputValueType)
84+ !#> unwrap >>> _.args
85+ :> GQL.field "isDeprecated" Scalar.boolean
86+ !#> unwrap >>> _.deprecationReason >>> isJust
87+ :> GQL.nullableField "deprecationReason" Scalar.string
88+ !#> unwrap >>> _.deprecationReason
89+
90+ enumValueType :: GQL.ObjectType m EnumValueIntrospection
91+ enumValueType = GQL.objectType "__EnumValue"
92+ .> """
93+ One possible value for a given Enum. Enum values are unique values, not a placeholder for a
94+ string or numeric value. However an Enum value is returned in a JSON response as a string.
95+ """
96+ :> GQL.field "name" Scalar.string
97+ !#> unwrap >>> _.name
98+ :> GQL.nullableField "description" Scalar.string
99+ !#> unwrap >>> _.description
100+ :> GQL.field "isDeprecated" Scalar.boolean
101+ !#> unwrap >>> _.deprecationReason >>> isJust
102+ :> GQL.nullableField "deprecationReason" Scalar.string
103+ !#> unwrap >>> _.deprecationReason
104+
105+ inputValueType :: GQL.ObjectType m InputValueIntrospection
106+ inputValueType = GQL.objectType "__InputValue"
107+ .> """
108+ Arguments provided to Fields or Directives and the input fields of an InputObject are
109+ represented as Input Values which describe their type and optionally a default value.
110+ """
111+ :> GQL.field "name" Scalar.string
112+ !#> unwrap >>> _.name
113+ :> GQL.nullableField "description" Scalar.string
114+ !#> unwrap >>> _.description
115+ :> GQL.field "type" (defer \_ -> typeType)
116+ !#> unwrap >>> _.type >>> applyFlipped unit
117+ :> GQL.nullableField "defaultValue" Scalar.string
118+ !#> unwrap >>> _.defaultValue
119+
120+-- type __Type {
121+-- kind: __TypeKind!
122+-- name: String
123+-- description: String
124+
125+-- # OBJECT and INTERFACE only
126+-- fields(includeDeprecated: Boolean = false): [__Field!]
127+
128+-- # OBJECT only
129+-- interfaces: [__Type!]
130+
131+-- # INTERFACE and UNION only
132+-- possibleTypes: [__Type!]
133+
134+-- # ENUM only
135+-- enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
136+
137+-- # INPUT_OBJECT only
138+-- inputFields: [__InputValue!]
139+
140+-- # NON_NULL and LIST only
141+-- ofType: __Type
142+-- }
143+
144+-- type __Field {
145+-- name: String!
146+-- description: String
147+-- args: [__InputValue!]!
148+-- type: __Type!
149+-- isDeprecated: Boolean!
150+-- deprecationReason: String
151+-- }
152+
153+-- enum __TypeKind {
154+-- SCALAR
155+-- OBJECT
156+-- INTERFACE
157+-- UNION
158+-- ENUM
159+-- INPUT_OBJECT
160+-- LIST
161+-- NON_NULL
162+-- }
163+
164+-- type __Directive {
165+-- name: String!
166+-- description: String
167+-- locations: [__DirectiveLocation!]!
168+-- args: [__InputValue!]!
169+-- }
170+
171+-- enum __DirectiveLocation {
172+-- QUERY
173+-- MUTATION
174+-- SUBSCRIPTION
175+-- FIELD
176+-- FRAGMENT_DEFINITION
177+-- FRAGMENT_SPREAD
178+-- INLINE_FRAGMENT
179+-- SCHEMA
180+-- SCALAR
181+-- OBJECT
182+-- FIELD_DEFINITION
183+-- ARGUMENT_DEFINITION
184+-- INTERFACE
185+-- UNION
186+-- ENUM
187+-- ENUM_VALUE
188+-- INPUT_OBJECT
189+-- INPUT_FIELD_DEFINITION
190+-- }
src/GraphQL/Type/Introspection/Datatypes.pursnew file+117
@@ -0,0 +1,117 @@
1+module GraphQL.Type.Introspection.Datatypes where
2+
3+import Prelude
4+
5+import Data.Enum (class Enum)
6+import Data.Generic.Rep (class Generic)
7+import Data.Generic.Rep.Bounded (genericBottom, genericTop)
8+import Data.Generic.Rep.Enum (genericPred, genericSucc)
9+import Data.Generic.Rep.Eq (genericEq)
10+import Data.Generic.Rep.Ord (genericCompare)
11+import Data.Maybe (Maybe)
12+import Data.Newtype (class Newtype)
13+
14+newtype SchemaIntrospection = SchemaIntrospection
15+ { queryType :: TypeIntrospection
16+ , mutationType :: Maybe TypeIntrospection
17+ }
18+
19+
20+derive instance newtypeSchemaIntrospection :: Newtype SchemaIntrospection _
21+
22+
23+newtype TypeIntrospection = TypeIntrospection
24+ { kind :: TypeKind
25+ , name :: String
26+ , description :: Maybe String
27+ , fields :: Maybe (Array FieldIntrospection)
28+ , enumValues :: Maybe (Array EnumValueIntrospection)
29+ }
30+
31+
32+derive instance newtypeTypeIntrospection :: Newtype TypeIntrospection _
33+
34+-- We can abuse the fact that our schema can only have unique type names to create an efficient Eq
35+-- instance
36+instance eqTypeIntrospection :: Eq TypeIntrospection where
37+ eq (TypeIntrospection t1) (TypeIntrospection t2) = t1.name == t2.name
38+
39+-- We can abuse the fact that our schema can only have unique type names to create an efficient Ord
40+-- instance
41+instance ordTypeIntrospection :: Ord TypeIntrospection where
42+ compare (TypeIntrospection t1) (TypeIntrospection t2) = compare t1.name t2.name
43+
44+newtype EnumValueIntrospection = EnumValueIntrospection
45+ { name :: String
46+ , description :: Maybe String
47+ , deprecationReason :: Maybe String
48+ }
49+
50+
51+derive instance newtypeEnumValueIntrospection :: Newtype EnumValueIntrospection _
52+
53+
54+newtype FieldIntrospection = FieldIntrospection
55+ { name :: String
56+ , description :: Maybe String
57+ , args :: Array InputValueIntrospection
58+ , type :: Unit -> TypeIntrospection
59+ , deprecationReason :: Maybe String
60+ }
61+
62+
63+derive instance newtypeFieldIntrospection :: Newtype FieldIntrospection _
64+
65+
66+newtype InputValueIntrospection = InputValueIntrospection
67+ { name :: String
68+ , description :: Maybe String
69+ , type :: Unit -> TypeIntrospection
70+ , defaultValue :: Maybe String
71+ }
72+
73+
74+derive instance newtypeInputValueIntrospection :: Newtype InputValueIntrospection _
75+
76+
77+data TypeKind
78+ = Scalar
79+ | Object
80+ | Interface
81+ | Union
82+ | Enum
83+ | InputObject
84+ | List
85+ | NonNull
86+
87+
88+derive instance genericTypeKind :: Generic TypeKind _
89+
90+
91+instance eqTypeKind :: Eq TypeKind where
92+ eq = genericEq
93+
94+
95+instance ordTypeKind :: Ord TypeKind where
96+ compare = genericCompare
97+
98+
99+instance boundedTypeKind :: Bounded TypeKind where
100+ top = genericTop
101+ bottom = genericBottom
102+
103+
104+instance enumTypeKind :: Enum TypeKind where
105+ succ = genericSucc
106+ pred = genericPred
107+
108+
109+instance showTypeKind :: Show TypeKind where
110+ show Scalar = "SCALAR"
111+ show Object = "OBJECT"
112+ show Interface = "INTERFACE"
113+ show Union = "UNION"
114+ show Enum = "ENUM"
115+ show InputObject = "INPUT_OBJECT"
116+ show List = "LIST"
117+ show NonNull = "NON_NULL"
src/GraphQL/Type/Introspection/Util.pursnew file+29
@@ -0,0 +1,29 @@
1+module GraphQL.Type.Introspection.Util where
2+
3+import Prelude
4+
5+import Data.Foldable (foldl)
6+import Data.Maybe (Maybe(..), maybe)
7+import Data.Set (Set, empty, member, singleton)
8+import GraphQL.Type.Introspection.Datatypes (FieldIntrospection(..), SchemaIntrospection(..), TypeIntrospection(..))
9+
10+
11+collectTypes :: SchemaIntrospection -> Set TypeIntrospection
12+collectTypes (SchemaIntrospection { queryType, mutationType }) =
13+ collectTypes' queryType <> maybe empty collectTypes' mutationType
14+ where
15+ collectTypes' :: TypeIntrospection -> Set TypeIntrospection
16+ -- If the type has no fields we can simply return a set with one element
17+ collectTypes' introspection@(TypeIntrospection { fields: Nothing }) = singleton introspection
18+
19+ -- If the type has fields we need to recursively go over these fields and add the type of the
20+ -- fields as well
21+ collectTypes' introspection@(TypeIntrospection { fields: Just fields }) =
22+ foldl collectTypesFromField (singleton introspection) fields
23+
24+ collectTypesFromField :: Set TypeIntrospection -> FieldIntrospection -> Set TypeIntrospection
25+ collectTypesFromField set (FieldIntrospection config) =
26+ let
27+ resultType = config.type unit
28+ in
29+ if member resultType set then set else set <> collectTypes' resultType