File Coverage

blib/lib/GraphQL/Introspection.pm
Criterion Covered Total %
statement 42 100 42.0
branch 0 30 0.0
condition 0 6 0.0
subroutine 18 18 100.0
pod n/a
total 60 154 38.9


line stmt bran cond sub pod time code
1             package GraphQL::Introspection;
2              
3 17     17   373 use 5.014;
  17         61  
4 17     17   90 use strict;
  17         35  
  17         347  
5 17     17   77 use warnings;
  17         31  
  17         446  
6 17     17   85 use Exporter 'import';
  17         30  
  17         511  
7 17     17   5852 use GraphQL::Type::Object;
  17         56  
  17         159  
8 17     17   8098 use GraphQL::Type::Enum;
  17         65  
  17         175  
9 17     17   130 use GraphQL::Type::Scalar qw($String $Boolean);
  17         42  
  17         2278  
10 17     17   145 use GraphQL::Debug qw(_debug);
  17         37  
  17         773  
11 17     17   113 use JSON::MaybeXS;
  17         39  
  17         1628  
12              
13             =head1 NAME
14              
15             GraphQL::Introspection - Perl implementation of GraphQL
16              
17             =cut
18              
19             our $VERSION = '0.02';
20              
21             our @EXPORT_OK = qw(
22             $QUERY
23             $TYPE_KIND_META_TYPE
24             $DIRECTIVE_LOCATION_META_TYPE
25             $ENUM_VALUE_META_TYPE
26             $INPUT_VALUE_META_TYPE
27             $FIELD_META_TYPE
28             $DIRECTIVE_META_TYPE
29             $TYPE_META_TYPE
30             $SCHEMA_META_TYPE
31             $SCHEMA_META_FIELD_DEF
32             $TYPE_META_FIELD_DEF
33             $TYPE_NAME_META_FIELD_DEF
34             );
35              
36 17     17   114 use constant DEBUG => $ENV{GRAPHQL_DEBUG};
  17         39  
  17         19937  
