File Coverage

blib/lib/GraphQL/Execution.pm
Criterion Covered Total %
statement 362 422 85.7
branch 194 326 59.5
condition 38 45 84.4
subroutine 59 60 98.3
pod 1 1 100.0
total 654 854 76.5


line stmt bran cond sub pod time code
1              
2             use 5.014;
3 15     15   20626 use strict;
  4578         42570  
4 4577     15   25219 use warnings;
  4779         34666  
  5286         38434  
5 5079     15   13284 use Types::Standard -all;
  5068         14984  
  4624         14604  
6 2836     15   37477 use Types::TypeTiny -all;
  3561         24426  
  1373         18234  
7 1765     15   623208 use GraphQL::Type::Library -all;
  1702         5765  
  1741         7238  
8 2908     15   19227 use GraphQL::MaybeTypeCheck;
  1536         2602  
  1686         3776  
9 2858     15   185864 use GraphQL::Language::Parser qw(parse);
  1495         4582  
  922         6573  
10 197     15   886 use GraphQL::Error;
  801         10419  
  271         1851  
11 231     15   660 use JSON::MaybeXS;
  586         1812  
  16         131  
12 216     15   493 use GraphQL::Debug qw(_debug);
  256         868  
  23         898  
13 216     15   573 use GraphQL::Introspection qw(
  17         80  
  17         695  
14 33         1707 $SCHEMA_META_FIELD_DEF $TYPE_META_FIELD_DEF $TYPE_NAME_META_FIELD_DEF
15             );
16 17     15   125 use GraphQL::Directive;
  214         1402  
17 214     15   600 use GraphQL::Schema qw(lookup_type);
  17         79  
  212         923  
18 212     15   346 use Exporter 'import';
  212         474  
  246         984  
19 246     15   429 use Scalar::Util qw(blessed);
  246         381  
  246         709  
20 246     15   378  
  246         733  
  64         1413  
21             =head1 NAME
22              
23             GraphQL::Execution - Execute GraphQL queries
24              
25             =cut
26              
27             our @EXPORT_OK = qw(
28             execute
29             );
30             our $VERSION = '0.02';
31              
32             my $JSON = JSON::MaybeXS->new->allow_nonref;
33             use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false
34 64     15   190  
  55         364  
  55         1611  
