File Coverage

blib/lib/GraphQL/Execution.pm
Criterion Covered Total %
statement 360 419 85.9
branch 194 326 59.5
condition 38 45 84.4
subroutine 58 59 98.3
pod 1 1 100.0
total 651 850 76.5


line stmt bran cond sub pod time code
1             package GraphQL::Execution;
2              
3 15     15   23679 use 5.014;
  4578         47807  
4 4577     15   26465 use strict;
  4779         39190  
  5286         41603  
5 5079     15   15557 use warnings;
  5068         18358  
  4624         16079  
6 2836     15   40756 use Types::Standard -all;
  3561         26147  
  1373         19418  
7 1765     15   710720 use Types::TypeTiny -all;
  1702         6489  
  1741         7861  
8 2908     15   20809 use GraphQL::Type::Library -all;
  1536         2795  
  1686         4260  
9 2858     15   208606 use GraphQL::MaybeTypeCheck;
  1495         2718  
  1495         9225  
10 197     15   14087 use GraphQL::Language::Parser qw(parse);
  397         853  
  437         2618  
11 801     15   178093 use GraphQL::Error;
  19         115  
  19         203  
12 786     15   4167 use JSON::MaybeXS;
  256         940  
  23         972  
13 216     15   649 use GraphQL::Debug qw(_debug);
  17         87  
  17         702  
14 33         1918 use GraphQL::Introspection qw(
15             $SCHEMA_META_FIELD_DEF $TYPE_META_FIELD_DEF $TYPE_NAME_META_FIELD_DEF
16 17     15   134 );
  214         1543  
17 214     15   607 use GraphQL::Directive;
  17         77  
  212         542  
18 212     15   375 use GraphQL::Schema qw(lookup_type);
  212         517  
  246         1428  
19 246     15   425 use Exporter 'import';
  246         456  
  246         1506  
20              
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 246     15   380 use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false
  246         765  
  64         1834  
34              
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 361 100   219 1 1358911 ) :ReturnType(ExecutionResult | Promise) {
  361 50       1190  
  361 100       959  
  361 100       1551  
  168 100       14791  
  157 100       916  
  157 100       464  
  261 100       10973  
  261 100       647  
  261 100       1444  
  261         881  
  17         6570  
163 210         590 my $context = eval {
164 15 100       73 my $ast = ref($doc) ? $doc : parse($doc);
165 99         328 _build_context(
166             $schema,
167             $ast,
168             $root_value,
169             $context_value,
170             $variable_values,
171             $operation_name,
172             $field_resolver,
173             $promise_code,
174             );
175             };
176 99         256 DEBUG and _debug('execute', $context, $@);
177 99 100       231 return _build_response(_wrap_error($@)) if $@;
178             my $result = _execute_operation(
179             $context,
180             $context->{operation},
181 99         379 $root_value,
182             );
183 99         785 DEBUG and _debug('execute(result)', $result, $@);
184 99         357 _build_response($result, 1);
185 64     15   67211 }
  55         354  
  55         252  
186              
187             fun _build_response(
188             ExecutionPartialResult | Promise $result,
189             Bool $force_data = 0,
190 15 100   259   72 ) :ReturnType(ExecutionResult | Promise) {
  229 100       763  
  229 100       655  
  229 50       761  
  229 100       1082  
  229         4022  
191 229 100   38   4322 return $result->then(sub { _build_response(@_) }) if is_Promise($result);
  229         1746  
192 229 100       1755 my @errors = @{$result->{errors} || []};
  229         2155  
193             +{
194             $force_data ? (data => undef) : (), # default if none given
195             %$result,
196 229 100       2303 @errors ? (errors => [ map $_->to_json, @{$result->{errors}} ]) : (),
  229 100       2391  
197             };
198 99     15   1398 }
  15         24831  
  15         34  
