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   24302 use 5.014;
  4578         51249  
4 4577     15   30563 use strict;
  4779         41564  
  5286         43573  
5 5079     15   16162 use warnings;
  5068         19675  
  4624         17402  
6 2836     15   44655 use Types::Standard -all;
  3561         28119  
  1373         21707  
7 1765     15   740918 use Types::TypeTiny -all;
  1702         6935  
  1741         8093  
8 2908     15   21988 use GraphQL::Type::Library -all;
  1536         2936  
  1686         4675  
9 2858     15   220742 use GraphQL::MaybeTypeCheck;
  1495         3128  
  1495         9192  
10 197     15   14380 use GraphQL::Language::Parser qw(parse);
  397         935  
  437         2620  
11 801     15   183479 use GraphQL::Error;
  19         99  
  19         159  
12 786     15   4356 use JSON::MaybeXS;
  256         902  
  23         957  
13 216     15   649 use GraphQL::Debug qw(_debug);
  17         87  
  17         692  
14 33         2012 use GraphQL::Introspection qw(
15             $SCHEMA_META_FIELD_DEF $TYPE_META_FIELD_DEF $TYPE_NAME_META_FIELD_DEF
16 17     15   140 );
  214         1539  
17 214     15   685 use GraphQL::Directive;
  17         88  
  212         594  
18 212     15   441 use GraphQL::Schema qw(lookup_type);
  212         534  
  246         1370  
19 246     15   500 use Exporter 'import';
  246         417  
  246         1592  
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   405 use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false
  246         872  
  64         1842  
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 1359183 ) :ReturnType(ExecutionResult | Promise) {
  361 50       1163  
  361 100       1076  
  361 100       1594  
  168 100       14741  
  157 100       980  
  157 100       558  
  261 100       11222  
  261 100       728  
  261 100       1506  
  261         921  
  17         6669  
163 210         659 my $context = eval {
164 15 100       71 my $ast = ref($doc) ? $doc : parse($doc);
165 99         342 _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         249 DEBUG and _debug('execute', $context, $@);
177 99 100       216 return _build_response(_wrap_error($@)) if $@;
178             my $result = _execute_operation(
179             $context,
180             $context->{operation},
181 99         323 $root_value,
182             );
183 99         779 DEBUG and _debug('execute(result)', $result, $@);
184 99         356 _build_response($result, 1);
185 64     15   67331 }
  55         405  
  55         277  
186              
187             fun _build_response(
188             ExecutionPartialResult | Promise $result,
189             Bool $force_data = 0,
190 15 100   259   78 ) :ReturnType(ExecutionResult | Promise) {
  229 100       837  
  229 100       718  
  229 50       752  
  229 100       1062  
  229         3866  
191 229 100   38   4785 return $result->then(sub { _build_response(@_) }) if is_Promise($result);
  229         1977  
192 229 100       1790 my @errors = @{$result->{errors} || []};
  229         2482  
193             +{
194             $force_data ? (data => undef) : (), # default if none given
195             %$result,
196 229 100       2556 @errors ? (errors => [ map $_->to_json, @{$result->{errors}} ]) : (),
  229 100       2622  
197             };
198 99     15   1367 }
  15         25192  
  15         42  
199              
200             fun _wrap_error(
201             Any $error,
202 229 50   99   900 ) :ReturnType(ExecutionPartialResult) {
  229 50       683  
  227 50       1005  
  226         886  
  224         2217  
203 15 50       14233 return $error if is_ExecutionPartialResult($error);
204 15         36 +{ errors => [ GraphQL::Error->coerce($error) ] };
205 229     15   7726 }
  229         1087  
  18         139  
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   543 ) :ReturnType(HashRef) {
  224 100       678  
  224 100       2516  
  224 50       2133  
  224 50       1550  
  224 50       764  
  60 50       2171  
  223 50       4634  
  1 50       22  
  222 100       2633  
  58         181  
  58         1187  
217             my %fragments = map {
218 58         304 ($_->{name} => $_)
219 58         1341 } grep $_->{kind} eq 'fragment', @$ast;
220 58         140 my @operations = grep $_->{kind} eq 'operation', @$ast;
221 58 100       374 die "No operations supplied.\n" if !@operations;
222 58 100       632 die "Can only execute document containing fragments or operations\n"
223             if @$ast != keys(%fragments) + @operations;
224 14         38 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     59 $variable_values || {},
      100        
      100        
235             ),
236             field_resolver => $field_resolver || \&_default_field_resolver,
237             promise_code => $promise_code,
238             };
239 15     15   77 }
  224         689  
  224         610  
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   811 ) :ReturnType(HashRef) {
  15 50       21451  
  15 50       41  
  799 50       69969  
  982 50       2822  
  982         617  
  982         1886  
251             my @bad = grep {
252 982         2342 ! lookup_type($operation_variables->{$_}, $schema->name2type)->DOES('GraphQL::Role::Input');
  982         3246  
253             } keys %$operation_variables;
254 2357 50       5501 die "Variable '\$$bad[0]' is type '@{[
255 2357         4700 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         4733 my $opvar = $operation_variables->{$_};
  2357         6114  
259 2357         3928 my $opvar_type = lookup_type($opvar, $schema->name2type);
260 2357         6110 my $parsed_value;
261 2356   100     7044 my $maybe_value = $variable_values->{$_} // $opvar->{default_value};
262 2356         7878 eval { $parsed_value = $opvar_type->graphql_to_perl($maybe_value) };
  2356         6194  
263 2356 50       18318 if ($@) {
264 2356         8376 my $error = $@;
265 2338         10720 $error =~ s#\s+at.*line\s+\d+\.#.#;
266             # JSON cannot encode scalar references
267 2180         11576 my $jsonable = _coerce_for_error($maybe_value);
268 2142         10246 die "Variable '\$$_' got invalid value @{[$JSON->canonical->encode($jsonable)]}.\n$error";
  768         1134  
269             }
270 966         6533 ($_ => { value => $parsed_value, type => $opvar_type })
271             } keys %$operation_variables };
272 14     15   63 }
  14         53  
  14         345  
