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