File Coverage

blib/lib/RPC/ExtDirect/Util.pm
Criterion Covered Total %
statement 104 121 85.9
branch 52 62 83.8
condition 18 20 90.0
subroutine 13 14 92.8
pod 0 5 0.0
total 187 222 84.2


line stmt bran cond sub pod time code
1             package RPC::ExtDirect::Util;
2              
3 33     33   1180 use strict;
  33         56  
  33         687  
4 33     33   95 use warnings;
  33         30  
  33         611  
5 33     33   98 no warnings 'uninitialized'; ## no critic
  33         30  
  33         705  
6              
7 33     33   117 use Carp;
  33         30  
  33         1447  
8 33     33   1192 use JSON;
  33         19037  
  33         139  
9              
10 33     33   2619 use base 'Exporter';
  33         44  
  33         8734  
11              
12             our @EXPORT_OK = qw/
13             clean_error_message
14             get_caller_info
15             parse_global_flags
16             /;
17              
18             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
19             #
20             # Clean croak() and die() messages of file/line information
21             #
22              
23             sub clean_error_message {
24 13     13 0 705 my ($msg) = @_;
25              
26 13         92 $msg =~ s/
27             (?
28             at
29             .*?
30             line \s \d+(, \s \s line \s \d+)? \.? \n*
31             (?:\s*eval \s \{...\} \s called \s at \s .*? line \s \d+ \n*)?
32             //msx;
33              
34 13         30 return $msg;
35             }
36              
37             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
38             #
39             # Return formatted call stack part to use in exceptions
40             #
41              
42             sub get_caller_info {
43 11     11 0 487 my ($depth) = @_;
44            
45 11         100 my ($package, $sub) = (caller $depth)[3] =~ / \A (.*) :: (.*?) \z /xms;
46            
47 11         49 return $package . '->' . $sub;
48             }
49              
50             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
51             #
52             # Fetch the values of the (deprecated) global flags into an object,
53             # giving a warning when they're used
54             #
55              
56             sub parse_global_flags {
57 175     175 0 3104 my ($flags, $obj) = @_;
58            
59 175         251 my $caller_pkg = caller;
60            
61 175         291 for my $flag ( @$flags ) {
62 2333         2059 my $package = $flag->{package};
63 2333         1634 my $var = $flag->{var};
64 2333         1567 my $type = $flag->{type};
65 2333         1568 my $fields = $flag->{setter};
66 2333         1540 my $default = $flag->{default};
67            
68 2333         1618 my $have_default = exists $flag->{default};
69 2333         2341 my $full_var = $package . '::' . $var;
70            
71 2333         1489 my ($value, $have_value);
72            
73             {
74 33     33   142 no strict 'refs';
  33         33  
  33         788  
  2333         1388  
75 33     33   104 no warnings 'once';
  33         726  
  33         11572  
76            
77 2333 100       2489 if ( $type eq 'scalar' ) {
    100          
    50          
78 2161         1338 $have_value = defined ${ $full_var };
  2161         3947  
79 2161 100       2458 $value = $have_value ? ${ $full_var } : $default;
  4         8  
80             }
81             elsif ( $type eq 'hash' ) {
82 169         125 $have_value = %{ $full_var };
  169         473  
83 169 100       449 $value = $have_value ? { %{ $full_var } }
  1 100       5  
84             : 'HASH' eq ref $default ? { %$default }
85             : undef
86             ;
87             }
88             elsif ( $type eq 'array' ) {
89 3         3 $have_value = @{ $full_var };
  3         6  
90 3 100       11 $value = $have_value ? [ @{ $full_var } ]
  1 100       3  
91             : 'ARRAY' eq ref $default ? [ @$default ]
92             : undef
93             ;
94             }
95             else {
96 0         0 die "Unknown global variable type: '$type'"; # Debug mostly
97             }
98             }
99            
100 2333 100       2817 if ( $have_value ) {
101 6         14 my $warning = <<"END";
102              
103             The package global variable $full_var is deprecated
104             and is going to be removed in the next RPC::ExtDirect version.
105             END
106            
107 6 50       12 if ( 'ARRAY' eq ref $fields ) {
108            
109 0         0 my $tpl = <<"END";
110             Use $caller_pkg instance with the following config options instead:
111             %s
112              
113             my \$config = $caller_pkg->new(
114             %s
115             );
116              
117             END
118 0         0 my $w1 = join ', ', map { "`$_`" } @$fields;
  0         0  
119 0         0 my $w2 = join "\n", map { "\t\t$_ => ..." } @$fields;
  0         0  
120            
121 0         0 $warning .= sprintf $tpl, $w1, $w2;
122             }
123             else {
124 6         20 $warning .= <<"END";
125             Use the `$fields` config option with the $caller_pkg
126             instance instead:
127              
128             my \$config = $caller_pkg->new(
129             $fields => ...
130             );
131            
132             END
133             }
134            
135 6         96 warn $warning;
136             }
137              
138 2333 50       2684 croak "Can't resolve the field name for var $full_var"
139             unless $fields;
140            
141 2333 50       3698 $fields = [ $fields ] unless 'ARRAY' eq ref $fields;
142            
143 2333         2163 for my $field ( @$fields ) {
144 2333         2105 my $predicate = "has_$field";
145            
146 2333 100 66     6987 $obj->$field($value)
      66        
147             if $have_value || ($have_default && !$obj->$predicate());
148             }
149             }
150             }
151              
152             ### NON-EXPORTED PUBLIC PACKAGE SUBROUTINE ###
153             #
154             # Parse ExtDirect attribute, perform sanity checks and return
155             # the attribute hashref
156             #
157              
158             sub parse_attribute {
159 357     357 0 23658 my ($package, $symbol, $referent, $attr, $data, $phase, $file, $line)
160             = @_;
161              
162 357 100       894 croak "Method attribute is not ExtDirect at $file line $line"
163             unless $attr eq 'ExtDirect';
164              
165             # Attribute::Handlers automagically parses the data into arrayref
166             # *if* it is parseable Perl (which it should be). If not, the data
167             # is going to be a garbled string which is kaput for us. However,
168             # an *empty* string means the bare attribute was used with no
169             # parameters, which is strange but is not an error.
170 356 100 100     1625 croak "Malformed ExtDirect attribute '$data' at $file line $line"
171             if $data ne '' && 'ARRAY' ne ref $data;
172              
173 33     33   133 my $symbol_name = eval { no strict 'refs'; *{$symbol}{NAME} };
  33         32  
  33         17654  
  355         412  
  355         273  
  355         644  
174 355 100       693 croak "Can't resolve symbol '$symbol' for package '$package' ".
175             "at $file line $line: $@"
176             if $@;
177            
178             # Attribute may be empty, means no argument checking
179 354   100     611 $data ||= [];
180              
181             # Calling convention attributes are mutually exclusive
182 354         292 my @calling_convention;
183              
184             my %attr;
185            
186             # Compatibility form (n, ...), where n stands for (len => n)
187 354 100       1161 if ( $data->[0] =~ / \A \d+ \z /xms ) {
188 193         346 $attr{len} = shift @$data;
189 193         268 push @calling_convention, 'len';
190             }
191              
192 354         591 while ( @$data ) {
193 314         304 my $param_def = shift @$data;
194            
195             # len means ordered (by position) arguments
196 314 100       1552 if ( $param_def =~ / \A len \z /xms ) {
    100          
    100          
    100          
    100          
    100          
197 10         16 $attr{len} = shift @$data;
198              
199             croak "ExtDirect attribute 'len' should be followed ".
200             "by a number of ordered arguments at file $file ".
201             "line $line"
202 10 100       301 unless $attr{len} =~ / \A \d+ \z /xms;
203            
204 8         10 push @calling_convention, 'len';
205             }
206              
207             # formHandler means exactly that, a handler for form requests
208             elsif ( $param_def =~ / \A formHandler \z /xms ) {
209 48         83 $attr{formHandler} = 1;
210 48         74 push @calling_convention, 'formHandler';
211             }
212              
213             # pollHandlers are used with EventProvider
214             elsif ( $param_def =~ / \A pollHandler \z /xms ) {
215 18         37 $attr{pollHandler} = 1;
216 18         35 push @calling_convention, 'pollHandler';
217             }
218            
219             # named arguments for the method
220             elsif ( $param_def =~ / \A params \z /ixms ) {
221 63         83 my $arg_names = shift @$data;
222              
223 63 100       336 croak "ExtDirect attribute 'params' must be followed by ".
224             "arrayref at $file line $line"
225             if ref $arg_names ne 'ARRAY';
226              
227             # Copy the names
228 61         60 $attr{params} = [ @{ $arg_names } ];
  61         126  
229              
230 61         109 push @calling_convention, 'params';
231             }
232              
233             # Hooks
234             elsif ( $param_def =~ / \A (before|instead|after) \z /ixms ) {
235 111         153 my $type = $1;
236 111         96 my $code = shift @$data;
237              
238 111 100 100     912 croak "ExtDirect attribute '$type' must be followed by coderef, ".
      100        
239             "undef, or 'NONE' at $file line $line"
240             if defined $code && $code ne 'NONE' && 'CODE' ne ref $code;
241            
242 108         182 $attr{ $type } = $code;
243             }
244              
245             # Strict is a boolean attribute, but let's be flexible about it
246             elsif ( $param_def =~ / \A strict \z /ixms ) {
247 6         12 $attr{strict} = !!(shift @$data);
248             }
249            
250             # Assume a generic foo => 'bar' attribute and fall through
251             else {
252 58         78 $attr{ $param_def } = shift @$data;
253             }
254              
255             # There should be at most one calling convention attribute defined,
256             # but we don't care how many exactly if more than one
257 307 100       1606 croak sprintf "ExtDirect attributes '%s' and '%s' are ".
258             "mutually exclusive at file $file line $line",
259             @calling_convention
260             if @calling_convention > 1;
261             };
262              
263             # strict should only be defined for named methods
264             croak "ExtDirect attribute 'strict' should be used with 'params' ".
265             "for named Methods at file $file line $line"
266 338 100 100     998 if exists $attr{strict} && !defined $attr{params};
267            
268             return {
269 334         6266 package => $package,
270             method => $symbol_name,
271             %attr,
272             };
273             }
274              
275             ### NON-EXPORTED PUBLIC PACKAGE SUBROUTINE ###
276             #
277             # Decode metadata sent by the client. This function changes
278             # the passed hashref in situ (so has side effects).
279             #
280              
281             sub decode_metadata {
282             # This is a bit hacky but will do
283 0     0 0   my ($self, $keywords) = @_;
284              
285 0           my $meta_encoded = $keywords->{metadata};
286              
287 0 0         if ( defined $meta_encoded ) {
288             # Whoever sends *multiple* metadata fields is going to regret it.
289 0 0         my $meta_json = 'ARRAY' eq ref $meta_encoded ? pop @$meta_encoded
290             : $meta_encoded
291             ;
292              
293 0           local $@;
294 0           $keywords->{metadata} = eval { JSON::from_json($meta_json) };
  0            
295              
296 0 0         if ( $@ ) {
297 0           my $error = clean_error_message($@);
298 0           $self->set_error("Invalid metadata: $error");
299             }
300             }
301             }
302              
303             1;
304