273              
274             fun _get_operation(
275             Maybe[Str] $operation_name,
276             ArrayRef[HashRef] $operations,
277 2471 50   226   5920 ) {
  1880 50       6200  
  226 50       725  
  226 50       693  
  226         573  
  226         685  
278 226         2279 DEBUG and _debug('_get_operation', @_);
279 226 50       3222 if (!$operation_name) {
280 226 50       417 die "Must provide operation name if query contains multiple operations.\n"
281             if @$operations > 1;
282 226         695 return $operations->[0];
283             }
284 214         670 my @matching = grep $_->{name} eq $operation_name, @$operations;
285 213 50       583 return $matching[0] if @matching == 1;
286 12         48 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   67 ) :ReturnType(ExecutionPartialResult | Promise) {
  784 100       1917  
  784 50       1670  
  784 50       1707  
  784 50       1889  
  784         6437  
  784         8928  
294 784   100     5802 my $op_type = $operation->{operationType} || 'query';
295 784         5653 my $type = $context->{schema}->$op_type;
296 15 50       16111 return _wrap_error("No $op_type in schema") if !$type;
297             my ($fields) = $type->_collect_fields(
298             $context,
299             $operation->{selections},
300 15         37 [[], {}],
301             {},
302             );
303 15         72 DEBUG and _debug('_execute_operation(fields)', $fields, $root_value);
304 725         1810 my $path = [];
305 725 50       1467 my $execute = $op_type eq 'mutation'
306             ? \&_execute_fields_serially : \&_execute_fields;
307 725         1497 my $result = eval {
308 725         1890 my $result = $execute->($context, $type, $root_value, $path, $fields);
309 15 50       12935 return $result if !is_Promise($result);
310             $result->then(undef, sub {
311             $context->{promise_code}{resolve}->(
312 15     0   82 +{ data => undef, %{_wrap_error($_[0])} }
  114         314  
313             );
314 15         39 });
315             };
316 114 50       337 return _wrap_error($@) if $@;
317 114         252 $result;
318 850     15   4134 }
  15         16327  
  15         48  
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   1646 ) :ReturnType(ExecutionPartialResult | Promise) {
  114 50       525  
  114 50       193  
  114 50       295  
  114 0       346  
  71 0       11816  
  71 0       376  
  15         12292  
  15         43  
327 15         45 my (%name2executionresult, @errors);
328 30         1582 my $promise_present;
329 15         6197 DEBUG and _debug('_execute_fields', $parent_type->to_string, $fields, $root_value);
330 15         38 my ($field_names, $nodes_defs) = @$fields;
331 15         69 for my $result_name (@$field_names) {
332 2169         4552 my $nodes = $nodes_defs->{$result_name};
333 2169         3918 my $field_node = $nodes->[0];
334 2169         4470 my $field_name = $field_node->{name};
335 2169         5941 my $field_def = _get_field_def($context->{schema}, $parent_type, $field_name);
336 2169         23261 DEBUG and _debug('_execute_fields(resolve)', $parent_type->to_string, $nodes, $root_value, $field_def);
337 2169 0       20146 next if !$field_def;
338 2169   100     30667 my $resolve = $field_def->{resolve} || $context->{field_resolver};
339 2169         5381 my $info = _build_resolve_info(
340             $context,
341             $parent_type,
342             $field_def,
343             [ @$path, $result_name ],
344             $nodes,
345             );
346 2169         5429 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   339 }
  114         1045  
  114         791  
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       12  
  7 50       30  
  7 50       28  
  7 50       23  
  7 50       28  
  7 50       86  
  7         110  
  7         73  
417 7         82 DEBUG and _debug('_execute_fields_serially', $parent_type->to_string, $fields, $root_value);
418             # TODO implement
419 7         439 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     6685 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   12 ) {
  7 50       32  
  2167 50       5476  
  2167 50       4192  
  2167 50       3990  
  2167 50       5448  
  2167 50       20162  
  2167         21871  
  2167         14727  
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         15894 };
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   29081 ) {
  2167 50       17165  
  2167 100       4547  
  2167 50       4392  
  2167 50       4214  
  2167 50       4882  
  2167 50       16019  
  2167 50       14380  
  2167         28091  
  2167         23232  
468 141         414 DEBUG and _debug('_resolve_field_value_or_error', $nodes, $field_def, eval { $JSON->encode($nodes->[0]) });
469 141         351 my $result = eval {
470             my $args = _get_argument_values(
471             $field_def, $nodes->[0], $context->{variable_values},
472 141         311 );
473 141         424 DEBUG and _debug("_resolve_field_value_or_error(args)", $args, eval { $JSON->encode($args) });
474 141         1291 $resolve->($root_value, $args, $context->{context_value}, $info)
475             };
476 141 50       2107 return GraphQL::Error->coerce($@) if $@;
477 141         972 $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   40 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 100       108  
  2522 50       5408  
  2522 50       4601  
  2522 50       5498  
  2522 50       6522  
  2522 50       20531  
  2522 50       26170  
  2522         35674  
  2522         16796  
488 2522         17372 DEBUG and _debug('_complete_value_catching_error(before)', $return_type->to_string, $result);
489 2522 50       15755 if ($return_type->isa('GraphQL::Type::NonNull')) {
490 2522         3364 return _complete_value_with_located_error(@_);
491             }
492 2522         8816 my $result = eval {
493 1097         2561 my $c = _complete_value_with_located_error(@_);
494 1425 50       2381 return $c if !is_Promise($c);
495             $c->then(undef, sub {
496 1384     33   2817 $context->{promise_code}{resolve}->(_wrap_error(@_))
497 1425         3155 });
498             };
499 98         1210 DEBUG and _debug("_complete_value_catching_error(after)(@{[$return_type->to_string]})", $return_type->to_string, $result, $@);
500 33 100       2599 return _wrap_error($@) if $@;
501 1425         18812 $result;
502 0     15   0 }
  1902         43077  
  15         53428  
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   43 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 50       103  
  2522 50       5061  
  2522 50       4753  
  2522 50       4903  
  2522 50       5251  
  2522 50       17691  
  2522 50       21180  
  2522         33856  
  2522         16381  
