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              
2             use 5.014;
3 17     17   3957 use strict;
  17         55  
4 17     17   80 use warnings;
  17         31  
  17         305  
5 17     17   71 use Moo;
  17         31  
  17         364  
6 17     17   91 use GraphQL::Debug qw(_debug);
  17         42  
  17         134  
7 983     17   21025 use Types::Standard -all;
  983         1224  
  983         2795  
8 2381     17   3106 use GraphQL::Type::Library -all;
  2381         3932  
  2371         4723  
9 2190     17   689497 use MooX::Thunking;
  2190         3257  
  2190         5240  
10 2190     17   207367 use GraphQL::MaybeTypeCheck;
  2190         8238  
  2190         6288  
11 42     17   2617 extends qw(GraphQL::Type);
  32         92  
  173         368  
12             with qw(
13             GraphQL::Role::Output
14             GraphQL::Role::Composite
15             GraphQL::Role::Nullable
16             GraphQL::Role::Named
17             GraphQL::Role::FieldsOutput
18             GraphQL::Role::HashMappable
19             );
20              
21             our $VERSION = '0.02';
22             use constant DEBUG => $ENV{GRAPHQL_DEBUG};
23 173     17   417  
  171         432  
  171         2296  
24             =head1 NAME
25              
26             GraphQL::Type::Object - GraphQL object type
27              
28             =head1 SYNOPSIS
29              
30             use GraphQL::Type::Object;
31             my $interface_type;
32             my $implementing_type = GraphQL::Type::Object->new(
33             name => 'Object',
34             interfaces => [ $interface_type ],
35             fields => { field_name => { type => $scalar_type, resolve => sub { '' } }},
36             );
37              
38             =head1 ATTRIBUTES
39              
40             Has C<name>, C<description> from L<GraphQL::Role::Named>.
41             Has C<fields> from L<GraphQL::Role::FieldsOutput>.
42              
43             =head2 interfaces
44              
45             Optional, thunked array-ref of interface type objects implemented.
46              
47             =cut
48              
49             has interfaces => (is => 'thunked', isa => ArrayRef[InstanceOf['GraphQL::Type::Interface']]);
50              
51             =head2 is_type_of
52              
53             Optional code-ref. Input is a value, an execution context hash-ref,
54             and resolve-info hash-ref.
55              
56             =cut
57              
58             has is_type_of => (is => 'ro', isa => CodeRef);
59              
60             method graphql_to_perl(Maybe[HashRef] $item) :ReturnType(Maybe[HashRef]) {
61 154 0   0 1 362 return $item if !defined $item;
  966 0       3226  
  0 50       0  
  0         0  
  0         0  
  17         13018  
62 17 50       38 $item = $self->uplift($item);
63 17         92 my $fields = $self->fields;
64 69         208 $self->hashmap($item, $fields, sub {
65             my ($key, $value) = @_;
66 69     0   139 $fields->{$key}{type}->graphql_to_perl(
67             $value // $fields->{$key}{default_value}
68             );
69 69   33     135 });
70 69         183 }
71 171     17   6889  
  171         386  
  171         332  
72             has to_doc => (is => 'lazy', isa => Str);
73             my ($self) = @_;
74             DEBUG and _debug('Object.to_doc', $self);
75 35     35   1130 my @fieldlines = map {
76 35         115 my ($main, @description) = @$_;
77             (
78 35         901 @description,
  966         1837  
79             $main,
80 966         1756 )
81             } $self->_make_fieldtuples($self->fields);
82             my $implements = join ' & ', map $_->name, @{ $self->interfaces || [] };
83             $implements &&= 'implements ' . $implements . ' ';
84 966 50       1251 join '', map "$_\n",
  966         1642  