199              
200             fun _wrap_error(
201             Any $error,
202 229 50   99   820 ) :ReturnType(ExecutionPartialResult) {
  229 50       702  
  227 50       903  
  226         813  
  224         2041  
203 15 50       13890 return $error if is_ExecutionPartialResult($error);
204 15         39 +{ errors => [ GraphQL::Error->coerce($error) ] };
205 229     15   7156 }
  229         1066  
  18         79  
206              
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 224 50   229   556 ) :ReturnType(HashRef) {
  224 100       681  
  224 100       2342  
  224 50       1928  
  224 50       1398  
  224 50       694  
  60 50       2023  
  223 50       4291  
  1 50       20  
  222 100       2465  
  58         180  
  58         1105  
217             my %fragments = map {
218 58         295 ($_->{name} => $_)
219 58         1174 } grep $_->{kind} eq 'fragment', @$ast;
220 58         126 my @operations = grep $_->{kind} eq 'operation', @$ast;
221 58 100       376 die "No operations supplied.\n" if !@operations;
222 58 100       593 die "Can only execute document containing fragments or operations\n"
223             if @$ast != keys(%fragments) + @operations;
224 14         46 my $operation = _get_operation($operation_name, \@operations);
225             {
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 14   100     64 $variable_values || {},
      100        
      100        
235             ),
236             field_resolver => $field_resolver || \&_default_field_resolver,
237             promise_code => $promise_code,
238             };
239 15     15   72 }
  224         623  
  224         583  
240              
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 44 50   224   765 ) :ReturnType(HashRef) {
  15 50       21406  
  15 50       37  
  799 50       61544  
  982 50       2585  
  982         498  
  982         1445  
251             my @bad = grep {
252 982         2102 ! lookup_type($operation_variables->{$_}, $schema->name2type)->DOES('GraphQL::Role::Input');
  982         3068  
253             } keys %$operation_variables;
254 2357 50       5055 die "Variable '\$$bad[0]' is type '@{[
255 2357         4361 lookup_type($operation_variables->{$bad[0]}, $schema->name2type)->to_string
256             ]}' which cannot be used as an input type.\n" if @bad;
257             +{ map {
258 2357         4451 my $opvar = $operation_variables->{$_};
  2357         5931  
259 2357         3358 my $opvar_type = lookup_type($opvar, $schema->name2type);
260 2357         5847 my $parsed_value;
261 2356   100     6577 my $maybe_value = $variable_values->{$_} // $opvar->{default_value};
262 2356         7264 eval { $parsed_value = $opvar_type->graphql_to_perl($maybe_value) };
  2356         5560  
263 2356 50       17882 if ($@) {
264 2356         7750 my $error = $@;
265 2338         9647 $error =~ s#\s+at.*line\s+\d+\.#.#;
266             # JSON cannot encode scalar references
267 2180         10046 my $jsonable = _coerce_for_error($maybe_value);
268 2142         9865 die "Variable '\$$_' got invalid value @{[$JSON->canonical->encode($jsonable)]}.\n$error";
  768         1168  
269             }
270 966         6430 ($_ => { value => $parsed_value, type => $opvar_type })
271             } keys %$operation_variables };
272 14     15   97 }
  14         60  
  14         349  
