File Coverage

blib/lib/GraphQL/Subscription.pm
Criterion Covered Total %
statement 58 61 95.0
branch 30 52 57.6
condition 5 8 62.5
subroutine 16 16 100.0
pod 1 1 100.0
total 110 138 79.7


line stmt bran cond sub pod time code
1              
2             use 5.014;
3 2     2   2076 use strict;
  12         579  
4 12     2   176 use warnings;
  22         1945  
  32         451  
5 22     2   138 use Types::Standard -all;
  22         177  
  12         96  
6 12     2   41 use Types::TypeTiny -all;
  12         81  
  12         365  
7 12     2   81429 use GraphQL::Type::Library -all;
  12         43  
  11         42  
8 11     2   1323 use GraphQL::MaybeTypeCheck;
  11         152  
  11         131  
9 11     2   24543 use GraphQL::Language::Parser qw(parse);
  12         614  
  12         77  
10 12     2   40 use GraphQL::Error;
  12         33  
  12         147  
11 12     2   46 use GraphQL::Debug qw(_debug);
  12         92  
  12         49  
12 12     2   62 require GraphQL::Execution;
  11         53  
  11         113  
13             use Exporter 'import';
14 11     2   57  
  11         49  
  11         174  
15             =head1 NAME
16              
17             GraphQL::Subscription - Implement GraphQL subscriptions
18              
19             =cut
20              
21             our @EXPORT_OK = qw(
22             subscribe
23             );
24             our $VERSION = '0.02';
25              
26             use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false
27 11     2   72  
  11         238  
  14         1200  
28             =head1 SYNOPSIS
29              
30             use GraphQL::Subscription qw(subscribe);
31             my $result = subscribe($schema, $doc, $root_value);
32              
33             =head1 DESCRIPTION
34              
35             Implements a GraphQL "subscription" - a feed of events, commonly of
36             others' mutations.
37              
38             =head1 METHODS
39              
40             =head2 subscribe
41              
42             my $result = subscribe(
43             $schema,
44             $doc, # can also be AST
45             $root_value,
46             $context_value,
47             $variable_values,
48             $operation_name,
49             $field_resolver,
50             $promise_code,
51             $subscribe_resolver,
52             );
53              
54             Takes the same args as L<GraphQL::Execution/execute>, except
55             that the C<$promise_code> is mandatory, and there is the optional
56             C<$subscribe_resolver> which, supplied or not, will be used instead
57             of the C<$field_resolver> to resolve the initial subscription. The
58             C<$field_resolver> will still be used for resolving each result.
59              
60             Returns a promise of either:
61              
62             =over
63              
64             =item *
65              
66             an error result if the subscription fails (generated by GraphQL)
67              
68             =item *
69              
70             a L<GraphQL::AsyncIterator> instance generated by a resolver, probably
71             hooked up to a L<GraphQL::PubSub> instance
72              
73             =back
74              
75             The event source returns values by using
76             L<GraphQL::AsyncIterator/publish>. To communicate errors, resolvers
77             can throw errors in the normal way, or at the top level, use
78             L<GraphQL::AsyncIterator/error>.
79              
80             The values will be resolved in the normal GraphQL fashion, including that
81             if there is no specific-to-field resolver, it must be a value acceptable
82             to the supplied or default field-resolver, typically a hash-ref with a
83             key of the field's name.
84              
85             =cut
86              
87             fun subscribe(
88             (InstanceOf['GraphQL::Schema']) $schema,
89             Str | ArrayRef[HashRef] $doc,
90             Any $root_value,
91             Any $context_value,
92             Maybe[HashRef] $variable_values,
93             Maybe[Str] $operation_name,
94             Maybe[CodeLike] $field_resolver,
95             PromiseCode $promise_code,
96             Maybe[CodeLike] $subscribe_resolver = undef,
97             ) :ReturnType(Promise) {
98 22 50   11 1 9846 die 'Must supply $promise_code' if !$promise_code;
  22 50       127  
  12 50       48  
  12 50       108  
  2 100       16  
  1 100       2  
  1 50       2  
  1 100       6  
  1 50       3  
  1 100       13  
  1 100       7  
  1 50       2  
  1 50       6  
    50          
    50          
    50          
    0          
    50          
99 1 50       4 my $result = eval {
100 1         2 my $ast;
101 1         5 my $context = eval {
102 1         22 $ast = ref($doc) ? $doc : parse($doc);
103 1 50       101 GraphQL::Execution::_build_context(
104 0         0 $schema,
105             $ast,
106             $root_value,
107             $context_value,
108             $variable_values,
109             $operation_name,
110             $subscribe_resolver,
111             $promise_code,
112             );
113             };
114             DEBUG and _debug('subscribe', $context, $@);
115 0         0 die $@ if $@;
116 0 50       0 # from GraphQL::Execution::_execute_operation
117             my $operation = $context->{operation};
118 1         4 my $op_type = $operation->{operationType} || 'subscription';
119 1   50     6 my $type = $context->{schema}->$op_type;
      66        
120             my ($fields) = $type->_collect_fields(
121             $context,
122             $operation->{selections},
123             [[], {}],
124             {},
125             );
126             DEBUG and _debug('subscribe(fields)', $fields, $root_value);
127             # from GraphQL::Execution::_execute_fields
128             my ($field_names, $nodes_defs) = @$fields;
129             die "Subscription needs to have only one field; got (@$field_names)\n"
130   50         if @$field_names != 1;
131             my $result_name = $field_names->[0];
132             my $nodes = $nodes_defs->{$result_name};
133             my $field_node = $nodes->[0];
134             my $field_name = $field_node->{name};
135             my $field_def = GraphQL::Execution::_get_field_def($context->{schema}, $type, $field_name);
136             DEBUG and _debug('subscribe(resolve)', $type->to_string, $nodes, $root_value, $field_def);
137             die "The subscription field '$field_name' is not defined\n"
138   50         if !$field_def;
139             my $resolve = $field_def->{subscribe} || $context->{field_resolver};
140     66       my $path = [ $result_name ];
141             my $info = GraphQL::Execution::_build_resolve_info(
142             $context,
143             $type,
144             $field_def,
145             $path,
146             $nodes,
147             );
148             my $result = GraphQL::Execution::_resolve_field_value_or_error(
149             $context,
150             $field_def,
151             $nodes,
152             $resolve,
153             $root_value,
154             $info,
155             );
156             DEBUG and _debug('subscribe(result)', $result, $@);
157             die "The subscription field '$field_name' returned non-AsyncIterator '$result'\n"
158   50         if !is_AsyncIterator($result);
159             $result->map_then(
160             sub {
161             GraphQL::Execution::execute(
162       12     $schema,
163             $ast,
164             $_[0],
165             $context_value,
166             $variable_values,
167             $operation_name,
168             $field_resolver,
169             $promise_code,
170             )
171             },
172             sub {
173             my ($error) = @_;
174       2     die $error if !GraphQL::Error->is($error);
175   50         GraphQL::Execution::_build_response(GraphQL::Execution::_wrap_error($error));
176             },
177             );
178             };
179             $result = GraphQL::Execution::_build_response(GraphQL::Execution::_wrap_error($@)) if $@;
180   50         $promise_code->{resolve}->($result);
181             }
182 5     2   4937  
  5         23  
  4         30  
183             1;