35             =head1 SYNOPSIS
36              
37             use GraphQL::Execution qw(execute);
38             my $result = execute($schema, $doc, $root_value);
39              
40             =head1 DESCRIPTION
41              
42             Executes a GraphQL query, returns results.
43              
44             =head1 METHODS
45              
46             =head2 execute
47              
48             my $result = execute(
49             $schema,
50             $doc, # can also be AST
51             $root_value,
52             $context_value,
53             $variable_values,
54             $operation_name,
55             $field_resolver,
56             $promise_code,
57             );
58              
59             =over
60              
61             =item $schema
62              
63             A L<GraphQL::Schema>.
64              
65             =item $doc
66              
67             Either a GraphQL query document to be fed in to
68             L<GraphQL::Language::Parser/parse>, or a return value from that.
69              
70             =item $root_value
71              
72             A root value that can be used by field-resolvers. The default one needs
73             a code-ref, a hash-ref or an object.
74              
75             For instance:
76              
77             my $schema = GraphQL::Schema->from_doc(<<'EOF');
78             type Query { dateTimeNow: String, hi: String }
79             EOF
80             my $root_value = {
81             dateTimeNow => sub { DateTime->now->ymd },
82             hi => "Bob",
83             };
84             my $data = execute($schema, "{ dateTimeNow hi }", $root_value);
85              
86             will return:
87              
88             {
89             data => {
90             dateTimeNow => { ymd => '20190501' },
91             hi => 'Bob',
92             }
93             }
94              
95             Be aware that with the default field-resolver, when it calls a method,
96             that method will get called with L<GraphQL::Type::Library/$args>
97             L<GraphQL::Type::Library/$context>, L<GraphQL::Type::Library/$info>. To
98             override that to pass no parameters, this is suitable as a
99             C<$field_resolver> parameters:
100              
101             sub {
102             my ($root_value, $args, $context, $info) = @_;
103             my $field_name = $info->{field_name};
104             my $property = ref($root_value) eq 'HASH'
105             ? $root_value->{$field_name}
106             : $root_value;
107             return $property->($args, $context, $info) if ref $property eq 'CODE';
108             return $root_value->$field_name if ref $property; # no args
109             $property;
110             }
111              
112             =item $context_value
113              
114             A per-request scalar, that will be passed to field-resolvers.
115              
116             =item $variable_values
117              
118             A hash-ref, typically the decoded JSON object supplied by a
119             client. E.g. for this query:
120              
121             query q($input: TestInputObject) {
122             fieldWithObjectInput(input: $input)
123             }
124              
125             The C<$variable_values> will need to be a JSON object with a
126             key C<input>, whose value will need to conform to the L<input
127             type|GraphQL::Type::InputObject> C<TestInputObject>.
128              
129             The purpose of this is to avoid needing to hard-code input values in
130             your query. This aids in, among other things, being able to whitelist
131             individual queries as acceptable, non-abusive queries to your system;
132             and being able to generate client-side code for client-side validation
133             rather than including the full GraphQL system in client code.
134              
135             =item $operation_name
136              
137             A string (or C<undef>) that if given will be the name of one of the
138             operations in the query.
139              
140             =item $field_resolver
141              
142             A code-ref to be used instead of the default field-resolver.
143              
144             =item $promise_code
145              
146             If you need to return a promise, supply a hash-ref matching
147             L<GraphQL::Type::Library/PromiseCode>.
148              
149             =back
150              
151             =cut
152              
153             fun execute(
154             (InstanceOf['GraphQL::Schema']) $schema,
155             Str | ArrayRef[HashRef] $doc,
156             Any $root_value = undef,
157             Any $context_value = undef,
158             Maybe[HashRef] $variable_values = undef,
159             Maybe[Str] $operation_name = undef,
160             Maybe[CodeLike] $field_resolver = undef,
161             Maybe[PromiseCode] $promise_code = undef,
162             ) :ReturnType(ExecutionResult | Promise) {
163 361 100   219 1 1103698 my $context = eval {
  361 50       1268  
  361 100       1465  
  361 100       1370  
  28 100       12957  
  17 100       53  
  17 100       84  
  261 100       10332  
  261 100       613  
  454 100       1315  
  259         763  
  15         5826  
164 15         36 my $ast = ref($doc) ? $doc : parse($doc);
165 15 100       66 _build_context(
166 99         278 $schema,
167             $ast,
168             $root_value,
169             $context_value,
170             $variable_values,
171             $operation_name,
172             $field_resolver,
173             $promise_code,
174             );
175             };
176             DEBUG and _debug('execute', $context, $@);
177 99         252 return _build_response(_wrap_error($@)) if $@;
178 99 100       201 my $result = _execute_operation(
179             $context,
180             $context->{operation},
181             $root_value,
182 99         275 );
183             DEBUG and _debug('execute(result)', $result, $@);
184 99         650 _build_response($result, 1);
185 99         310 }
186 157     15   58553  
  157         342  
  157         308  
187             fun _build_response(
188             ExecutionPartialResult | Promise $result,
189             Bool $force_data = 0,
190             ) :ReturnType(ExecutionResult | Promise) {
191 15 100   259   69 return $result->then(sub { _build_response(@_) }) if is_Promise($result);
  229 100       673  
  229 100       597  
  229 50       593  
  229 100       769  
  229         3005  
192 229 100   38   3961 my @errors = @{$result->{errors} || []};
  229         1558  
193 229 100       1433 +{
  229         1865  
194             $force_data ? (data => undef) : (), # default if none given
195             %$result,
196             @errors ? (errors => [ map $_->to_json, @{$result->{errors}} ]) : (),
197 229 100       2080 };
  229 100       1972  
198             }
199 99     15   1187  
  15         21966  
  15         48  
200             fun _wrap_error(
201             Any $error,
202             ) :ReturnType(ExecutionPartialResult) {
203 229 50   99   718 return $error if is_ExecutionPartialResult($error);
  229 50       598  
  227 50       775  
  226         684  
  224         1749  
204 15 50       12442 +{ errors => [ GraphQL::Error->coerce($error) ] };
205 15         35 }
206 229     15   6010  
  229         845  
  18         98  
207             fun _build_context(
208             (InstanceOf['GraphQL::Schema']) $schema,
209             ArrayRef[HashRef] $ast,
210             Any $root_value,
211             Any $context_value,
212             Maybe[HashRef] $variable_values,
213             Maybe[Str] $operation_name,
214             Maybe[CodeLike] $field_resolver,
215             Maybe[PromiseCode] $promise_code,
216             ) :ReturnType(HashRef) {
217 224 50   229   444 my %fragments = map {
  224 100       565  
  224 100       2151  
  224 50       1804  
  224 50       1278  
  224 50       685  
  60 50       1465  
  223 50       3660  
  1 50       18  
  222 100       2076  
  58         113  
  58         960  
218             ($_->{name} => $_)
219 58         250 } grep $_->{kind} eq 'fragment', @$ast;
220 58         1112 my @operations = grep $_->{kind} eq 'operation', @$ast;
221 58         158 die "No operations supplied.\n" if !@operations;
222 58 100       288 die "Can only execute document containing fragments or operations\n"
223 58 100       522 if @$ast != keys(%fragments) + @operations;
224             my $operation = _get_operation($operation_name, \@operations);
225 14         33 {
226             schema => $schema,
227             fragments => \%fragments,
228             root_value => $root_value,
229             context_value => $context_value,
230             operation => $operation,
231             variable_values => _variables_apply_defaults(
232             $schema,
233             $operation->{variables} || {},
234             $variable_values || {},
235 14   100     45 ),
      100        
      100        
236             field_resolver => $field_resolver || \&_default_field_resolver,
237             promise_code => $promise_code,
238             };
239             }
240 15     15   100  
  224         589  
  224         502  
