-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
Introspect.hs
730 lines (670 loc) · 26 KB
/
Introspect.hs
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
{-# LANGUAGE ApplicativeDo #-}
module Hasura.GraphQL.Schema.Introspect
( buildIntrospectionSchema,
schema,
typeIntrospection,
)
where
import Data.Aeson.Ordered qualified as J
import Data.HashMap.Strict qualified as HashMap
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.List.NonEmpty qualified as NE
import Data.Text qualified as T
import Data.Vector qualified as V
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Parser.Directives
import Hasura.GraphQL.Parser.Name qualified as GName
import Hasura.GraphQL.Schema.Parser as P
import Hasura.Prelude
import Language.GraphQL.Draft.Printer qualified as GP
import Language.GraphQL.Draft.Syntax qualified as G
import Text.Builder qualified as T
{-
Note [Basics of introspection schema generation]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We generate the introspection schema from the existing schema for queries,
mutations and subscriptions. In other words, we generate one @Parser@ from some
other @Parser@s. In this way, we avoid having to remember what types we have to
expose through introspection explicitly, as we did in a previous version of
graphql-engine.
However the schema information is obtained, the @Schema@ type stores it. From a
@Schema@ object we then produce one @FieldParser@ that reads a `__schema` field,
and one that reads a `__type` field. The idea is that these parsers simply
output a JSON value directly, and so indeed the type of @schema@, for instance,
is @FieldParser n J.Value@.
The idea of "just output the JSON object directly" breaks down when we want to
output a list of things, however, such as in the `types` field of `__schema`.
In the case of `types`, the JSON object to be generated is influenced by the
underlying selection set, so that, for instance,
```
query {
__schema {
types {
name
}
}
}
```
means that we only output the _name_ of every type in our schema. One naive
approach one might consider here would be to have a parser
```
typeField :: P.Type k -> Parser n J.Value
```
that takes a type, and is able to produce a JSON value for it, and then to apply
this parser to all the types in our schema.
However, we only have *one* selection set to parse: so which of the parsers we
obtained should we use to parse it? And what should we do in the theoretical
case that we have a schema without any types? (The latter is actually not
possible since we always have `query_root`, but it illustrates the problem that
there is no canonical choice of type to use to parse the selection set.)
Additionally, this would allow us to get the JSON output for *one* type, rather
than for our list of types. After all, @Parser n@ is *not* a @Monad@ (it's not
even an @Applicative@), so we don't have a map @(a -> Parser n b) -> [a] -> m
[b]@.
In order to resolve this conundrum, let's consider what the ordinary Postgres
schema generates for a query such as follows.
```
query {
author {
articles {
title
}
}
}
```
Now the @Parser@ for an article's title does not directly give the desired
output data: indeed, there would be many different titles, rather than any
single one we can return. Instead, it returns a value that can, after parsing,
be used to build an output, along the lines of:
```
articleTitle :: FieldParser n SQLArticleTitle
```
(This is a heavily simplified representation of reality.)
These values can be thought of as an intermediate representation that can then
be used to generate and run SQL that gives the desired JSON output at a later
stage. In other words, in the above example, @SQLArticleTitle@ can be thought
of as a function @Article -> Title@ that, given an article, gives back its
title.
Such an approach could help us as well, as, from instructions on how to generate
a JSON return for a given `__Type`, surely we can later simply apply this
construction to all types desired.
However, we don't _need_ to build an intermediate AST to do this: we can simply
output the conversion functions directly. So the type of @typeField@ is closer
to:
```
typeField :: Parser n (P.Type k -> J.Value)
```
This says that we are always able to parse a selection set for a `__Type`, and
once we do, we obtain a map, which we refer to as `printer` in this module,
which can output a JSON object for a given GraphQL type from our schema.
To use `typeField` as part of another selection set, we build up a corresponding
`FieldParser`, thus obtaining a printer, then apply this printer to all desired
types, and output the final JSON object as a J.Array of the printed results,
like so (again, heavily simplified):
```
types :: FieldParser n J.Value
types = do
printer <- P.subselection_ GName._types Nothing typeField
return $ J.Array $ map printer $ allSchemaTypes
```
Upon reading this you may be bewildered how we are able to use do notation for
@FieldParser@, which does not have a @Monad@ instance, or even an @Applicative@
instance. It just so happens that, as long as we write our do blocks carefully,
so that we only use the functoriality of @FieldParser@, the simplification rules
of GHC kick in just in time to avoid any application of @(>>=)@ or @return@.
Arguably the above notation is prettier than equivalent code that explicitly
reduces this to applications of @fmap@. If you, dear reader, feel like the do
notation adds more confusion than value, you should feel free to change this, as
there is no deeper meaning to the application of do notation than ease of
reading.
-}
{- Note [What introspection exposes]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NB: By "introspection query", we mean a query making use of the __type or
__schema top-level fields.
It would be very convenient if we could simply build up our desired GraphQL
schema, without regard for introspection. Ideally, we would then extract the
data required for introspection from this complete GraphQL schema. There are,
however, some complications:
1. Of course, we _do_ need to include the introspection fields themselves into
the query_root, so that we can deal with introspection queries
appropriately. So we can't avoid thinking about introspection entirely while
constructing the GraphQL schema.
2. The GraphQL specification says that although we must always expose __type and
__schema fields as part of the query_root, they must not be visible fields of
the query_root object type. See
http://spec.graphql.org/June2018/#sec-Schema-Introspection
At this point, one might naively attempt to generate two GraphQL schemas:
- One without the __type and __schema fields, from which we generate the data
required for responding to introspection queries.
- One with the __type and __schema fields, which is used to actually respond to
queries.
However, this is also not GraphQL-compliant!
The problem here is that while __type and __schema are not visible fields of the
query root object, their *types*, __Type and __Schema respectively, *must be*
exposed through __type introspection, even though those types never appear as
(transitive) members of the query/mutation/subscription root fields.
So in order to gather the data required for introspection, we follow the
following recipe:
A. Collect type information from the introspection-free GraphQL schema
B. Collect type information from the introspection-only GraphQL schema
C. Stitch together the results of (A) and (B). In particular, the query_root
from (A) is used, and all types from (A) and (B) are used, except for the
query_root obtained in (B). -}
-- | Builds a @Schema@ from GraphQL types for the query_root, mutation_root and
-- subscription_root.
--
-- See Note [What introspection exposes]
buildIntrospectionSchema ::
P.Type 'Output ->
Maybe (P.Type 'Output) ->
Maybe (P.Type 'Output) ->
Either P.ConflictingDefinitions P.Schema
buildIntrospectionSchema queryRoot' mutationRoot' subscriptionRoot' = do
let -- The only directives that we currently expose over introspection are our
-- statically defined ones. So, for instance, we don't correctly expose
-- directives from remote schemas.
directives :: [DirectiveInfo] = directivesInfo @P.Parse
-- Collect type information of all fields
--
-- TODO: it may be worth looking at whether we can stop collecting
-- introspection types monadically. They are independent of the user schema;
-- the types here are always the same and specified by the GraphQL spec
allTypes <-
P.collectTypeDefinitions
[ P.TypeDefinitionsWrapper queryRoot',
P.TypeDefinitionsWrapper mutationRoot',
P.TypeDefinitionsWrapper subscriptionRoot',
P.TypeDefinitionsWrapper $ P.diArguments =<< directives,
-- The __schema introspection field
P.TypeDefinitionsWrapper $ fDefinition (schema @Parse),
-- The __type introspection field
P.TypeDefinitionsWrapper $ fDefinition (typeIntrospection @Parse)
]
pure
$ P.Schema
{ sDescription = Nothing,
sTypes = allTypes,
sQueryType = queryRoot',
sMutationType = mutationRoot',
sSubscriptionType = subscriptionRoot',
sDirectives = directives
}
-- | Generate a __type introspection parser
typeIntrospection ::
forall n.
(MonadParse n) =>
FieldParser n (Schema -> J.Value)
{-# INLINE typeIntrospection #-}
typeIntrospection = do
let nameArg :: P.InputFieldsParser n Text
nameArg = P.field GName._name Nothing P.string
~(nameText, printer) <- P.subselection GName.___type Nothing nameArg typeField
-- We pass around the GraphQL schema information under the name `partialSchema`,
-- because the GraphQL spec forces us to expose a hybrid between the
-- specification of valid queries (including introspection) and an
-- introspection-free GraphQL schema. See Note [What introspection exposes].
pure $ \partialSchema -> fromMaybe J.Null $ do
name <- G.mkName nameText
P.SomeDefinitionTypeInfo def <- HashMap.lookup name $ sTypes partialSchema
Just $ printer $ SomeType $ P.TNamed P.Nullable def
-- | Generate a __schema introspection parser.
schema ::
forall n.
(MonadParse n) =>
FieldParser n (Schema -> J.Value)
{-# INLINE schema #-}
schema = P.subselection_ GName.___schema Nothing schemaSet
{-
type __Type {
kind: __TypeKind!
name: String
description: String
# should be non-null for OBJECT and INTERFACE only, must be null for the others
fields(includeDeprecated: Boolean = false): [__Field!]
# should be non-null for OBJECT and INTERFACE only, must be null for the others
interfaces: [__Type!]
# should be non-null for INTERFACE and UNION only, always null for the others
possibleTypes: [__Type!]
# should be non-null for ENUM only, must be null for the others
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
# should be non-null for INPUT_OBJECT only, must be null for the others
inputFields: [__InputValue!]
# should be non-null for NON_NULL and LIST only, must be null for the others
ofType: __Type
}
-}
data SomeType = forall k. SomeType (P.Type k)
typeField ::
forall n.
(MonadParse n) =>
Parser 'Output n (SomeType -> J.Value)
typeField =
let includeDeprecated :: P.InputFieldsParser n Bool
includeDeprecated =
P.fieldWithDefault GName._includeDeprecated Nothing (G.VBoolean False) (P.nullable P.boolean)
<&> fromMaybe False
kind :: FieldParser n (SomeType -> J.Value)
kind =
P.selection_ GName._kind Nothing typeKind
$> \case
SomeType tp ->
case tp of
P.TList P.NonNullable _ ->
J.String "NON_NULL"
P.TNamed P.NonNullable _ ->
J.String "NON_NULL"
P.TList P.Nullable _ ->
J.String "LIST"
P.TNamed P.Nullable (P.Definition _ _ _ _ P.TIScalar) ->
J.String "SCALAR"
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIEnum _)) ->
J.String "ENUM"
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIInputObject _)) ->
J.String "INPUT_OBJECT"
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIObject _)) ->
J.String "OBJECT"
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIInterface _)) ->
J.String "INTERFACE"
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIUnion _)) ->
J.String "UNION"
name :: FieldParser n (SomeType -> J.Value)
name =
P.selection_ GName._name Nothing P.string
$> \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition name' _ _ _ _) ->
nameAsJSON name'
_ -> J.Null
description :: FieldParser n (SomeType -> J.Value)
description =
P.selection_ GName._description Nothing P.string
$> \case
SomeType (P.TNamed _ (P.Definition _ (Just desc) _ _ _)) ->
J.String (G.unDescription desc)
_ -> J.Null
fields :: FieldParser n (SomeType -> J.Value)
fields = do
-- TODO handle the value of includeDeprecated
~(_includeDeprecated, printer) <- P.subselection GName._fields Nothing includeDeprecated fieldField
return
$ \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIObject (P.ObjectInfo fields' _interfaces'))) ->
J.Array $ V.fromList $ printer <$> fields'
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIInterface (P.InterfaceInfo fields' _objects'))) ->
J.Array $ V.fromList $ printer <$> fields'
_ -> J.Null
interfaces :: FieldParser n (SomeType -> J.Value)
interfaces = do
printer <- P.subselection_ GName._interfaces Nothing typeField
return
$ \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIObject (P.ObjectInfo _fields' interfaces'))) ->
J.Array $ V.fromList $ printer . SomeType . P.TNamed P.Nullable . fmap P.TIInterface <$> interfaces'
_ -> J.Null
possibleTypes :: FieldParser n (SomeType -> J.Value)
possibleTypes = do
printer <- P.subselection_ GName._possibleTypes Nothing typeField
return
$ \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIInterface (P.InterfaceInfo _fields' objects'))) ->
J.Array $ V.fromList $ printer . SomeType . P.TNamed P.Nullable . fmap P.TIObject <$> objects'
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIUnion (P.UnionInfo objects'))) ->
J.Array $ V.fromList $ printer . SomeType . P.TNamed P.Nullable . fmap P.TIObject <$> objects'
_ -> J.Null
enumValues :: FieldParser n (SomeType -> J.Value)
enumValues = do
-- TODO handle the value of includeDeprecated
~(_includeDeprecated, printer) <- P.subselection GName._enumValues Nothing includeDeprecated enumValue
return
$ \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIEnum vals)) ->
J.Array $ V.fromList $ fmap printer $ toList vals
_ -> J.Null
inputFields :: FieldParser n (SomeType -> J.Value)
inputFields = do
printer <- P.subselection_ GName._inputFields Nothing inputValue
return
$ \case
SomeType tp ->
case tp of
P.TNamed P.Nullable (P.Definition _ _ _ _ (P.TIInputObject (P.InputObjectInfo fieldDefs))) ->
J.Array $ V.fromList $ map printer fieldDefs
_ -> J.Null
-- ofType peels modalities off of types
ofType :: FieldParser n (SomeType -> J.Value)
ofType = do
printer <- P.subselection_ GName._ofType Nothing typeField
return $ \case
-- kind = "NON_NULL": !a -> a
SomeType (P.TNamed P.NonNullable x) ->
printer $ SomeType $ P.TNamed P.Nullable x
-- kind = "NON_NULL": ![a] -> [a], and ![!a] -> [!a]
SomeType (P.TList P.NonNullable x) ->
printer $ SomeType $ P.TList P.Nullable x
-- kind = "LIST": [a] -> a
SomeType (P.TList P.Nullable x) ->
printer $ SomeType x
_ -> J.Null
in applyPrinter
<$> P.selectionSet
GName.___Type
Nothing
[ kind,
name,
description,
fields,
interfaces,
possibleTypes,
enumValues,
inputFields,
ofType
]
{-
type __InputValue {
name: String!
description: String
type: __Type!
defaultValue: String
}
-}
inputValue ::
forall n.
(MonadParse n) =>
Parser 'Output n (P.Definition P.InputFieldInfo -> J.Value)
inputValue =
let name :: FieldParser n (P.Definition P.InputFieldInfo -> J.Value)
name =
P.selection_ GName._name Nothing P.string
$> nameAsJSON
. P.dName
description :: FieldParser n (P.Definition P.InputFieldInfo -> J.Value)
description =
P.selection_ GName._description Nothing P.string
$> maybe J.Null (J.String . G.unDescription)
. P.dDescription
typeF :: FieldParser n (P.Definition P.InputFieldInfo -> J.Value)
typeF = do
printer <- P.subselection_ GName._type Nothing typeField
return $ \defInfo -> case P.dInfo defInfo of
P.InputFieldInfo tp _ -> printer $ SomeType tp
defaultValue :: FieldParser n (P.Definition P.InputFieldInfo -> J.Value)
defaultValue =
P.selection_ GName._defaultValue Nothing P.string
$> \defInfo -> case P.dInfo defInfo of
P.InputFieldInfo _ (Just val) -> J.String $ T.run $ GP.value val
_ -> J.Null
in applyPrinter
<$> P.selectionSet
GName.___InputValue
Nothing
[ name,
description,
typeF,
defaultValue
]
{-
type __EnumValue {
name: String!
description: String
isDeprecated: Boolean!
deprecationReason: String
}
-}
enumValue ::
forall n.
(MonadParse n) =>
Parser 'Output n (P.Definition P.EnumValueInfo -> J.Value)
enumValue =
let name :: FieldParser n (P.Definition P.EnumValueInfo -> J.Value)
name =
P.selection_ GName._name Nothing P.string
$> nameAsJSON
. P.dName
description :: FieldParser n (P.Definition P.EnumValueInfo -> J.Value)
description =
P.selection_ GName._description Nothing P.string
$> maybe J.Null (J.String . G.unDescription)
. P.dDescription
-- TODO We don't seem to support enum value deprecation
isDeprecated :: FieldParser n (P.Definition P.EnumValueInfo -> J.Value)
isDeprecated =
P.selection_ GName._isDeprecated Nothing P.string
$> const (J.Bool False)
deprecationReason :: FieldParser n (P.Definition P.EnumValueInfo -> J.Value)
deprecationReason =
P.selection_ GName._deprecationReason Nothing P.string
$> const J.Null
in applyPrinter
<$> P.selectionSet
GName.___EnumValue
Nothing
[ name,
description,
isDeprecated,
deprecationReason
]
{-
enum __TypeKind {
ENUM
INPUT_OBJECT
INTERFACE
LIST
NON_NULL
OBJECT
SCALAR
UNION
}
-}
typeKind ::
forall n.
(MonadParse n) =>
Parser 'Both n ()
typeKind =
P.enum
GName.___TypeKind
Nothing
( NE.fromList
[ mkDefinition GName._ENUM,
mkDefinition GName._INPUT_OBJECT,
mkDefinition GName._INTERFACE,
mkDefinition GName._LIST,
mkDefinition GName._NON_NULL,
mkDefinition GName._OBJECT,
mkDefinition GName._SCALAR,
mkDefinition GName._UNION
]
)
where
mkDefinition name = (P.Definition name Nothing Nothing [] P.EnumValueInfo, ())
{-
type __Field {
name: String!
description: String
args: [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}
-}
fieldField ::
forall n.
(MonadParse n) =>
Parser 'Output n (P.Definition P.FieldInfo -> J.Value)
fieldField =
let name :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
name =
P.selection_ GName._name Nothing P.string
$> nameAsJSON
. P.dName
description :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
description =
P.selection_ GName._description Nothing P.string $> \defInfo ->
case P.dDescription defInfo of
Nothing -> J.Null
Just desc -> J.String (G.unDescription desc)
args :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
args = do
printer <- P.subselection_ GName._args Nothing inputValue
return $ J.Array . V.fromList . map printer . sortOn P.dName . P.fArguments . P.dInfo
typeF :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
typeF = do
printer <- P.subselection_ GName._type Nothing typeField
return $ printer . (\case P.FieldInfo _ tp -> SomeType tp) . P.dInfo
-- TODO We don't seem to track deprecation info
isDeprecated :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
isDeprecated =
P.selection_ GName._isDeprecated Nothing P.string
$> const (J.Bool False)
deprecationReason :: FieldParser n (P.Definition P.FieldInfo -> J.Value)
deprecationReason =
P.selection_ GName._deprecationReason Nothing P.string
$> const J.Null
in applyPrinter
<$> P.selectionSet
GName.___Field
Nothing
[ name,
description,
args,
typeF,
isDeprecated,
deprecationReason
]
{-
type __Directive {
name: String!
description: String
locations: [__DirectiveLocation!]!
args: [__InputValue!]!
isRepeatable: Boolean!
}
-}
directiveSet ::
forall n.
(MonadParse n) =>
Parser 'Output n (P.DirectiveInfo -> J.Value)
directiveSet =
let name :: FieldParser n (P.DirectiveInfo -> J.Value)
name =
P.selection_ GName._name Nothing P.string
$> (J.toOrdered . P.diName)
description :: FieldParser n (P.DirectiveInfo -> J.Value)
description =
P.selection_ GName._description Nothing P.string
$> (J.toOrdered . P.diDescription)
locations :: FieldParser n (P.DirectiveInfo -> J.Value)
locations =
P.selection_ GName._locations Nothing P.string
$> (J.toOrdered . map showDirLoc . P.diLocations)
args :: FieldParser n (P.DirectiveInfo -> J.Value)
args = do
printer <- P.subselection_ GName._args Nothing inputValue
pure $ J.array . map printer . P.diArguments
isRepeatable :: FieldParser n (P.DirectiveInfo -> J.Value)
isRepeatable =
P.selection_ GName._isRepeatable Nothing P.boolean
$> (J.Bool . P.diIsRepeatable)
in applyPrinter
<$> P.selectionSet
GName.___Directive
Nothing
[ name,
description,
locations,
args,
isRepeatable
]
where
showDirLoc :: G.DirectiveLocation -> Text
showDirLoc = \case
G.DLExecutable edl -> T.pack $ drop 3 $ show edl
G.DLTypeSystem tsdl -> T.pack $ drop 4 $ show tsdl
{-
type __Schema {
description: String
types: [__Type!]!
queryType: __Type!
mutationType: __Type
subscriptionType: __Type
directives: [__Directive!]!
}
-}
schemaSet ::
forall n.
(MonadParse n) =>
Parser 'Output n (Schema -> J.Value)
{-# INLINE schemaSet #-}
schemaSet =
let description :: FieldParser n (Schema -> J.Value)
description =
P.selection_ GName._description Nothing P.string
$> \partialSchema -> case sDescription partialSchema of
Nothing -> J.Null
Just s -> J.String $ G.unDescription s
types :: FieldParser n (Schema -> J.Value)
types = do
printer <- P.subselection_ GName._types Nothing typeField
return
$ \partialSchema ->
J.Array
$ V.fromList
$ map (printer . schemaTypeToSomeType)
$ sortOn P.getName
$ HashMap.elems
$ sTypes partialSchema
where
schemaTypeToSomeType :: P.SomeDefinitionTypeInfo -> SomeType
schemaTypeToSomeType (P.SomeDefinitionTypeInfo def) =
SomeType $ P.TNamed P.Nullable def
queryType :: FieldParser n (Schema -> J.Value)
queryType = do
printer <- P.subselection_ GName._queryType Nothing typeField
return $ \partialSchema -> printer $ SomeType $ sQueryType partialSchema
mutationType :: FieldParser n (Schema -> J.Value)
mutationType = do
printer <- P.subselection_ GName._mutationType Nothing typeField
return $ \partialSchema -> case sMutationType partialSchema of
Nothing -> J.Null
Just tp -> printer $ SomeType tp
subscriptionType :: FieldParser n (Schema -> J.Value)
subscriptionType = do
printer <- P.subselection_ GName._subscriptionType Nothing typeField
return $ \partialSchema -> case sSubscriptionType partialSchema of
Nothing -> J.Null
Just tp -> printer $ SomeType tp
directives :: FieldParser n (Schema -> J.Value)
directives = do
printer <- P.subselection_ GName._directives Nothing directiveSet
return $ \partialSchema -> J.array $ map printer $ sDirectives partialSchema
in applyPrinter
<$> P.selectionSet
GName.___Schema
Nothing
[ description,
types,
queryType,
mutationType,
subscriptionType,
directives
]
selectionSetToJSON ::
InsOrdHashMap.InsOrdHashMap G.Name J.Value ->
J.Value
selectionSetToJSON = J.object . map (first G.unName) . InsOrdHashMap.toList
applyPrinter ::
InsOrdHashMap.InsOrdHashMap G.Name (P.ParsedSelection (a -> J.Value)) ->
a ->
J.Value
applyPrinter = flip (\x -> selectionSetToJSON . fmap (($ x) . P.handleTypename (const . nameAsJSON)))
nameAsJSON :: (P.HasName a) => a -> J.Value
nameAsJSON = J.String . G.unName . P.getName