273              
274             fun _get_operation(
275             Maybe[Str] $operation_name,
276             ArrayRef[HashRef] $operations,
277 2471 50   226   5412 ) {
  1880 50       6631  
  226 50       615  
  226 50       611  
  226         557  
  226         615  
278 226         2102 DEBUG and _debug('_get_operation', @_);
279 226 50       3242 if (!$operation_name) {
280 226 50       376 die "Must provide operation name if query contains multiple operations.\n"
281             if @$operations > 1;
282 226         615 return $operations->[0];
283             }
284 214         631 my @matching = grep $_->{name} eq $operation_name, @$operations;
285 213 50       558 return $matching[0] if @matching == 1;
286 12         58 die "No operations matching '$operation_name' found.\n";
287             }
288              
289             fun _execute_operation(
290             HashRef $context,
291             HashRef $operation,
292             Any $root_value,
293 15 100   198   71 ) :ReturnType(ExecutionPartialResult | Promise) {
  784 100       1931  
  784 50       1635  
  784 50       1608  
  784 50       1824  
  784         6030  
  784         8159  
294 784   100     5443 my $op_type = $operation->{operationType} || 'query';
295 784         5204 my $type = $context->{schema}->$op_type;
296 15 50       15756 return _wrap_error("No $op_type in schema") if !$type;
297             my ($fields) = $type->_collect_fields(
298             $context,
299             $operation->{selections},
300 15         58 [[], {}],
301             {},
302             );
303 15         71 DEBUG and _debug('_execute_operation(fields)', $fields, $root_value);
304 725         1818 my $path = [];
305 725 50       1649 my $execute = $op_type eq 'mutation'
306             ? \&_execute_fields_serially : \&_execute_fields;
307 725         1458 my $result = eval {
308 725         2121 my $result = $execute->($context, $type, $root_value, $path, $fields);
309 15 50       12241 return $result if !is_Promise($result);
310             $result->then(undef, sub {
311             $context->{promise_code}{resolve}->(
312 15     0   98 +{ data => undef, %{_wrap_error($_[0])} }
  114         327  
313             );
314 15         42 });
315             };
316 114 50       271 return _wrap_error($@) if $@;
317 114         222 $result;
318 850     15   4156 }
  15         15806  
  15         43  
319              
320             fun _execute_fields(
321             HashRef $context,
322             (InstanceOf['GraphQL::Type']) $parent_type,
323             Any $root_value,
324             ArrayRef $path,
325             FieldsGot $fields,
326 114 50   784   1864 ) :ReturnType(ExecutionPartialResult | Promise) {
  114 50       547  
  114 50       204  
  114 50       315  
  114 0       377  
  71 0       13007  
  71 0       437  
  15         11829  
  15         34  
327 15         52 my (%name2executionresult, @errors);
328 30         1573 my $promise_present;
329 15         6221 DEBUG and _debug('_execute_fields', $parent_type->to_string, $fields, $root_value);
330 15         46 my ($field_names, $nodes_defs) = @$fields;
331 15         76 for my $result_name (@$field_names) {
332 2169         4063 my $nodes = $nodes_defs->{$result_name};
333 2169         3684 my $field_node = $nodes->[0];
334 2169         3818 my $field_name = $field_node->{name};
335 2169         5371 my $field_def = _get_field_def($context->{schema}, $parent_type, $field_name);
336 2169         21464 DEBUG and _debug('_execute_fields(resolve)', $parent_type->to_string, $nodes, $root_value, $field_def);
337 2169 0       17472 next if !$field_def;
338 2169   100     27318 my $resolve = $field_def->{resolve} || $context->{field_resolver};
339 2169         5121 my $info = _build_resolve_info(
340             $context,
341             $parent_type,
342             $field_def,
343             [ @$path, $result_name ],
344             $nodes,
345             );
346 2169         4947 my $result = _resolve_field_value_or_error(
347             $context,
348             $field_def,
349             $nodes,
350             $resolve,
351             $root_value,
352             $info,
353             );
354 0         0 DEBUG and _debug('_execute_fields(resolved)', $parent_type->to_string, $result);
355             $result = _complete_value_catching_error(
356             $context,
357             $field_def->{type},
358 0         0 $nodes,
359             $info,
360             [ @$path, $result_name ],
361             $result,
362             );
363 0   66     0 $promise_present ||= is_Promise($result);
364 0         0 DEBUG and _debug("_execute_fields(complete)($result_name)", $result);
365 0         0 $name2executionresult{$result_name} = $result;
366             }
367 0         0 DEBUG and _debug('_execute_fields(done)', \%name2executionresult, \@errors, $promise_present);
368 0 0       0 return _promise_for_hash($context, \%name2executionresult, \@errors)
369             if $promise_present;
370 0         0 _merge_hash(
371             [ keys %name2executionresult ],
372             [ values %name2executionresult ],
373             \@errors,
374             );
375 114     15   439 }
  114         1135  
  114         838  