512 2522         16302 my $result = eval {
513 2522         14700 my $c = _complete_value(@_);
514 2522 50       3624 return $c if !is_Promise($c);
515             $c->then(undef, sub {
516 2461     80   13681 $context->{promise_code}{reject}->(
517             _located_error($_[0], $nodes, $path)
518             )
519 2522         5181 });
520             };
521 164         2150 DEBUG and _debug('_complete_value_with_located_error(after)', $return_type->to_string, $result, $@);
522 80 100       6257 die _located_error($@, $nodes, $path) if $@;
523 2522         38726 $result;
524 1425     15   2627 }
  1384         2695  
  15         16644  
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   43 ) :ReturnType(ExecutionPartialResult | Promise) {
  15 100       79  
  3686 100       7286  
  3686 100       6682  
  3686 50       6761  
  3686 50       7085  
  3686 50       24775  
  3686 50       30624  
  3686         47635  
  3686         24053  
534 3686         23986 DEBUG and _debug('_complete_value', $return_type->to_string, $path, $result);
535 3686 100       21311 if (is_Promise($result)) {
536 3686         4366 my @outerargs = @_[0..4];
537 3686     79   8086 return $result->then(sub { _complete_value(@outerargs, $_[0]) });
  98         1247  
538             }
539 98 100       700 die $result if GraphQL::Error->is($result);
540 79 50       7573 if ($return_type->isa('GraphQL::Type::NonNull')) {
541 3588         25206 my $completed = _complete_value(
542             $context,
543             $return_type->of,
544             $nodes,
545             $info,
546             $path,
547             $result,
548             );
549 3556         11862 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         7136 "Cannot return null for non-nullable field @{[$info->{parent_type}->name]}.@{[$info->{field_name}]}."
  1077         2471  
554 1085 100 100     3267 ) if !is_Promise($completed) and !defined $completed->{data};
555 17         246 return $completed;
556             }
557 17 50       100 return { data => undef } if !defined $result;
558 1060         8434 $return_type->_complete_value(
559             $context,
560             $nodes,
561             $info,
562             $path,
563             $result,
564             );
565 2522     15   4585 }
  2461         5973  
  15         18573  