241             # takes each operation var: query q(a: String)
242             # applies to it supplied variable from web request
243             # if none, applies any defaults in the operation var: query q(a: String = "h")
244             # converts with graphql_to_perl (which also validates) to Perl values
245             # return { varname => { value => ..., type => $type } }
246             fun _variables_apply_defaults(
247             (InstanceOf['GraphQL::Schema']) $schema,
248             HashRef $operation_variables,
249             HashRef $variable_values,
250             ) :ReturnType(HashRef) {
251 828 50   224   59046 my @bad = grep {
  799 50       20782  
  799 50       32  
  799 50       961  
  982 50       1983  
  982         1692  
  2357         3650  
252             ! lookup_type($operation_variables->{$_}, $schema->name2type)->DOES('GraphQL::Role::Input');
253 2357         3308 } keys %$operation_variables;
  2357         4878  
254             die "Variable '\$$bad[0]' is type '@{[
255 2357 50       6034 lookup_type($operation_variables->{$bad[0]}, $schema->name2type)->to_string
256 2357         3887 ]}' which cannot be used as an input type.\n" if @bad;
257             +{ map {
258             my $opvar = $operation_variables->{$_};
259 2357         4571 my $opvar_type = lookup_type($opvar, $schema->name2type);
  2356         6491  
260 2356         6741 my $parsed_value;
261 2356         5587 my $maybe_value = $variable_values->{$_} // $opvar->{default_value};
262 2356   100     16205 eval { $parsed_value = $opvar_type->graphql_to_perl($maybe_value) };
263 2356         7076 if ($@) {
  2340         7675  
264 2340 50       9315 my $error = $@;
265 2340         9723 $error =~ s#\s+at.*line\s+\d+\.#.#;
266 964         2557 # JSON cannot encode scalar references
267             my $jsonable = _coerce_for_error($maybe_value);
268 806         2060 die "Variable '\$$_' got invalid value @{[$JSON->canonical->encode($jsonable)]}.\n$error";
269 654         2930 }
  0         0  
270             ($_ => { value => $parsed_value, type => $opvar_type })
271 198         4139 } keys %$operation_variables };
272             }
273 14     15   39  
  14         36  
  14         222  
274             fun _get_operation(
275             Maybe[Str] $operation_name,
276             ArrayRef[HashRef] $operations,
277             ) {
278 2471 50   226   4701 DEBUG and _debug('_get_operation', @_);
  1880 50       6170  
  226 50       605  
  226 50       530  
  226         514  
  226         567  
279 226         1910 if (!$operation_name) {
280 226 50       3011 die "Must provide operation name if query contains multiple operations.\n"
281 226 50       294 if @$operations > 1;
282             return $operations->[0];
283 226         551 }
284             my @matching = grep $_->{name} eq $operation_name, @$operations;
285 214         516 return $matching[0] if @matching == 1;
286 213 50       499 die "No operations matching '$operation_name' found.\n";
287 12         44 }
288              
289             fun _execute_operation(
290             HashRef $context,
291             HashRef $operation,
292             Any $root_value,
293             ) :ReturnType(ExecutionPartialResult | Promise) {
294 15 100   198   73 my $op_type = $operation->{operationType} || 'query';
  784 100       1807  
  784 50       1495  
  784 50       1549  
  784 50       1762  
  784         5622  
  784         7645  
295 784   100     4993 my $type = $context->{schema}->$op_type;
296 784         5016 return _wrap_error("No $op_type in schema") if !$type;
297 15 50       14079 my ($fields) = $type->_collect_fields(
298             $context,
299             $operation->{selections},
300             [[], {}],
301 15         36 {},
302             );
303             DEBUG and _debug('_execute_operation(fields)', $fields, $root_value);
304 15         63 my $path = [];
305 725         1416 my $execute = $op_type eq 'mutation'
306 725 50       1306 ? \&_execute_fields_serially : \&_execute_fields;
307             my $result = eval {
308 725         1196 my $result = $execute->($context, $type, $root_value, $path, $fields);
309 725         1701 return $result if !is_Promise($result);
310 15 50       10724 $result->then(undef, sub {
311             $context->{promise_code}{resolve}->(
312             +{ data => undef, %{_wrap_error($_[0])} }
313 15     0   70 );
  114         258  
314             });
315 15         42 };
316             return _wrap_error($@) if $@;
317 114 50       225 $result;
318 114         208 }
319 196     15   728  
  15         14024  
  15         33  