376              
377             fun _merge_hash(
378             ArrayRef[Str] $keys,
379             ArrayRef[ExecutionPartialResult] $values,
380             (ArrayRef[InstanceOf['GraphQL::Error']]) $errors,
381 0 0   725   0 ) :ReturnType(ExecutionPartialResult) {
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
382 0         0 DEBUG and _debug('_merge_hash', $keys, $values, $errors);
383 0 0       0 my @errors = (@$errors, map @{$_->{errors} || []}, @$values);
  0         0  
384 0         0 my %name2data;
385 0         0 for (my $i = @$values - 1; $i >= 0; $i--) {
386 0         0 $name2data{$keys->[$i]} = $values->[$i]{data};
387             }
388 0         0 DEBUG and _debug('_merge_hash(after)', \%name2data, \@errors);
389             +{
390 0 0       0 %name2data ? (data => \%name2data) : (),
    0          
391             @errors ? (errors => \@errors) : ()
392             };
393 0     15   0 }
  0         0  
  0         0  
394              
395             fun _promise_for_hash(
396             HashRef $context,
397             HashRef $hash,
398             (ArrayRef[InstanceOf['GraphQL::Error']]) $errors,
399 0 0   114   0 ) :ReturnType(Promise) {
  0 0       0  
  0 0       0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
400 0         0 my ($keys, $values) = ([ keys %$hash ], [ values %$hash ]);
401 0         0 DEBUG and _debug('_promise_for_hash', $keys);
402             die "Given a promise in object but no PromiseCode given\n"
403 0 50       0 if !$context->{promise_code};
404             $context->{promise_code}{all}->(@$values)->then(sub {
405 0     71   0 DEBUG and _debug('_promise_for_hash(all)', \@_);
406 0         0 _merge_hash($keys, [ map $_->[0], @_ ], $errors);
407 0         0 });
408 0     15   0 }
  0         0  
  0         0  
409              
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 12 50   7   48 ) {
  1 50       11  
  7 50       28  
  7 50       31  
  7 50       21  
  7 50       28  
  7 50       77  
  7         139  
  7         72  
417 7         62 DEBUG and _debug('_execute_fields_serially', $parent_type->to_string, $fields, $root_value);
418             # TODO implement
419 7         405 goto &_execute_fields;
420             }
421              
422             use constant FIELDNAME2SPECIAL => {
423 0         0 map { ($_->{name} => $_) } $SCHEMA_META_FIELD_DEF, $TYPE_META_FIELD_DEF
  0         0  
424 0     15   0 };
  0         0  
425             fun _get_field_def(
426             (InstanceOf['GraphQL::Schema']) $schema,
427             (InstanceOf['GraphQL::Type']) $parent_type,
428             StrNameValid $field_name,
429 0 50   2169   0 ) :ReturnType(Maybe[HashRef]) {
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
430             return $TYPE_NAME_META_FIELD_DEF
431 0 50       0 if $field_name eq $TYPE_NAME_META_FIELD_DEF->{name};
432             return FIELDNAME2SPECIAL->{$field_name}
433 254 100 100     6494 if FIELDNAME2SPECIAL->{$field_name} and $parent_type == $schema->query;
434 0         0 $parent_type->fields->{$field_name};
435 0     15   0 }
  0         0  
  0         0  