566              
567             fun _located_error(
568             Any $error,
569             ArrayRef[HashRef] $nodes,
570             ArrayRef $path,
571 141 100   141   223 ) {
  141 100       411  
  141 50       10226  
  76 50       239  
  2188 50       4440  
  2188         4105  
  2188         5445  
572 2188         5085 DEBUG and _debug('_located_error', $error);
573 39         110 $error = GraphQL::Error->coerce($error);
574 39 50       96 return $error if $error->locations;
575             GraphQL::Error->coerce($error)->but(
576 39         82 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   99 ) {
  39 50       271  
  39 100       76  
  39 100       197  
  12 100       24  
  11 100       34  
  39         108  
586 167         441 my $arg_defs = $def->{args};
587 167         417 my $arg_nodes = $node->{arguments};
588 167         497 DEBUG and _debug("_get_argument_values", $arg_defs, $arg_nodes, $variable_values, eval { $JSON->encode($node) });
589 167 50       465 return {} if !$arg_defs;
590 167 50 100     1415 my @bad = grep { !exists $arg_nodes->{$_} and !defined $arg_defs->{$_}{default_value} and $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') } keys %$arg_defs;
  167         1410  
591 167 50       1155 die GraphQL::Error->new(
592             message => "Argument '$bad[0]' of type ".
593 167         799 "'@{[$arg_defs->{$bad[0]}{type}->to_string]}' not given.",
594             nodes => [ $node ],
595             ) if @bad;
596             @bad = grep {
597 2         27 ref($arg_nodes->{$_}) eq 'SCALAR' and
598 7         39 $variable_values->{${$arg_nodes->{$_}}} and
599 4         21 !_type_will_accept($arg_defs->{$_}{type}, $variable_values->{${$arg_nodes->{$_}}}{type})
600 7 50 66     29 } keys %$arg_defs;
601 17         49 die GraphQL::Error->new(
602 147         488 message => "Variable '\$${$arg_nodes->{$bad[0]}}' of type '@{[$variable_values->{${$arg_nodes->{$bad[0]}}}{type}->to_string]}'".
  41         141  
  41         106  
603 41         104 " where expected '@{[$arg_defs->{$bad[0]}{type}->to_string]}'.",
604             nodes => [ $node ],
605             ) if @bad;
606             my @novar = grep {
607 41         195 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     1585 $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull')
      0        
      66        
611             } keys %$arg_defs;
612 742         1405 die GraphQL::Error->new(
613             message => "Argument '$novar[0]' of type ".
614 742         1404 "'@{[$arg_defs->{$novar[0]}{type}->to_string]}'".
615 742         1638 " 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;