37             my $JSON_noutf8 = JSON::MaybeXS->new->utf8(0)->allow_nonref;
38              
39             =head1 SYNOPSIS
40              
41             use GraphQL::Introspection qw($QUERY);
42             my $schema_data = execute($schema, $QUERY);
43              
44             =head1 DESCRIPTION
45              
46             Provides infrastructure implementing GraphQL's introspection.
47              
48             =head1 EXPORT
49              
50             =head2 $QUERY
51              
52             The GraphQL query to introspect the schema.
53              
54             =cut
55              
56             our $QUERY = '
57             query IntrospectionQuery {
58             __schema {
59             queryType { name }
60             mutationType { name }
61             subscriptionType { name }
62             types {
63             ...FullType
64             }
65             directives {
66             name
67             description
68             locations
69             args {
70             ...InputValue
71             }
72             }
73             }
74             }
75             fragment FullType on __Type {
76             kind
77             name
78             description
79             fields(includeDeprecated: true) {
80             name
81             description
82             args {
83             ...InputValue
84             }
85             type {
86             ...TypeRef
87             }
88             isDeprecated
89             deprecationReason
90             }
91             inputFields {
92             ...InputValue
93             }
94             interfaces {
95             ...TypeRef
96             }
97             enumValues(includeDeprecated: true) {
98             name
99             description
100             isDeprecated
101             deprecationReason
102             }
103             possibleTypes {
104             ...TypeRef
105             }
106             }
107             fragment InputValue on __InputValue {
108             name
109             description
110             type { ...TypeRef }
111             defaultValue
112             }
113             fragment TypeRef on __Type {
114             kind
115             name
116             ofType {
117             kind
118             name
119             ofType {
120             kind
121             name
122             ofType {
123             kind
124             name
125             ofType {
126             kind
127             name
128             ofType {
129             kind
130             name
131             ofType {
132             kind
133             name
134             ofType {
135             kind
136             name
137             }
138             }
139             }
140             }
141             }
142             }
143             }
144             }
145             ';
146              
147             =head2 $TYPE_KIND_META_TYPE
148              
149             The enum type describing kinds of type. The second-most-meta type here,
150             after C<$TYPE_META_TYPE> itself.
151              
152             =cut
153              
154             # TODO sort out is_introspection
155             our $TYPE_KIND_META_TYPE = GraphQL::Type::Enum->new(
156             name => '__TypeKind',
157             is_introspection => 1,
158             description => 'An enum describing what kind of type a given `__Type` is.',
159             values => {
160             SCALAR => {
161             description => 'Indicates this type is a scalar.'
162             },
163             OBJECT => {
164             description => 'Indicates this type is an object. ' .
165             '`fields` and `interfaces` are valid fields.'
166             },
167             INTERFACE => {
168             description => 'Indicates this type is an interface. ' .
169             '`fields` and `possibleTypes` are valid fields.'
170             },
171             UNION => {
172             description => 'Indicates this type is a union. ' .
173             '`possibleTypes` is a valid field.'
174             },
175             ENUM => {
176             description => 'Indicates this type is an enum. ' .
177             '`enumValues` is a valid field.'
178             },
179             INPUT_OBJECT => {
180             description => 'Indicates this type is an input object. ' .
181             '`inputFields` is a valid field.'
182             },
183             LIST => {
184             description => 'Indicates this type is a list. ' .
185             '`ofType` is a valid field.'
186             },
187             NON_NULL => {
188             description => 'Indicates this type is a non-null. ' .
189             '`ofType` is a valid field.'
190             },
191             },
192             );
193              
194             =head2 $DIRECTIVE_LOCATION_META_TYPE
195              
196             The enum type describing directive locations.
197              
198             =cut
199              
200             # TODO sort out is_introspection
201             our $DIRECTIVE_LOCATION_META_TYPE = GraphQL::Type::Enum->new(
202             name => '__DirectiveLocation',
203             is_introspection => 1,
204             description =>
205             'A Directive can be adjacent to many parts of the GraphQL language, a ' .
206             '__DirectiveLocation describes one such possible adjacencies.',
207             values => {
208             QUERY => {
209             description => 'Location adjacent to a query operation.'
210             },
211             MUTATION => {
212             description => 'Location adjacent to a mutation operation.'
213             },
214             SUBSCRIPTION => {
215             description => 'Location adjacent to a subscription operation.'
216             },
217             FIELD => {
218             description => 'Location adjacent to a field.'
219             },
220             FRAGMENT_DEFINITION => {
221             description => 'Location adjacent to a fragment definition.'
222             },
223             FRAGMENT_SPREAD => {
224             description => 'Location adjacent to a fragment spread.'
225             },
226             INLINE_FRAGMENT => {
227             description => 'Location adjacent to an inline fragment.'
228             },
229             SCHEMA => {
230             description => 'Location adjacent to a schema definition.'
231             },
232             SCALAR => {
233             description => 'Location adjacent to a scalar definition.'
234             },
235             OBJECT => {
236             description => 'Location adjacent to an object type definition.'
237             },
238             FIELD_DEFINITION => {
239             description => 'Location adjacent to a field definition.'
240             },
241             ARGUMENT_DEFINITION => {
242             description => 'Location adjacent to an argument definition.'
243             },
244             INTERFACE => {
245             description => 'Location adjacent to an interface definition.'
246             },
247             UNION => {
248             description => 'Location adjacent to a union definition.'
249             },
250             ENUM => {
251             description => 'Location adjacent to an enum definition.'
252             },
253             ENUM_VALUE => {
254             description => 'Location adjacent to an enum value definition.'
255             },
256             INPUT_OBJECT => {
257             description => 'Location adjacent to an input object type definition.'
258             },
259             INPUT_FIELD_DEFINITION => {
260             description => 'Location adjacent to an input object field definition.'
261             },
262             },
263             );
264              
265             =head2 $ENUM_VALUE_META_TYPE
266              
267             The type describing enum values.
268              
269             =cut
270              
271             # makes field-resolver that takes resolver args and calls Moo accessor
272             # returns field_def
273             sub _make_moo_field {
274 0     62   0 my ($field_name, $type) = @_;
275             ($field_name => { resolve => sub {
276 23     53   164 my ($root_value, $args, $context, $info) = @_;
277 77 0       408 return undef unless $root_value->can($field_name);
278 0 0       0 my @passon = %$args ? ($args) : ();
279 0         0 $root_value->$field_name(@passon);
280 0         0 }, type => $type });
281             }
282              
283             # makes field-resolver that takes resolver args and looks up "real" hash val
284             # returns field_def
285             sub _make_hash_bool_field {
286 0     28   0 my ($field_name, $type, $real) = @_;
287             ($field_name => { resolve => sub {
288 0     132   0 my ($root_value, $args, $context, $info) = @_;
289 0         0 !!$root_value->{$real};
290 0         0 }, type => $type });
291             }
292              
293             # makes field-resolver that takes resolver args and looks up "real" hash val
294             # returns field_def
295             sub _make_hash_field {
296 0     28   0 my ($field_name, $type, $real) = @_;
297             ($field_name => { resolve => sub {
298 0     132   0 my ($root_value, $args, $context, $info) = @_;
299 0         0 $root_value->{$real};
300 0         0 }, type => $type });
301             }
302              
303             # hash, returns array-ref of hashes with keys put in as 'name'
304             sub _hash2array {
305 0     77   0 [ map { +{ name => $_, %{$_[0]->{$_}} } } sort keys %{$_[0]} ];
  0         0  
  0         0  
  0         0  
306             }
307              
308             our $ENUM_VALUE_META_TYPE = GraphQL::Type::Object->new(
309             name => '__EnumValue',
310             is_introspection => 1,
311             description =>
312             'One possible value for a given Enum. Enum values are unique values, not ' .
313             'a placeholder for a string or numeric value. However an Enum value is ' .
314             'returned in a JSON response as a string.',
315             fields => {
316             name => { type => $String->non_null },
317             description => { type => $String },
318             _make_hash_bool_field(isDeprecated => $Boolean->non_null, 'isDeprecated'),
319             _make_hash_field(deprecationReason => $String, 'deprecationReason'),
320             },
321             );
322              
323             =head2 $INPUT_VALUE_META_TYPE
324              
325             The type describing input values.
326              
327             =cut
328              
329             our $TYPE_META_TYPE; # predeclare so available for thunk
330             our $INPUT_VALUE_META_TYPE = GraphQL::Type::Object->new(
331             name => '__InputValue',
332             is_introspection => 1,
333             description =>
334             'Arguments provided to Fields or Directives and the input fields of an ' .
335             'InputObject are represented as Input Values which describe their type ' .
336             'and optionally a default value.',
337             fields => sub { {
338             name => { type => $String->non_null },
339             description => { type => $String },
340             type => { type => $TYPE_META_TYPE->non_null },
341             defaultValue => {
342             type => $String,
343             description =>
344             'A GraphQL-formatted string representing the default value for this ' .
345             'input value.',
346             resolve => sub {
347 28         289 DEBUG and _debug('__InputValue.defaultValue.resolve', @_);
348             # must be JSON-encoded one time extra as buildClientSchema wants
349             # it parseable as though literal in query - hence "GraphQL-formatted"
350 28 0       94 return unless defined(my $value = $_[0]->{default_value});
351 28         674 my $gql = $_[0]->{type}->perl_to_graphql($value);
352 77 0       3309 return $gql if $_[0]->{type}->isa('GraphQL::Type::Enum');
353 23         49 $JSON_noutf8->encode($gql);
354             },
355             },
356             } },
357             );
358              
359             =head2 $FIELD_META_TYPE
360              
361             The type describing fields.
362              
363             =cut
364              
365             our $FIELD_META_TYPE = GraphQL::Type::Object->new(
366             name => '__Field',
367             is_introspection => 1,
368             description =>
369             'Object and Interface types are described by a list of Fields, each of ' .
370             'which has a name, potentially a list of arguments, and a return type.',
371             fields => sub { {
372             name => { type => $String->non_null },
373             description => { type => $String },
374             args => {
375             type => $INPUT_VALUE_META_TYPE->non_null->list->non_null,
376 0   0     0 resolve => sub { _hash2array($_[0]->{args}||{}) },
377             },
378             type => { type => $TYPE_META_TYPE->non_null },
379             _make_hash_bool_field(isDeprecated => $Boolean->non_null, 'isDeprecated'),
380             _make_hash_field(deprecationReason => $String, 'deprecationReason'),
381             } },
382             );
383              
384             =head2 $DIRECTIVE_META_TYPE
385              
386             The type describing directives.
387              
388             =cut
389              
390             our $DIRECTIVE_META_TYPE = GraphQL::Type::Object->new(
391             name => '__Directive',
392             is_introspection => 1,
393             description =>
394             'A Directive provides a way to describe alternate runtime execution and ' .
395             'type validation behavior in a GraphQL document.' .
396             "\n\nIn some cases, you need to provide options to alter GraphQL's " .
397             'execution behavior in ways field arguments will not suffice, such as ' .
398             'conditionally including or skipping a field. Directives provide this by ' .
399             'describing additional information to the executor.',
400             fields => {
401             _make_moo_field(name => $String->non_null),
402             _make_moo_field(description => $String),
403             _make_moo_field(locations => $DIRECTIVE_LOCATION_META_TYPE->non_null->list->non_null),
404             args => {
405             type => $INPUT_VALUE_META_TYPE->non_null->list->non_null,
406 28         3676 resolve => sub { _hash2array($_[0]->args) },
407             },
408             # NOTE onOperation onFragment onField not part of spec -> not implemented
409             },
410             );
411              
412             =head2 $TYPE_META_TYPE
413              
414             The type describing a type. "Yo dawg..."
415              
416             =cut
417              
418 17         22777 use constant CLASS2KIND => {
419             'GraphQL::Type::Enum' => 'ENUM',
420             'GraphQL::Type::Interface' => 'INTERFACE',
421             'GraphQL::Type::List' => 'LIST',
422             'GraphQL::Type::Object' => 'OBJECT',
423             'GraphQL::Type::Union' => 'UNION',
424             'GraphQL::Type::InputObject' => 'INPUT_OBJECT',
425             'GraphQL::Type::NonNull' => 'NON_NULL',
426             'GraphQL::Type::Scalar' => 'SCALAR',
427 17     17   174 };
  17         45  