85 966   33     1858 $self->_description_doc_lines($self->description),
86 966 50       6302 "type @{[$self->name]} $implements\{",
87             (map length() ? " $_" : "", @fieldlines),
88 966         6022 "}";
89             }
90              
91             method from_ast(
92             HashRef $name2type,
93             HashRef $ast_node,
94             ) :ReturnType(InstanceOf[__PACKAGE__]) {
95             $self->new(
96 69 50   69 1 349 $self->_from_ast_named($ast_node),
  17 100       42748  
  17 100       49  
  17 100       475  
  179         350  
  179         325  
  179         237  
97 179         287 $self->_from_ast_maptype($name2type, $ast_node, 'interfaces'),
98             $self->_from_ast_fields($name2type, $ast_node, 'fields'),
99             );
100             }
101              
102 69     17   209 method _collect_fields(
  69         706  
  69         412  
103             HashRef $context,
104             ArrayRef $selections,
105             FieldsGot $fields_got,
106             Map[StrNameValid,Bool] $visited_fragments,
107             ) {
108             DEBUG and _debug('_collect_fields', $self->to_string, $fields_got, $selections);
109 966 50   966   27375 for my $selection (@$selections) {
  587 50       1226  
  587 50       1145  
  587 50       768  
  587 50       1096  
  587 50       1110  
  587         3718  
  587         6746  
  587         3605  
110 587         3440 my $node = $selection;
111 587         3020 next if !_should_include_node($context->{variable_values}, $node);
112 587         1559 if ($selection->{kind} eq 'field') {
113 6 50       25 my $use_name = $node->{alias} || $node->{name};
114 6 0       48 my ($field_names, $nodes_defs) = @$fields_got;
    0          
    0          
115 1         24 $field_names = [ @$field_names, $use_name ] if !exists $nodes_defs->{$use_name};
116 1         50 $nodes_defs = {
117 586 50       1354 %$nodes_defs,
118             $use_name => [ @{$nodes_defs->{$use_name} || []}, $node ],
119             };
120 586 50       969 $fields_got = [ $field_names, $nodes_defs ]; # no mutation
  586         1699  
121             } elsif ($selection->{kind} eq 'inline_fragment') {
122 589         1623 next if !$self->_fragment_condition_match($context, $node);
123             ($fields_got, $visited_fragments) = $self->_collect_fields(
124 586 50       745 $context,
125             $node->{selections},
126             $fields_got,
127             $visited_fragments,
128 586         2008 );
129             } elsif ($selection->{kind} eq 'fragment_spread') {
130             my $frag_name = $node->{name};
131             next if $visited_fragments->{$frag_name};
132 0           $visited_fragments = { %$visited_fragments, $frag_name => 1 }; # !mutate
133 0 100         my $fragment = $context->{fragments}{$frag_name};
134 0           next if !$fragment;
135 0           next if !$self->_fragment_condition_match($context, $fragment);
136 0 100         DEBUG and _debug('_collect_fields(fragment_spread)', $fragment);
137 0 0         ($fields_got, $visited_fragments) = $self->_collect_fields(
138             $context,
139             $fragment->{selections},
140             $fields_got,
141             $visited_fragments,
142             );
143             }
144             }
145             ($fields_got, $visited_fragments);
146             }
147              
148             method _fragment_condition_match(
149             HashRef $context,
150             HashRef $node,
151             ) :ReturnType(Bool) {
152             DEBUG and _debug('_fragment_condition_match', $self->to_string, $node);
153 179 100   179   202 return 1 if !$node->{on};
  179 100       399  
  177 100       680  
  10 100       202  
  10         1294  
  0         0  
  17         8848  
154 17         39 return 1 if $node->{on} eq $self->name;
155 17 100       65 my $condition_type = $context->{schema}->name2type->{$node->{on}} //
156 2364 100       3817 die GraphQL::Error->new(
157             message => "Unknown type for fragment condition '$node->{on}'."
158 2364   100     3435 );
159             return '' if !$condition_type->DOES('GraphQL::Role::Abstract');
160             $context->{schema}->is_possible_type($condition_type, $self);
161 2364 100       3163 }
162 2364         4099  
163 179     17   352 fun _should_include_node(
  179         1108  
  179         1018  
164             HashRef $variables,
165             HashRef $node,
166             ) :ReturnType(Bool) {
167             DEBUG and _debug('_should_include_node', $variables, $node);
168 2364 50   2364   5316 my $skip = $GraphQL::Directive::SKIP->_get_directive_values($node, $variables);
  2364 50       3914  
  2359 100       4333  
  2359 100       4082  
  2354         3846  
  35         3302  
169 35         51 return '' if $skip and $skip->{if};
170 35         622 my $include = $GraphQL::Directive::INCLUDE->_get_directive_values($node, $variables);
171 58 100 100     133 return '' if $include and !$include->{if};
172 58         124 1;
173 35 100 100     80 }
174 35         668  
175 2364     17   13924 method _complete_value(
  2364         11400  
  2364         2486  
176             HashRef $context,
177             ArrayRef[HashRef] $nodes,
178             HashRef $info,
179             ArrayRef $path,
180             Any $result,
181             ) {
182             if ($self->is_type_of) {
183   0   587     my $is_type_of = $self->is_type_of->($result, $context->{context_value}, $info);
    0          
    0          
    0          
    0          
184             # TODO promise stuff
185             die GraphQL::Error->new(message => "Expected a value of type '@{[$self->to_string]}' but received: '@{[ref($result)||$result]}'.") if !$is_type_of;
186             }
187             my $subfield_nodes = [[], {}];
188             my $visited_fragment_names = {};
189             for (grep $_->{selections}, @$nodes) {
190             ($subfield_nodes, $visited_fragment_names) = $self->_collect_fields(
191             $context,
192             $_->{selections},
193             $subfield_nodes,
194             $visited_fragment_names,
195             );
196             }
197             DEBUG and _debug('Object._complete_value', $self->to_string, $subfield_nodes, $result);
198             GraphQL::Execution::_execute_fields($context, $self, $result, $path, $subfield_nodes);
199             }
200              
201             __PACKAGE__->meta->make_immutable();
202              
203             1;