436              
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 7 50   2167   14 ) {
  7 50       31  
  2167 50       4203  
  2167 50       3711  
  2167 50       3456  
  2167 50       5520  
  2167 50       16863  
  2167         19068  
  2167         12881  
445             {
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 2167         13366 };
458             }
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 2167 50   2167   25540 ) {
  2167 50       15877  
  2167 100       4132  
  2167 50       3603  
  2167 50       3723  
  2167 50       4204  
  2167 50       13560  
  2167 50       12103  
  2167         23808  
  2167         21160  
468 141         434 DEBUG and _debug('_resolve_field_value_or_error', $nodes, $field_def, eval { $JSON->encode($nodes->[0]) });
469 141         332 my $result = eval {
470             my $args = _get_argument_values(
471             $field_def, $nodes->[0], $context->{variable_values},
472 141         289 );
473 141         439 DEBUG and _debug("_resolve_field_value_or_error(args)", $args, eval { $JSON->encode($args) });
474 141         1220 $resolve->($root_value, $args, $context->{context_value}, $info)
475             };
476 141 50       2085 return GraphQL::Error->coerce($@) if $@;
477 141         997 $result;
478             }
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 15 100   2522   43 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 100       94  
  2522 50       5088  
  2522 50       4338  
  2522 50       4842  
  2522 50       5668  
  2522 50       17890  
  2522 50       23981  
  2522         30433  
  2522         14576  
488 2522         15253 DEBUG and _debug('_complete_value_catching_error(before)', $return_type->to_string, $result);
489 2522 50       13920 if ($return_type->isa('GraphQL::Type::NonNull')) {
490 2522         2866 return _complete_value_with_located_error(@_);
491             }
492 2522         8027 my $result = eval {
493 1097         2427 my $c = _complete_value_with_located_error(@_);
494 1425 50       2259 return $c if !is_Promise($c);
495             $c->then(undef, sub {
496 1384     33   2638 $context->{promise_code}{resolve}->(_wrap_error(@_))
497 1425         2858 });
498             };
499 98         1364 DEBUG and _debug("_complete_value_catching_error(after)(@{[$return_type->to_string]})", $return_type->to_string, $result, $@);
500 33 100       2636 return _wrap_error($@) if $@;
501 1425         18385 $result;
502 0     15   0 }
  1902         38157  
  15         52786  
503              
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 15 100   2522   40 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 50       72  
  2522 50       4275  
  2522 50       4152  
  2522 50       4167  
  2522 50       4430  
  2522 50       15438  
  2522 50       17944  
  2522         27874  
  2522         14881  
512 2522         14176 my $result = eval {
513 2522         12710 my $c = _complete_value(@_);
514 2522 50       3074 return $c if !is_Promise($c);
515             $c->then(undef, sub {
516 2461     80   13525 $context->{promise_code}{reject}->(
517             _located_error($_[0], $nodes, $path)
518             )
519 2522         4728 });
520             };
521 164         2439 DEBUG and _debug('_complete_value_with_located_error(after)', $return_type->to_string, $result, $@);
522 80 100       6595 die _located_error($@, $nodes, $path) if $@;
523 2522         36264 $result;
524 1425     15   2369 }
  1384         2529  
  15         16522  
525              
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 15 100   3686   37 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 100       71  
  3686 100       6270  
  3686 100       6162  
  3686 50       6047  
  3686 50       6242  
  3686 50       21882  
  3686 50       26834  
  3686         40218  
  3686         20663  
534 3686         20396 DEBUG and _debug('_complete_value', $return_type->to_string, $path, $result);
535 3686 100       18852 if (is_Promise($result)) {
536 3686         3845 my @outerargs = @_[0..4];
537 3686     79   7964 return $result->then(sub { _complete_value(@outerargs, $_[0]) });
  98         1319  
538             }
539 98 100       615 die $result if GraphQL::Error->is($result);
540 79 50       8028 if ($return_type->isa('GraphQL::Type::NonNull')) {
541 3588         22435 my $completed = _complete_value(
542             $context,
543             $return_type->of,
544             $nodes,
545             $info,
546             $path,
547             $result,
548             );
549 3556         10637 DEBUG and _debug('_complete_value(NonNull)', $return_type->to_string, $completed);
550             # 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 1077         7133 "Cannot return null for non-nullable field @{[$info->{parent_type}->name]}.@{[$info->{field_name}]}."
  1077         2414  
554 1085 100 100     3440 ) if !is_Promise($completed) and !defined $completed->{data};
555 17         269 return $completed;
556             }
557 17 50       116 return { data => undef } if !defined $result;
558 1060         8192 $return_type->_complete_value(
559             $context,
560             $nodes,
561             $info,
562             $path,
563             $result,
564             );
565 2522     15   4478 }
  2461         5666  
  15         17837  