428              
429             $TYPE_META_TYPE = GraphQL::Type::Object->new(
430             name => '__Type',
431             is_introspection => 1, # and then some
432             description =>
433             'The fundamental unit of any GraphQL Schema is the type. There are ' .
434             'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' .
435             "\n\nDepending on the kind of a type, certain fields describe " .
436             'information about that type. Scalar types provide no information ' .
437             'beyond a name and description, while Enum types provide their values. ' .
438             'Object and Interface types provide the fields they describe. Abstract ' .
439             'types, Union and Interface, provide the Object types possible ' .
440             'at runtime. List and NonNull types compose other types.',
441             fields => sub { {
442             kind => {
443             type => $TYPE_KIND_META_TYPE->non_null,
444 0   0     0 resolve => sub { my $c = ref $_[0]; $c =~ s#__.*##; CLASS2KIND->{$c} // die "Unknown kind of type => ".ref $_[0] },
  0         0  
  0         0  
445             },
446             name => { resolve => sub {
447 0         0 my ($root_value, $args, $context, $info) = @_;
448             # if not a "real" name
449 0 0       0 return undef if $root_value->can('of');
450 0 0       0 my @passon = %$args ? ($args) : ();
451 0         0 $root_value->name(@passon);
452             }, type => $String },
453             _make_moo_field(description => $String),
454             fields => {
455             type => $FIELD_META_TYPE->non_null->list,
456             args => { includeDeprecated => { type => $Boolean, default_value => 0 } },
457             resolve => sub {
458 0         0 my ($type, $args) = @_;
459 0 0       0 return undef if !$type->DOES('GraphQL::Role::FieldsOutput');
460 0         0 my $map = $type->fields;
461             $map = {
462 0         0 map { ($_ => $map->{$_}) } grep !$map->{$_}{deprecation_reason}, keys %$map
463 0 0       0 } if !$args->{includeDeprecated};
464             [ map { +{
465             name => $_,
466             description => $map->{$_}{description},
467             args => $map->{$_}{args},
468             type => $map->{$_}{type},
469             isDeprecated => $map->{$_}{is_deprecated},
470             deprecationReason => $map->{$_}{deprecation_reason},
471 0         0 } } sort keys %{$map} ];
  0         0  
  0         0  
472             }
473             },
474             interfaces => {
475             type => $TYPE_META_TYPE->non_null->list,
476             resolve => sub {
477 0         0 my ($type) = @_;
478 0 0       0 return if !$type->isa('GraphQL::Type::Object');
479 0 0       0 $type->interfaces || [];
480             }
481             },
482             possibleTypes => {
483             type => $TYPE_META_TYPE->non_null->list,
484             resolve => sub {
485 0 0       0 return if !$_[0]->DOES('GraphQL::Role::Abstract');
486 0         0 $_[3]->{schema}->get_possible_types($_[0]);
487             },
488             },
489             enumValues => {
490             type => $ENUM_VALUE_META_TYPE->non_null->list,
491             args => {
492             includeDeprecated => { type => $Boolean, default_value => 0 }
493             },
494             resolve => sub {
495 0         0 my ($type, $args) = @_;
496 0 0       0 return if !$type->isa('GraphQL::Type::Enum');
497 0         0 my $values = $type->values;
498 0         0 DEBUG and _debug('enumValues.resolve', $type, $args, $values);
499 0 0       0 $values = { map { ($_ => $values->{$_}) } grep !$values->{$_}{is_deprecated}, keys %$values } if !$args->{includeDeprecated};
  0         0  
500             [ map { +{
501             name => $_,
502             description => $values->{$_}{description},
503             isDeprecated => $values->{$_}{is_deprecated},
504             deprecationReason => $values->{$_}{deprecation_reason},
505 0         0 } } sort keys %{$values} ];
  0         0  
  0         0  
506             },
507             },
508             inputFields => {
509             type => $INPUT_VALUE_META_TYPE->non_null->list,
510             resolve => sub {
511 0         0 my ($type) = @_;
512 0 0       0 return if !$type->isa('GraphQL::Type::InputObject');
513 0   0     0 _hash2array($type->fields || {});
514             },
515             },
516             ofType => {
517             type => $TYPE_META_TYPE,
518 0 0       0 resolve => sub { return unless $_[0]->can('of'); $_[0]->of },
  0         0  
519             },
520             } },
521             );
522              
523             =head2 $SCHEMA_META_TYPE
524              
525             The type describing the schema itself.
526              
527             =cut
528              
529             our $SCHEMA_META_TYPE = GraphQL::Type::Object->new(
530             name => '__Schema',
531             is_introspection => 1,
532             description =>
533             'A GraphQL Schema defines the capabilities of a GraphQL server. It ' .
534             'exposes all available types and directives on the server, as well as ' .
535             'the entry points for query, mutation, and subscription operations.',
536             fields => {
537             types => {
538             description => 'A list of all types supported by this server.',
539             type => $TYPE_META_TYPE->non_null->list->non_null,
540 0         0 resolve => sub { [ sort { $a->name cmp $b->name } values %{ $_[0]->name2type } ] },
  0         0  
  0         0  
541             },
542             queryType => {
543             description => 'The type that query operations will be rooted at.',
544             type => $TYPE_META_TYPE->non_null,
545 0         0 resolve => sub { $_[0]->query },
546             },
547             mutationType => {
548             description => 'If this server supports mutation, the type that ' .
549             'mutation operations will be rooted at.',
550             type => $TYPE_META_TYPE,
551 0         0 resolve => sub { $_[0]->mutation },
552             },
553             subscriptionType => {
554             description => 'If this server support subscription, the type that ' .
555             'subscription operations will be rooted at.',
556             type => $TYPE_META_TYPE,
557 0         0 resolve => sub { $_[0]->subscription },
558             },
559             directives => {
560             description => 'A list of all directives supported by this server.',
561             type => $DIRECTIVE_META_TYPE->non_null->list->non_null,
562 62         1141 resolve => sub { $_[0]->directives },
563             }
564             },
565             );
566              
567             =head2 $SCHEMA_META_FIELD_DEF
568              
569             The meta-field existing on the top query.
570              
571             =cut
572              
573             our $SCHEMA_META_FIELD_DEF = {
574             name => '__schema',
575             type => $SCHEMA_META_TYPE->non_null,
576             description => 'Access the current type schema of this server.',
577 62         3748 resolve => sub { $_[3]->{schema} }, # the $info
578             };
579              
580             =head2 $TYPE_META_FIELD_DEF
581              
582             The meta-field existing on the top query, describing a named type.
583              
584             =cut
585              
586             our $TYPE_META_FIELD_DEF = {
587             name => '__type',
588             type => $TYPE_META_TYPE,
589             description => 'Request the type information of a single type.',
590             args => { name => { type => $String->non_null } },
591 0         0 resolve => sub { $_[3]->{schema}->name2type->{$_[1]->{name}} }, # the $args, $info
592             };
593              
594             =head2 $TYPE_NAME_META_FIELD_DEF
595              
596             The meta-field existing on each object field, naming its type.
597              
598             =cut
599              
600             our $TYPE_NAME_META_FIELD_DEF = {
601             name => '__typename',
602             type => $String->non_null,
603             description => 'The name of the current Object type at runtime.',
604             resolve => sub { $_[3]->{parent_type}->name }, # the $info
605             };
606              
607             1;