320             fun _execute_fields(
321             HashRef $context,
322             (InstanceOf['GraphQL::Type']) $parent_type,
323             Any $root_value,
324             ArrayRef $path,
325             FieldsGot $fields,
326             ) :ReturnType(ExecutionPartialResult | Promise) {
327 114 50   784   1459 my (%name2executionresult, @errors);
  114 50       446  
  114 50       155  
  114 50       252  
  114 0       287  
  71 0       10600  
  71 0       542  
  15         10604  
  15         36  
328 15         32 my $promise_present;
329 30         1477 DEBUG and _debug('_execute_fields', $parent_type->to_string, $fields, $root_value);
330 15         5394 my ($field_names, $nodes_defs) = @$fields;
331 15         43 for my $result_name (@$field_names) {
332 15         63 my $nodes = $nodes_defs->{$result_name};
333 2169         3734 my $field_node = $nodes->[0];
334 2169         3448 my $field_name = $field_node->{name};
335 2169         3624 my $field_def = _get_field_def($context->{schema}, $parent_type, $field_name);
336 2169         5004 DEBUG and _debug('_execute_fields(resolve)', $parent_type->to_string, $nodes, $root_value, $field_def);
337 2169         20719 next if !$field_def;
338 2169 0       17433 my $resolve = $field_def->{resolve} || $context->{field_resolver};
339 2169   100     26292 my $info = _build_resolve_info(
340 2169         4574 $context,
341             $parent_type,
342             $field_def,
343             [ @$path, $result_name ],
344             $nodes,
345             );
346             my $result = _resolve_field_value_or_error(
347 2169         4863 $context,
348             $field_def,
349             $nodes,
350             $resolve,
351             $root_value,
352             $info,
353             );
354             DEBUG and _debug('_execute_fields(resolved)', $parent_type->to_string, $result);
355 0         0 $result = _complete_value_catching_error(
356             $context,
357             $field_def->{type},
358             $nodes,
359 0         0 $info,
360             [ @$path, $result_name ],
361             $result,
362             );
363             $promise_present ||= is_Promise($result);
364 0   66     0 DEBUG and _debug("_execute_fields(complete)($result_name)", $result);
365 0         0 $name2executionresult{$result_name} = $result;
366 0         0 }
367             DEBUG and _debug('_execute_fields(done)', \%name2executionresult, \@errors, $promise_present);
368 0         0 return _promise_for_hash($context, \%name2executionresult, \@errors)
369 0 0       0 if $promise_present;
370             _merge_hash(
371 0         0 [ keys %name2executionresult ],
372             [ values %name2executionresult ],
373             \@errors,
374             );
375             }
376 114     15   314  
  114         991  
  114         747  
377             fun _merge_hash(
378             ArrayRef[Str] $keys,
379             ArrayRef[ExecutionPartialResult] $values,
380             (ArrayRef[InstanceOf['GraphQL::Error']]) $errors,
381             ) :ReturnType(ExecutionPartialResult) {
382 0 0   725   0 DEBUG and _debug('_merge_hash', $keys, $values, $errors);
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
383 0         0 my @errors = (@$errors, map @{$_->{errors} || []}, @$values);
384 0 0       0 my %name2data;
  0         0  
385 0         0 for (my $i = @$values - 1; $i >= 0; $i--) {
386 0         0 $name2data{$keys->[$i]} = $values->[$i]{data};
387 0         0 }
388             DEBUG and _debug('_merge_hash(after)', \%name2data, \@errors);
389 0         0 +{
390             %name2data ? (data => \%name2data) : (),
391 0 0       0 @errors ? (errors => \@errors) : ()
    0          
392             };
393             }
394 0     15   0  
  0         0  
  0         0  
395             fun _promise_for_hash(
396             HashRef $context,
397             HashRef $hash,
398             (ArrayRef[InstanceOf['GraphQL::Error']]) $errors,
399             ) :ReturnType(Promise) {
400 0 0   114   0 my ($keys, $values) = ([ keys %$hash ], [ values %$hash ]);
  0 0       0  
  0 0       0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
401 0         0 DEBUG and _debug('_promise_for_hash', $keys);
402 0         0 die "Given a promise in object but no PromiseCode given\n"
403             if !$context->{promise_code};
404 0 50       0 $context->{promise_code}{all}->(@$values)->then(sub {
405             DEBUG and _debug('_promise_for_hash(all)', \@_);
406 0     71   0 _merge_hash($keys, [ map $_->[0], @_ ], $errors);
407 0         0 });
408 0         0 }
409 0     15   0  
  0         0  
  0         0  
