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