File Coverage

blib/lib/GraphQL/Type/Object.pm
Criterion Covered Total %
statement 114 124 91.9
branch 49 88 55.6
condition 11 15 73.3
subroutine 20 22 90.9
pod 2 2 100.0
total 196 251 78.0


line stmt bran cond sub pod time code
1             package GraphQL::Type::Object;
2              
3 17     17   4558 use 5.014;
  17         66  
4 17     17   89 use strict;
  17         35  
  17         350  
5 17     17   86 use warnings;
  17         33  
  17         408  
6 17     17   91 use Moo;
  17         35  
  17         131  
7 983     17   24248 use GraphQL::Debug qw(_debug);
  983         1407  
  983         2909  
8 2381     17   3770 use Types::Standard -all;
  2381         4686  
  2371         5818  
9 2190     17   809945 use GraphQL::Type::Library -all;
  2190         4155  
  2190         6408  
10 2190     17   243505 use MooX::Thunking;
  2190         8986  
  2190         7556  
11 42     17   2715 use GraphQL::MaybeTypeCheck;
  32         121  
  173         449  
12             extends qw(GraphQL::Type);
13             with qw(
14             GraphQL::Role::Output
15             GraphQL::Role::Composite
16             GraphQL::Role::Nullable
17             GraphQL::Role::Named
18             GraphQL::Role::FieldsOutput
19             GraphQL::Role::HashMappable
20             );
21              
22             our $VERSION = '0.02';
23 173     17   425 use constant DEBUG => $ENV{GRAPHQL_DEBUG};
  171         436  
  171         2381  
24              
25             =head1 NAME
26              
27             GraphQL::Type::Object - GraphQL object type
28              
29             =head1 SYNOPSIS
30              
31             use GraphQL::Type::Object;
32             my $interface_type;
33             my $implementing_type = GraphQL::Type::Object->new(
34             name => 'Object',
35             interfaces => [ $interface_type ],
36             fields => { field_name => { type => $scalar_type, resolve => sub { '' } }},
37             );
38              
39             =head1 ATTRIBUTES
40              
41             Has C<name>, C<description> from L<GraphQL::Role::Named>.
42             Has C<fields> from L<GraphQL::Role::FieldsOutput>.
43              
44             =head2 interfaces
45              
46             Optional, thunked array-ref of interface type objects implemented.
47              
48             =cut
49              
50             has interfaces => (is => 'thunked', isa => ArrayRef[InstanceOf['GraphQL::Type::Interface']]);
51              
52             =head2 is_type_of
53              
54             Optional code-ref. Input is a value, an execution context hash-ref,
55             and resolve-info hash-ref.
56              
57             =cut
58              
59             has is_type_of => (is => 'ro', isa => CodeRef);
60              
61 154 0   0 1 384 method graphql_to_perl(Maybe[HashRef] $item) :ReturnType(Maybe[HashRef]) {
  966 0       3663  
  0 50       0  
  0         0  
  0         0  
  17         14591  
62 17 50       43 return $item if !defined $item;
63 17         88 $item = $self->uplift($item);
64 69         182 my $fields = $self->fields;
65             $self->hashmap($item, $fields, sub {
66 69     0   129 my ($key, $value) = @_;
67             $fields->{$key}{type}->graphql_to_perl(
68             $value // $fields->{$key}{default_value}
69 69   33     128 );
70 69         161 });
71 171     17   7522 }
  171         394  
  171         353  
72              
73             has to_doc => (is => 'lazy', isa => Str);
74             sub _build_to_doc {
75 35     35   1220 my ($self) = @_;
76 35         147 DEBUG and _debug('Object.to_doc', $self);
77             my @fieldlines = map {
78 35         984 my ($main, @description) = @$_;
  966         2126  
79             (
80 966         2022 @description,
81             $main,
82             )
83             } $self->_make_fieldtuples($self->fields);
84 966 50       1538 my $implements = join ' & ', map $_->name, @{ $self->interfaces || [] };
  966         1871  
85 966   33     2106 $implements &&= 'implements ' . $implements . ' ';
86 966 50       6955 join '', map "$_\n",
87             $self->_description_doc_lines($self->description),
88 966         7066 "type @{[$self->name]} $implements\{",
89             (map length() ? " $_" : "", @fieldlines),
90             "}";
91             }
92              
93             method from_ast(
94             HashRef $name2type,
95             HashRef $ast_node,
96 69 50   69 1 267 ) :ReturnType(InstanceOf[__PACKAGE__]) {
  17 100       46486  
  17 100       44  
  17 100       418  
  179         375  
  179         383  
  179         257  
97 179         313 $self->new(
98             $self->_from_ast_named($ast_node),
99             $self->_from_ast_maptype($name2type, $ast_node, 'interfaces'),
100             $self->_from_ast_fields($name2type, $ast_node, 'fields'),
101             );
102 69     17   201 }
  69         726  
  69         477  