566              
567             fun _located_error(
568             Any $error,
569             ArrayRef[HashRef] $nodes,
570             ArrayRef $path,
571 141 100   141   211 ) {
  141 100       452  
  141 50       11146  
  76 50       223  
  2188 50       4084  
  2188         3473  
  2188         4675  
572 2188         4657 DEBUG and _debug('_located_error', $error);
573 39         105 $error = GraphQL::Error->coerce($error);
574 39 50       108 return $error if $error->locations;
575             GraphQL::Error->coerce($error)->but(
576 39         81 locations => [ map $_->{location}, @$nodes ],
577             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 39 50   2188   96 ) {
  39 50       250  
  39 100       72  
  39 100       160  
  12 100       22  
  11 100       40  
  39         110  
586 167         463 my $arg_defs = $def->{args};
587 167         383 my $arg_nodes = $node->{arguments};
588 167         468 DEBUG and _debug("_get_argument_values", $arg_defs, $arg_nodes, $variable_values, eval { $JSON->encode($node) });
589 167 50       412 return {} if !$arg_defs;
590 167 50 100     1216 my @bad = grep { !exists $arg_nodes->{$_} and !defined $arg_defs->{$_}{default_value} and $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') } keys %$arg_defs;
  167         1171  
591 167 50       962 die GraphQL::Error->new(
592             message => "Argument '$bad[0]' of type ".
593 167         780 "'@{[$arg_defs->{$bad[0]}{type}->to_string]}' not given.",
594             nodes => [ $node ],
595             ) if @bad;
596             @bad = grep {
597 2         39 ref($arg_nodes->{$_}) eq 'SCALAR' and
598 7         29 $variable_values->{${$arg_nodes->{$_}}} and
599 4         27 !_type_will_accept($arg_defs->{$_}{type}, $variable_values->{${$arg_nodes->{$_}}}{type})
600 7 50 66     24 } keys %$arg_defs;
601 17         44 die GraphQL::Error->new(
602 147         433 message => "Variable '\$${$arg_nodes->{$bad[0]}}' of type '@{[$variable_values->{${$arg_nodes->{$bad[0]}}}{type}->to_string]}'".
  41         103  
  41         106  
603 41         87 " where expected '@{[$arg_defs->{$bad[0]}{type}->to_string]}'.",
604             nodes => [ $node ],
605             ) if @bad;
606             my @novar = grep {
607 41         183 ref($arg_nodes->{$_}) eq 'SCALAR' and
608             (!$variable_values or !exists $variable_values->{${$arg_nodes->{$_}}}) and
609             !defined $arg_defs->{$_}{default_value} and
610 742   66     1609 $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull')
      0        
      66        
611             } keys %$arg_defs;
612 742         1402 die GraphQL::Error->new(
613             message => "Argument '$novar[0]' of type ".
614 742         1475 "'@{[$arg_defs->{$novar[0]}{type}->to_string]}'".
615 742         1543 " was given variable '\$${$arg_nodes->{$novar[0]}}' but no runtime value.",
616             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     100       !($arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') and $arg_defs->{$_}{type}->of->isa('GraphQL::Type::Enum'))
623             } 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       39     fun _coerce_for_error(Any $value) {
685             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       167     ) {
699             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       41     ) {
729             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       742     ) {
748             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 (eval { CodeLike->($property); 1 }) {
754             DEBUG and _debug('_default_field_resolver', 'codelike');
755             return $property->($args, $context, $info);
756             }
757             if (is_InstanceOf($root_value) and $root_value->can($field_name)) {
758             DEBUG and _debug('_default_field_resolver', 'method');
759             return $root_value->$field_name($args, $context, $info);
760             }
761             $property;
762             }
763              
764             1;