410             fun _execute_fields_serially(
411             HashRef $context,
412             (InstanceOf['GraphQL::Type']) $parent_type,
413             Any $root_value,
414             ArrayRef $path,
415             FieldsGot $fields,
416             ) {
417 12 50   7   41 DEBUG and _debug('_execute_fields_serially', $parent_type->to_string, $fields, $root_value);
  1 50       14  
  7 50       28  
  7 50       21  
  7 50       19  
  7 50       24  
  7 50       58  
  7         84  
  7         70  
418 7         73 # TODO implement
419             goto &_execute_fields;
420 7         345 }
421              
422             use constant FIELDNAME2SPECIAL => {
423             map { ($_->{name} => $_) } $SCHEMA_META_FIELD_DEF, $TYPE_META_FIELD_DEF
424 0         0 };
  0         0  
425 0     15   0 fun _get_field_def(
  0         0  
426             (InstanceOf['GraphQL::Schema']) $schema,
427             (InstanceOf['GraphQL::Type']) $parent_type,
428             StrNameValid $field_name,
429             ) :ReturnType(Maybe[HashRef]) {
430 0 50   2169   0 return $TYPE_NAME_META_FIELD_DEF
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
431             if $field_name eq $TYPE_NAME_META_FIELD_DEF->{name};
432 0 50       0 return FIELDNAME2SPECIAL->{$field_name}
433             if FIELDNAME2SPECIAL->{$field_name} and $parent_type == $schema->query;
434 254 100 100     5568 $parent_type->fields->{$field_name};
435 0         0 }
436 0     15   0  
  0         0  
  0         0  
437             # NB similar ordering as _execute_fields - graphql-js switches
438             fun _build_resolve_info(
439             HashRef $context,
440             (InstanceOf['GraphQL::Type']) $parent_type,
441             HashRef $field_def,
442             ArrayRef $path,
443             ArrayRef[HashRef] $nodes,
444             ) {
445 7 50   2167   13 {
  7 50       27  
  2167 50       4002  
  2167 50       3433  
  2167 50       3294  
  2167 50       4785  
  2167 50       16224  
  2167         18711  
  2167         12707  
446             field_name => $nodes->[0]{name},
447             field_nodes => $nodes,
448             return_type => $field_def->{type},
449             parent_type => $parent_type,
450             path => $path,
451             schema => $context->{schema},
452             fragments => $context->{fragments},
453             root_value => $context->{root_value},
454             operation => $context->{operation},
455             variable_values => $context->{variable_values},
456             promise_code => $context->{promise_code},
457             };
458 2167         13098 }
459              
460             fun _resolve_field_value_or_error(
461             HashRef $context,
462             HashRef $field_def,
463             ArrayRef[HashRef] $nodes,
464             Maybe[CodeLike] $resolve,
465             Maybe[Any] $root_value,
466             HashRef $info,
467             ) {
468 2167 50   2167   24418 DEBUG and _debug('_resolve_field_value_or_error', $nodes, $field_def, eval { $JSON->encode($nodes->[0]) });
  2167 50       15420  
  2167 100       3709  
  2167 50       3385  
  2167 50       3603  
  2167 50       3747  
  2167 50       13085  
  2167 50       11906  
  2167         22799  
  2167         19431  
469 141         365 my $result = eval {
470 141         313 my $args = _get_argument_values(
471             $field_def, $nodes->[0], $context->{variable_values},
472             );
473 141         279 DEBUG and _debug("_resolve_field_value_or_error(args)", $args, eval { $JSON->encode($args) });
474 141         369 $resolve->($root_value, $args, $context->{context_value}, $info)
475 141         1044 };
476             return GraphQL::Error->coerce($@) if $@;
477 141 50       1823 $result;
478 141         855 }
479              
480             fun _complete_value_catching_error(
481             HashRef $context,
482             (InstanceOf['GraphQL::Type']) $return_type,
483             ArrayRef[HashRef] $nodes,
484             HashRef $info,
485             ArrayRef $path,
486             Any $result,
487             ) :ReturnType(ExecutionPartialResult | Promise) {
488 15 100   2522   44 DEBUG and _debug('_complete_value_catching_error(before)', $return_type->to_string, $result);
  15 100       98  
  2522 50       4662  
  2522 50       4105  
  2522 50       4230  
  2522 50       4696  
  2522 50       16390  
  2522 50       22011  
  2522         28566  
  2522         14089  
489 2522         14892 if ($return_type->isa('GraphQL::Type::NonNull')) {
490 2522 50       13039 return _complete_value_with_located_error(@_);
491 2522         2653 }
492             my $result = eval {
493 2522         7475 my $c = _complete_value_with_located_error(@_);
494 1097         2091 return $c if !is_Promise($c);
495 1425 50       1865 $c->then(undef, sub {
496             $context->{promise_code}{resolve}->(_wrap_error(@_))
497 1384     33   2345 });
498 1425         2857 };
499             DEBUG and _debug("_complete_value_catching_error(after)(@{[$return_type->to_string]})", $return_type->to_string, $result, $@);
500 98         1091 return _wrap_error($@) if $@;
501 33 100       2228 $result;
502 1425         16224 }
503 0     15   0  
  1902         36751  
  15         46822  