103              
104             method _collect_fields(
105             HashRef $context,
106             ArrayRef $selections,
107             FieldsGot $fields_got,
108             Map[StrNameValid,Bool] $visited_fragments,
109 966 50   966   33216 ) {
  587 50       1342  
  587 50       1208  
  587 50       956  
  587 50       1119  
  587 50       1617  
  587         4243  
  587         7863  
  587         4024  
110 587         4010 DEBUG and _debug('_collect_fields', $self->to_string, $fields_got, $selections);
111 587         3497 for my $selection (@$selections) {
112 587         1694 my $node = $selection;
113 6 50       24 next if !_should_include_node($context->{variable_values}, $node);
114 6 0       44 if ($selection->{kind} eq 'field') {
    0          
    0          
115 1         26 my $use_name = $node->{alias} || $node->{name};
116 1         57 my ($field_names, $nodes_defs) = @$fields_got;
117 586 50       1400 $field_names = [ @$field_names, $use_name ] if !exists $nodes_defs->{$use_name};
118             $nodes_defs = {
119             %$nodes_defs,
120 586 50       980 $use_name => [ @{$nodes_defs->{$use_name} || []}, $node ],
  586         1922  
121             };
122 589         1608 $fields_got = [ $field_names, $nodes_defs ]; # no mutation
123             } elsif ($selection->{kind} eq 'inline_fragment') {
124 586 50       886 next if !$self->_fragment_condition_match($context, $node);
125             ($fields_got, $visited_fragments) = $self->_collect_fields(
126             $context,
127             $node->{selections},
128 586         1998 $fields_got,
129             $visited_fragments,
130             );
131             } elsif ($selection->{kind} eq 'fragment_spread') {
132 0           my $frag_name = $node->{name};
133 0 100         next if $visited_fragments->{$frag_name};
134 0           $visited_fragments = { %$visited_fragments, $frag_name => 1 }; # !mutate
135 0           my $fragment = $context->{fragments}{$frag_name};
136 0 100         next if !$fragment;
137 0 0         next if !$self->_fragment_condition_match($context, $fragment);
138             DEBUG and _debug('_collect_fields(fragment_spread)', $fragment);
139             ($fields_got, $visited_fragments) = $self->_collect_fields(
140             $context,
141             $fragment->{selections},
142             $fields_got,
143             $visited_fragments,
144             );
145             }
146             }
147             ($fields_got, $visited_fragments);
148             }
149              
150             method _fragment_condition_match(
151             HashRef $context,
152             HashRef $node,
153 179 100   179   231 ) :ReturnType(Bool) {
  179 100       467  
  177 100       742  
  10 100       250  
  10         1555  
  0         0  
  17         10494  
154 17         50 DEBUG and _debug('_fragment_condition_match', $self->to_string, $node);
155 17 100       72 return 1 if !$node->{on};
156 2364 100       4417 return 1 if $node->{on} eq $self->name;
157             my $condition_type = $context->{schema}->name2type->{$node->{on}} //
158 2364   100     4309 die GraphQL::Error->new(
159             message => "Unknown type for fragment condition '$node->{on}'."
160             );
161 2364 100       3977 return '' if !$condition_type->DOES('GraphQL::Role::Abstract');
162 2364         4822 $context->{schema}->is_possible_type($condition_type, $self);
163 179     17   400 }
  179         1320  
  179         1505  
164              
165             fun _should_include_node(
166             HashRef $variables,
167             HashRef $node,
168 2364 50   2364   5680 ) :ReturnType(Bool) {
  2364 50       4631  
  2359 100       4874  
  2359 100       5209  
  2354         4933  
  35         3600  
169 35         57 DEBUG and _debug('_should_include_node', $variables, $node);
170 35         662 my $skip = $GraphQL::Directive::SKIP->_get_directive_values($node, $variables);
171 58 100 100     125 return '' if $skip and $skip->{if};
172 58         129 my $include = $GraphQL::Directive::INCLUDE->_get_directive_values($node, $variables);
173 35 100 100     87 return '' if $include and !$include->{if};
174 35         744 1;
175 2364     17   16743 }
  2364         14043  
  2364         2782  
176              
177             method _complete_value(
178             HashRef $context,
179             ArrayRef[HashRef] $nodes,
180             HashRef $info,
181             ArrayRef $path,
182             Any $result,
183   0   587     ) {
    0          
    0          
    0          
    0          
184             if ($self->is_type_of) {
185             my $is_type_of = $self->is_type_of->($result, $context->{context_value}, $info);
186             # TODO promise stuff
187             die GraphQL::Error->new(message => "Expected a value of type '@{[$self->to_string]}' but received: '@{[ref($result)||$result]}'.") if !$is_type_of;
188             }
189             my $subfield_nodes = [[], {}];
190             my $visited_fragment_names = {};
191             for (grep $_->{selections}, @$nodes) {
192             ($subfield_nodes, $visited_fragment_names) = $self->_collect_fields(
193             $context,
194             $_->{selections},
195             $subfield_nodes,
196             $visited_fragment_names,
197             );
198             }
199             DEBUG and _debug('Object._complete_value', $self->to_string, $subfield_nodes, $result);
200             GraphQL::Execution::_execute_fields($context, $self, $result, $path, $subfield_nodes);
201             }
202              
203             __PACKAGE__->meta->make_immutable();
204              
205             1;