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   4240 use 5.014;
  17         58  
4 17     17   99 use strict;
  17         35  
  17         374  
5 17     17   82 use warnings;
  17         31  
  17         434  
6 17     17   85 use Moo;
  17         30  
  17         198  
7 983     17   23528 use GraphQL::Debug qw(_debug);
  983         1189  
  983         3129  
8 2381     17   3361 use Types::Standard -all;
  2381         4374  
  2371         5280  
9 2190     17   767247 use GraphQL::Type::Library -all;
  2190         3441  
  2190         5800  
10 2190     17   231151 use MooX::Thunking;
  2190         8215  
  2190         6728  
11 42     17   3048 use GraphQL::MaybeTypeCheck;
  32         113  
  173         508  
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   469 use constant DEBUG => $ENV{GRAPHQL_DEBUG};
  171         454  
  171         2502  
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 475 method graphql_to_perl(Maybe[HashRef] $item) :ReturnType(Maybe[HashRef]) {
  966 0       3486  
  0 50       0  
  0         0  
  0         0  
  17         14184  
62 17 50       51 return $item if !defined $item;
63 17         74 $item = $self->uplift($item);
64 69         232 my $fields = $self->fields;
65             $self->hashmap($item, $fields, sub {
66 69     0   164 my ($key, $value) = @_;
67             $fields->{$key}{type}->graphql_to_perl(
68             $value // $fields->{$key}{default_value}
69 69   33     162 );
70 69         219 });
71 171     17   7646 }
  171         444  
  171         411  
72              
73             has to_doc => (is => 'lazy', isa => Str);
74             sub _build_to_doc {
75 35     35   1380 my ($self) = @_;
76 35         159 DEBUG and _debug('Object.to_doc', $self);
77             my @fieldlines = map {
78 35         1132 my ($main, @description) = @$_;
  966         2264  
79             (
80 966         2069 @description,
81             $main,
82             )
83             } $self->_make_fieldtuples($self->fields);
84 966 50       1443 my $implements = join ' & ', map $_->name, @{ $self->interfaces || [] };
  966         1741  
85 966   33     2030 $implements &&= 'implements ' . $implements . ' ';
86 966 50       6244 join '', map "$_\n",
87             $self->_description_doc_lines($self->description),
88 966         6880 "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 357 ) :ReturnType(InstanceOf[__PACKAGE__]) {
  17 100       46756  
  17 100       60  
  17 100       513  
  179         416  
  179         387  
  179         269  
97 179         298 $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   260 }
  69         753  
  69         448  
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   31020 ) {
  587 50       1491  
  587 50       1525  
  587 50       865  
  587 50       1241  
  587 50       1257  
  587         4125  
  587         7182  
  587         3820  
110 587         3743 DEBUG and _debug('_collect_fields', $self->to_string, $fields_got, $selections);
111 587         3279 for my $selection (@$selections) {
112 587         1895 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         24 my $use_name = $node->{alias} || $node->{name};
116 1         52 my ($field_names, $nodes_defs) = @$fields_got;
117 586 50       1467 $field_names = [ @$field_names, $use_name ] if !exists $nodes_defs->{$use_name};
118             $nodes_defs = {
119             %$nodes_defs,
120 586 50       955 $use_name => [ @{$nodes_defs->{$use_name} || []}, $node ],
  586         1998  
121             };
122 589         1873 $fields_got = [ $field_names, $nodes_defs ]; # no mutation
123             } elsif ($selection->{kind} eq 'inline_fragment') {
124 586 50       864 next if !$self->_fragment_condition_match($context, $node);
125             ($fields_got, $visited_fragments) = $self->_collect_fields(
126             $context,
127             $node->{selections},
128 586         2323 $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   266 ) :ReturnType(Bool) {
  179 100       499  
  177 100       834  
  10 100       182  
  10         1225  
  0         0  
  17         9817  
154 17         40 DEBUG and _debug('_fragment_condition_match', $self->to_string, $node);
155 17 100       83 return 1 if !$node->{on};
156 2364 100       4463 return 1 if $node->{on} eq $self->name;
157             my $condition_type = $context->{schema}->name2type->{$node->{on}} //
158 2364   100     3824 die GraphQL::Error->new(
159             message => "Unknown type for fragment condition '$node->{on}'."
160             );
161 2364 100       3450 return '' if !$condition_type->DOES('GraphQL::Role::Abstract');
162 2364         4515 $context->{schema}->is_possible_type($condition_type, $self);
163 179     17   360 }
  179         1147  
  179         928  
164              
165             fun _should_include_node(
166             HashRef $variables,
167             HashRef $node,
168 2364 50   2364   6438 ) :ReturnType(Bool) {
  2364 50       4111  
  2359 100       4460  
  2359 100       4303  
  2354         4167  
  35         3918  
169 35         97 DEBUG and _debug('_should_include_node', $variables, $node);
170 35         798 my $skip = $GraphQL::Directive::SKIP->_get_directive_values($node, $variables);
171 58 100 100     137 return '' if $skip and $skip->{if};
172 58         162 my $include = $GraphQL::Directive::INCLUDE->_get_directive_values($node, $variables);
173 35 100 100     101 return '' if $include and !$include->{if};
174 35         816 1;
175 2364     17   14448 }
  2364         11805  
  2364         2478  
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;