504             fun _complete_value_with_located_error(
505             HashRef $context,
506             (InstanceOf['GraphQL::Type']) $return_type,
507             ArrayRef[HashRef] $nodes,
508             HashRef $info,
509             ArrayRef $path,
510             Any $result,
511             ) :ReturnType(ExecutionPartialResult | Promise) {
512 15 100   2522   35 my $result = eval {
  15 50       75  
  2522 50       3934  
  2522 50       3777  
  2522 50       3882  
  2522 50       4155  
  2522 50       14719  
  2522 50       17665  
  2522         27013  
  2522         13739  
513 2522         13786 my $c = _complete_value(@_);
514 2522         12489 return $c if !is_Promise($c);
515 2522 50       3170 $c->then(undef, sub {
516             $context->{promise_code}{reject}->(
517 2461     80   11724 _located_error($_[0], $nodes, $path)
518             )
519             });
520 2522         4396 };
521             DEBUG and _debug('_complete_value_with_located_error(after)', $return_type->to_string, $result, $@);
522 164         1931 die _located_error($@, $nodes, $path) if $@;
523 80 100       5564 $result;
524 2522         31978 }
525 1425     15   2438  
  1384         2274  
  15         14918  
526             fun _complete_value(
527             HashRef $context,
528             (InstanceOf['GraphQL::Type']) $return_type,
529             ArrayRef[HashRef] $nodes,
530             HashRef $info,
531             ArrayRef $path,
532             Any $result,
533             ) :ReturnType(ExecutionPartialResult | Promise) {
534 15 100   3686   31 DEBUG and _debug('_complete_value', $return_type->to_string, $path, $result);
  15 100       62  
  3686 100       5669  
  3686 100       5321  
  3686 50       5603  
  3686 50       5922  
  3686 50       21127  
  3686 50       26440  
  3686         38645  
  3686         19869  
535 3686         19564 if (is_Promise($result)) {
536 3686 100       17642 my @outerargs = @_[0..4];
537 3686         3776 return $result->then(sub { _complete_value(@outerargs, $_[0]) });
538 3686     79   7512 }
  98         1128  
539             die $result if GraphQL::Error->is($result);
540 98 100       547 if ($return_type->isa('GraphQL::Type::NonNull')) {
541 79 50       6387 my $completed = _complete_value(
542 3588         21009 $context,
543             $return_type->of,
544             $nodes,
545             $info,
546             $path,
547             $result,
548             );
549             DEBUG and _debug('_complete_value(NonNull)', $return_type->to_string, $completed);
550 3556         9926 # The !is_Promise is necessary unlike in the JS because there the
551             # null-check will work fine on either a promise or a real value.
552             die GraphQL::Error->coerce(
553             "Cannot return null for non-nullable field @{[$info->{parent_type}->name]}.@{[$info->{field_name}]}."
554 1077         6103 ) if !is_Promise($completed) and !defined $completed->{data};
  1077         2137  
555 1085 100 100     3097 return $completed;
556 17         220 }
557             return { data => undef } if !defined $result;
558 17 50       89 $return_type->_complete_value(
559 1060         7707 $context,
560             $nodes,
561             $info,
562             $path,
563             $result,
564             );
565             }
566 2522     15   3945  
  2461         5070  
  15         16083  
567             fun _located_error(
568             Any $error,
569             ArrayRef[HashRef] $nodes,
570             ArrayRef $path,
571             ) {
572 141 100   141   156 DEBUG and _debug('_located_error', $error);
  141 100       421  
  141 50       10003  
  76 50       195  
  2188 50       3649  
  2188         3352  
  2188         4293  
573 2188         4372 $error = GraphQL::Error->coerce($error);
574 39         82 return $error if $error->locations;
575 39 50       74 GraphQL::Error->coerce($error)->but(
576             locations => [ map $_->{location}, @$nodes ],
577 39         56 path => $path,
578             );
579             }
580              
581             fun _get_argument_values(
582             (HashRef | InstanceOf['GraphQL::Directive']) $def,
583             HashRef $node,
584             Maybe[HashRef] $variable_values = {},
585             ) {
586 39 50   2188   78 my $arg_defs = $def->{args};
  39 50       221  
  39 100       57  
  39 100       127  
  12 100       20  
  11 100       37  
  39         84  
587 167         376 my $arg_nodes = $node->{arguments};
588 167         328 DEBUG and _debug("_get_argument_values", $arg_defs, $arg_nodes, $variable_values, eval { $JSON->encode($node) });
589 167         316 return {} if !$arg_defs;
590 167 50       402 my @bad = grep { !exists $arg_nodes->{$_} and !defined $arg_defs->{$_}{default_value} and $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') } keys %$arg_defs;
591 167 50 100     1182 die GraphQL::Error->new(
  167         1171  
592 167 50       917 message => "Argument '$bad[0]' of type ".
593             "'@{[$arg_defs->{$bad[0]}{type}->to_string]}' not given.",
594 167         715 nodes => [ $node ],
595             ) if @bad;
596             @bad = grep {
597             ref($arg_nodes->{$_}) eq 'SCALAR' and
598 2         30 $variable_values->{${$arg_nodes->{$_}}} and
599 7         21 !_type_will_accept($arg_defs->{$_}{type}, $variable_values->{${$arg_nodes->{$_}}}{type})
600 4         27 } keys %$arg_defs;
601 7 50 66     22 die GraphQL::Error->new(
602 17         32 message => "Variable '\$${$arg_nodes->{$bad[0]}}' of type '@{[$variable_values->{${$arg_nodes->{$bad[0]}}}{type}->to_string]}'".
603 147         412 " where expected '@{[$arg_defs->{$bad[0]}{type}->to_string]}'.",
  41         114  
  41         103  
604 41         92 nodes => [ $node ],
605             ) if @bad;
606             my @novar = grep {
607             ref($arg_nodes->{$_}) eq 'SCALAR' and
608 41         184 (!$variable_values or !exists $variable_values->{${$arg_nodes->{$_}}}) and
609             !defined $arg_defs->{$_}{default_value} and
610             $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull')
611 742   66     1395 } keys %$arg_defs;
      0        
      66        
612             die GraphQL::Error->new(
613 742         1302 message => "Argument '$novar[0]' of type ".
614             "'@{[$arg_defs->{$novar[0]}{type}->to_string]}'".
615 742         1243 " was given variable '\$${$arg_nodes->{$novar[0]}}' but no runtime value.",
616 742         1435 nodes => [ $node ],
617             ) if @novar;
618             my @enumfail = grep {
619             ref($arg_nodes->{$_}) eq 'REF' and
620             ref(${$arg_nodes->{$_}}) eq 'SCALAR' and
621             !$arg_defs->{$_}{type}->isa('GraphQL::Type::Enum') and
622             !($arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') and $arg_defs->{$_}{type}->of->isa('GraphQL::Type::Enum'))
623     100       } keys %$arg_defs;
624             die GraphQL::Error->new(
625             message => "Argument '$enumfail[0]' of type ".
626             "'@{[$arg_defs->{$enumfail[0]}{type}->to_string]}'".
627             " was given ${${$arg_nodes->{$enumfail[0]}}} which is enum value.",
628             nodes => [ $node ],
629             ) if @enumfail;
630             my @enumstring = grep {
631             defined($arg_nodes->{$_}) and
632             !ref($arg_nodes->{$_})
633             } grep $arg_defs->{$_}{type}->isa('GraphQL::Type::Enum'), keys %$arg_defs;
634             die GraphQL::Error->new(
635             message => "Argument '$enumstring[0]' of type ".
636             "'@{[$arg_defs->{$enumstring[0]}{type}->to_string]}'".
637             " was given '$arg_nodes->{$enumstring[0]}' which is not enum value.",
638             nodes => [ $node ],
639             ) if @enumstring;
640             return {} if !$arg_nodes;
641             my %coerced_values;
642             for my $name (keys %$arg_defs) {
643             my $arg_def = $arg_defs->{$name};
644             my $arg_type = $arg_def->{type};
645             my $argument_node = $arg_nodes->{$name};
646             my $default_value = $arg_def->{default_value};
647             DEBUG and _debug("_get_argument_values($name)", $arg_def, $arg_type, $argument_node, $default_value);
648             if (!exists $arg_nodes->{$name}) {
649             # none given - apply type arg's default if any. already validated perl
650             $coerced_values{$name} = $default_value if exists $arg_def->{default_value};
651             next;
652             } elsif (ref($argument_node) eq 'SCALAR') {
653             # scalar ref means it's a variable. already validated perl
654             $coerced_values{$name} =
655             ($variable_values && $variable_values->{$$argument_node} && $variable_values->{$$argument_node}{value})
656             // $default_value;
657             next;
658             } else {
659             # query literal or variable. JSON land, needs convert/validate
660             $coerced_values{$name} = _coerce_value(
661             $argument_node, $variable_values, $default_value
662             );
663             }
664             next if !exists $coerced_values{$name};
665             DEBUG and _debug("_get_argument_values($name after initial)", $arg_def, $arg_type, $argument_node, $default_value, eval { $JSON->encode(\%coerced_values) });
666             eval { $coerced_values{$name} = $arg_type->graphql_to_perl($coerced_values{$name}) };
667             DEBUG and do { local $@; _debug("_get_argument_values($name after coerce)", eval { $JSON->encode(\%coerced_values) }) };
668             if ($@) {
669             my $error = $@;
670             $error =~ s#\s+at.*line\s+\d+\.#.#;
671             # JSON can't encode scalar references
672             my $jsonable = _coerce_for_error($coerced_values{$name});
673             die GraphQL::Error->new(
674             message => "Argument '$name' got invalid value"
675             . " @{[$JSON->encode($jsonable)]}.\nExpected '"
676             . $arg_type->to_string . "'.\n$error",
677             nodes => [ $node ],
678             );
679             }
680             }
681             \%coerced_values;
682             }
683              
684             fun _coerce_for_error(Any $value) {
685       39     my $ref = ref $value;
686             my $ret = 'SCALAR' eq $ref ? $$value
687             : 'ARRAY' eq $ref ? [ map { _coerce_for_error($_) } @$value ]
688             : 'HASH' eq $ref ? { map { $_ => _coerce_for_error($value->{$_}) } keys %$value }
689             : $value
690             ;
691             return $ret;
692             }
693              
694             fun _coerce_value(
695             Any $argument_node,
696             Maybe[HashRef] $variable_values,
697             Any $default_value,
698             ) {
699       167     if (ref($argument_node) eq 'SCALAR') {
700             # scalar ref means it's a variable. already validated perl but
701             # revalidate again as may be in middle of array which would need
702             # validate
703             return
704             ($variable_values && $variable_values->{$$argument_node} && $variable_values->{$$argument_node}{value})
705             // $default_value;
706             } elsif (ref($argument_node) eq 'REF') {
707             # double ref means it's an enum value. JSON land, needs convert/validate
708             return $$$argument_node;
709             } elsif (ref($argument_node) eq 'ARRAY') {
710             # list. recurse
711             return [ map _coerce_value(
712             $_, $variable_values, $default_value
713             ), @$argument_node ];
714             } elsif (ref($argument_node) eq 'HASH') {
715             # hash. recurse
716             return +{ map { $_ => _coerce_value(
717             $argument_node->{$_}, $variable_values, $default_value
718             ) } keys %$argument_node };
719             } else {
720             # query literal. JSON land, needs convert/validate
721             return $argument_node;
722             }
723             }
724              
725             fun _type_will_accept(
726             (ConsumerOf['GraphQL::Role::Input']) $arg_type,
727             (ConsumerOf['GraphQL::Role::Input']) $var_type,
728             ) {
729       41     return 1 if $arg_type == $var_type;
730             $arg_type = $arg_type->of if $arg_type->isa('GraphQL::Type::NonNull');
731             $var_type = $var_type->of if $var_type->isa('GraphQL::Type::NonNull');
732             return 1 if $arg_type == $var_type;
733             return 1 if $arg_type->to_string eq $var_type->to_string;
734             '';
735             }
736              
737             # $root_value is either a hash with fieldnames as keys and either data
738             # or coderefs as values
739             # OR it's just a coderef itself
740             # OR it's an object which gets tried for fieldname as method
741             # any code gets called with obvious args
742             fun _default_field_resolver(
743             CodeLike | HashRef | InstanceOf $root_value,
744             HashRef $args,
745             Any $context,
746             HashRef $info,
747             ) {
748       742     my $field_name = $info->{field_name};
749             my $property = is_HashRef($root_value)
750             ? $root_value->{$field_name}
751             : $root_value;
752             DEBUG and _debug('_default_field_resolver', $root_value, $field_name, $args, $property);
753             if (length(ref $property) and (
754             ref($property) eq 'CODE' or
755             (blessed $property and overload::Method($property, '&{}'))
756             )) {
757             DEBUG and _debug('_default_field_resolver', 'codelike');
758             return $property->($args, $context, $info);
759             }
760             if (length(ref $root_value) and blessed($root_value) and $root_value->can($field_name)) {
761             DEBUG and _debug('_default_field_resolver', 'method');
762             return $root_value->$field_name($args, $context, $info);
763             }
764             $property;
765             }
766              
767             1;