File Coverage

blib/lib/Perinci/Access/Schemeless.pm
Criterion Covered Total %
statement 435 546 79.6
branch 182 296 61.4
condition 85 116 73.2
subroutine 59 73 80.8
pod 3 43 6.9
total 764 1074 71.1


line stmt bran cond sub pod time code
1              
2             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
3             our $DATE = '2021-08-01'; # DATE
4             our $DIST = 'Perinci-Access-Perl'; # DIST
5             our $VERSION = '0.897'; # VERSION
6              
7             use 5.010001;
8 3     3   125549 use strict;
  3         22  
9 3     3   13 use warnings;
  3         6  
  3         74  
10 3     3   14 use experimental 'smartmatch';
  3         6  
  3         112  
11 3     3   753 use Log::ger;
  3         5996  
  3         1352  
12 3     3   4570  
  3         154  
  3         12  
13             use parent qw(Perinci::Access::Base);
14 3     3   938  
  3         253  
  3         17  
15             use List::Util qw(first);
16 3     3   8786 use Perinci::Object;
  3         6  
  3         241  
17 3     3   1235 use Perinci::Sub::Normalize qw(normalize_function_metadata);
  3         1152  
  3         187  
18 3     3   1104 use Perinci::Sub::Util qw(err);
  3         3077  
  3         175  
19 3     3   1275 use Scalar::Util qw(blessed);
  3         6086  
  3         187  
20 3     3   21 use Module::Path::More qw(module_path);
  3         7  
  3         175  
21 3     3   1374 use Package::Util::Lite qw(package_exists);
  3         3023  
  3         170  
22 3     3   1102 use Tie::Cache;
  3         1103  
  3         144  
23 3     3   1177 use URI::Split qw(uri_split uri_join);
  3         7035  
  3         158  
24 3     3   23  
  3         7  
  3         4335  
25             our $re_perl_package =
26             qr/\A[A-Za-z_][A-Za-z_0-9]*(::[A-Za-z_0-9][A-Za-z_0-9]*)*\z/;
27              
28             require Class::Inspector;
29              
30 36     36 1 136467 my $class = shift;
31             my $self = $class->SUPER::new(@_);
32 36         8951  
33 36         151 # build a list of supported actions for each type of entity
34             my %typeacts = (
35             package => [],
36 36         335 function => [],
37             variable => [],
38             ); # key = type, val = [[ACTION, META], ...]
39              
40             my @comacts;
41             for my $meth (@{Class::Inspector->methods(ref $self)}) {
42 36         55 next unless $meth =~ /^actionmeta_(.+)/;
43 36         51 my $act = $1;
  36         159  
44 2846 100       29886 my $meta = $self->$meth();
45 685         1164 $self->{_actionmetas}{$act} = $meta;
46 685         1407 for my $type (@{$meta->{applies_to}}) {
47 685         1155 if ($type eq '*') {
48 685         781 push @comacts, [$act, $meta];
  685         1064  
49 685 100       998 } else {
50 469         1058 push @{$typeacts{$type}}, [$act, $meta];
51             }
52 216         258 }
  216         553  
53             }
54             for my $type (keys %typeacts) {
55             $typeacts{$type} = { map {$_->[0] => $_->[1]}
56 36         203 @{$typeacts{$type}}, @comacts };
57 1623         2716 }
58 108         134 $self->{_typeacts} = \%typeacts;
  108         205  
59              
60 36         97 $self->{cache_size} //= 100; # for caching metadata & code
61             #$self->{use_tx} //= 0;
62 36   50     166 $self->{wrap} //= 1;
63             #$self->{custom_tx_manager} //= undef;
64 36   100     114 $self->{load} //= 1;
65             $self->{normalize_metadata} //= 1;
66 36   50     123 #$self->{after_load}
67 36   100     124 #$self->{allow_paths}
68             #$self->{deny_paths}
69             #$self->{allow_schemes}
70             #$self->{deny_schemes}
71             #$self->{package_prefix}
72             $self->{debug} //= $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG} // 0;
73             $self->{accept_argv} //= 1;
74 36   50     267  
      33        
75 36   50     119 if ($self->{cache_size} > 0) {
76             my %metacache;
77 36 50       92 tie %metacache, 'Tie::Cache', $self->{cache_size};
78 36         51 $self->{_meta_cache} = \%metacache;
79 36         189 my %codecache;
80 36         840 tie %codecache, 'Tie::Cache', $self->{cache_size};
81 36         50 $self->{_code_cache} = \%codecache;
82 36         103 }
83 36         610  
84             $self;
85             }
86 36         164  
87             # for older Perinci::Access::Base 0.28-, to remove later
88              
89             # if paths=/a/b, will match /a/b as well as /a/b/c
90       0     my ($path, $paths) = @_;
91              
92             my $pathslash = $path =~ m!/\z! ? $path : "$path/";
93              
94 30     30   1268 for (ref($paths) eq 'ARRAY' ? @$paths : $paths) {
95             if (ref($_) eq 'Regexp') {
96 30 100       111 return 1 if $path =~ $_;
97             } else {
98 30 50       74 if (m!/\z!) {
99 30 100       44 return 1 if $_ eq $pathslash || index($pathslash, $_) == 0;
100 8 100       59 } else {
101             my $p = "$_/";
102 22 100       50 return 1 if $p eq $path || index($pathslash, $p) == 0;
103 10 100 100     49 }
104             }
105 12         21 }
106 12 100 100     89 0;
107             }
108              
109             # if paths=/a/b, will match /a/b as well as /a/b/c AS WELL AS /a and /. only
110 15         52 # suitable for 'list' action, e.g. allow_path is '/a/b' but we can do 'list /'
111             # and 'list /a' too (but not 'list /c').
112             my ($path, $paths) = @_;
113              
114             my $pathslash = $path =~ m!/\z! ? $path : "$path/";
115              
116             for (ref($paths) eq 'ARRAY' ? @$paths : $paths) {
117 461     461   4341 if (ref($_) eq 'Regexp') {
118             # we can't match a regex against a string, so we just pass here
119 461 100       980 return 1;
120             } else {
121 461 50       800 if (m!/\z!) {
122 461 100       593 return 1 if $_ eq $pathslash || index($_, $pathslash) == 0 ||
123             index($pathslash, $_) == 0;
124 53         124 } else {
125             my $p = "$_/";
126 408 100       554 return 1 if $p eq $path || index($p, $pathslash) == 0 ||
127 10 100 100     63 index($pathslash, $p) == 0 ;
      100        
128             }
129             }
130 398         527 }
131 398 100 100     1580 0;
      100        
132             }
133              
134             my ($self, $req) = @_;
135              
136 331         717 my $path = $req->{-uri_path};
137             if (defined $self->{allow_paths}) {
138             my $allow;
139             if ($self->{_actionmetas}{$req->{action}}{allow_request_parent_path}) {
140 84     84   149 $allow = __match_paths2($path, $self->{allow_paths});
141             } else {
142 84         124 $allow = __match_paths($path, $self->{allow_paths});
143 84 100       176 }
144 5         54 return err(403, "Forbidden uri path (does not match allow_paths)")
145 5 100       17 unless $allow;
146 4         12 }
147             if (defined($self->{deny_paths}) &&
148 1         5 __match_paths($path, $self->{deny_paths})) {
149             return err(403, "Forbidden uri path (matches deny_paths)");
150 5 100       14 }
151              
152             my $sch = $req->{-uri_scheme} // "";
153 83 100 100     196 if (defined($self->{allow_schemes}) && !($sch ~~ $self->{allow_schemes})) {
154             return err(501,
155 1         5 "Unsupported uri scheme (does not match allow_schemes)");
156             }
157             if (defined($self->{deny_schemes}) && ($sch ~~ $self->{deny_schemes})) {
158 82   100     246 return err(501, "Unsupported uri scheme (matches deny_schemes)");
159 82 100 100     238 }
160 3         16  
161             my ($dir, $leaf, $perl_package);
162             if ($path =~ m!(.*)/(.*)!) {
163 79 100 100     163 $dir = $1;
164 1         4 $leaf = $2;
165             } else {
166             $dir = $path;
167 78         123 $leaf = '';
168 78 50       292 }
169 78         178 for ($perl_package) {
170 78         163 $_ = $dir;
171             s!^/+!!;
172 0         0 s!/+!::!g;
173 0         0 if (defined $self->{package_prefix}) {
174             $_ = $self->{package_prefix} . (length($_) ? "::":"") . $_;
175 78         151 }
176 78         103 }
177 78         243 return err(400, "Invalid perl package name: $perl_package")
178 78         259 if $perl_package && $perl_package !~ $re_perl_package;
179 78 50       210  
180 0 0       0 my $type;
181             if (length $leaf) {
182             if ($leaf =~ /^[%\@\$]/) {
183 78 50 66     535 $type = 'variable';
184             } else {
185             $type = 'function';
186 78         110 }
187 78 100       153 } else {
188 43 100       113 $type = 'package';
189 2         3 # make sure path ends in /, to ease processing
190             $req->{-uri_path} .= "/" unless $path =~ m!/\z!;
191 41         70 }
192              
193             $req->{-uri_dir} = $dir;
194 35         48 $req->{-uri_leaf} = $leaf;
195             $req->{-perl_package} = $perl_package;
196 35 50       111 $req->{-type} = $type;
197              
198             #$log->tracef("TMP: req=%s", $req);
199 78         140 return;
200 78         134 }
201 78         122  
202 78         122 # key = module_p, val = error resp or undef if successful
203             my %loadcache;
204             tie %loadcache, 'Tie::Cache', 200;
205 78         148  
206             my ($self, $req) = @_;
207              
208             my $pkg = $req->{-perl_package};
209              
210             # skip there is no module to load
211             return if !$pkg;
212              
213 65     65   110 # if we are instructed not to load any module, we just check via existence
214             # of packages
215 65         104 unless ($self->{load}) {
216             return if package_exists($pkg);
217             return err(500, "Package $pkg does not exist");
218 65 100       136 }
219              
220             my $module_p = $pkg;
221             $module_p =~ s!::!/!g;
222 64 50       137 $module_p .= ".pm";
223 0 0       0  
224 0         0 # module has been required before and successfully loaded
225             return if $INC{$module_p};
226              
227 64         94 # module has been required before and failed
228 64         209 return err(500, "Module $pkg has failed to load previously" .
229 64         105 $loadcache{$module_p} ?
230             ": $loadcache{$module_p}[0] - $loadcache{$module_p}[1]" :
231             "")
232 64 100       169 if exists($INC{$module_p});
233              
234             # use cache result (for caching errors, or packages like 'main' and 'CORE'
235             # where no modules for such packages exist)
236             return $loadcache{$module_p} if exists $loadcache{$module_p};
237              
238             # load and cache negative result
239 46 50       102 my $res;
    100          
240             {
241             my $fullpath = module_path(module=>$pkg, find_pmc=>0, find_prefix=>1);
242              
243 45 100       153 # when the module path does not exist, but the package does, we can
244             # ignore this error. for example: main, CORE, etc.
245             my $pkg_exists = package_exists($pkg);
246 41         255  
247             if (!$fullpath) {
248 41         49 last if $pkg_exists;
  41         110  
249             $res = [404, "Can't find module or prefix path for package $pkg"];
250             last;
251             } elsif ($fullpath !~ /\.pm$/) {
252 41         11754 last if $pkg_exists;
253             $res = [405, "Can only find a prefix path for package $pkg"];
254 41 100       683 last;
    100          
255 30 100       80 }
256 1         4 eval { require $module_p };
257 1         2 if ($@) {
258             die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
259 7 50       21 $res = [500, "Can't load module $pkg (probably compile error): $@"];
260 0         0 last;
261 0         0 }
262             # load is successful
263 4         9 if ($self->{after_load}) {
  4         3171  
264 4 100       8526 eval { $self->{after_load}($self, module=>$pkg) };
265 1 50       37 if ($@) {
266 1         5 die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
267 1         3 log_error("after_load for package $pkg dies: $@");
268             }
269             }
270 3 100       12 }
271 1         2 $loadcache{$module_p} = $res;
  1         3  
272 1 50       6 return $res;
273 0 0       0 }
274 0         0  
275             no strict 'refs';
276              
277             my ($req, $meta) = @_;
278 41         194  
279 41         416 my $pkg = $req->{-perl_package};
280             unless (defined $meta->{entity_v}) {
281             my $ver = ${"$pkg\::VERSION"};
282             if (defined $ver) {
283 3     3   22 $meta->{entity_v} = $ver;
  3         7  
  3         467  
284             }
285 44     44   115 }
286             unless (defined $meta->{entity_date}) {
287 44         77 my $date = ${"$pkg\::DATE"};
288 44 100       90 if (defined $date) {
289 38         48 $meta->{entity_date} = $date;
  38         118  
290 38 100       78 }
291 25         47 }
292             }
293              
294 44 100       93 no strict 'refs';
295 41         54  
  41         102  
296 41 100       78 my ($self, $req) = @_;
297 13         28  
298             my $pkg = $req->{-perl_package};
299             my $leaf = $req->{-uri_leaf};
300             my $type = $req->{-type};
301             if (!length($pkg)) {
302             if (length $leaf) {
303 3     3   17 # 404 for all non-subpackage entity directly under /
  3         7  
  3         1853  
304             return [404, "No metadata for ::$leaf (".
305 64     64 0 96 (package_exists($pkg) ? "package '$pkg' exists, perhaps you mentioned '$pkg' somewhere without actually loading the module, or perhaps '$leaf' is a typo?" :
306             "package '$pkg' doesn't exist, perhaps '$pkg' or '$leaf' is a typo?").
307 64         102 ")"
308 64         89 ];
309 64         94 } else {
310 64 100       131 # empty metadata for root (/)
311 5 100       12 $req->{-meta} = {v=>1.1};
312             return;
313 1 50       5 }
314             }
315              
316             my $name = "$pkg\::$leaf";
317             if ($self->{_meta_cache}{$name}) {
318             $req->{-meta} = $self->{_meta_cache}{$name};
319             $req->{-orig_meta} = $self->{_orig_meta_cache}{$name}
320 4         13 if $self->{_orig_meta_cache}{$name};
321 4         8 return;
322             }
323              
324             my $res = $self->_load_module($req);
325 59         111 # missing module (but existing prefix) is okay for package, we construct an
326 59 100       295 # empty package metadata for it
327 17         339 return $res if $res && !($type eq 'package' && $res->[0] == 405);
328              
329 17 50       231 my $meta;
330 17         39 my $metas = \%{"$pkg\::SPEC"};
331             $meta = $metas->{ $leaf || ":package" };
332              
333 42         723 if (!$meta && $type eq 'package') {
334             $meta = {v=>1.1};
335             }
336 42 100 33     271  
      66        
337             return err(404,
338 36         47 join("",
339 36         50 "No metadata for $name (package '$pkg' exists, ",
  36         123  
340 36   100     126 "perhaps you mentioned '$pkg' ",
341             "somewhere without actually loading the module, or ",
342 36 100 100     102 "perhaps '$leaf' is a typo?)",
343 6         15 )) unless $meta;
344              
345             if ($res) {
346 36 100       85 if ($res->[0] == 405) {
347             $meta = {v=>1.1}; # empty package metadata for dir
348             } elsif ($res->[0] != 200) {
349             return $res;
350             }
351             }
352              
353             # normalize has only been implemented for function
354 33 50       57 if ($type eq 'function' && $self->{normalize_metadata}) {
355 0 0       0 eval { $meta = normalize_function_metadata($meta) };
    0          
356 0         0 if ($@) {
357             die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
358 0         0 return [500, "Can't normalize function metadata: $@"];
359             }
360              
361             $meta->{args} //= {};
362             $meta->{_orig_args_as} = $meta->{args_as};
363 33 100 100     118 $meta->{args_as} = 'hash';
364 18         29 $meta->{_orig_result_naked} = $meta->{result_naked};
  18         55  
365 18 50       11410 $meta->{result_naked} = 0;
366 0 0       0 my $sfp = $self->{set_function_properties};
367 0         0 if ($sfp) {
368             for (keys %$sfp) {
369             $meta->{"_orig_$_"} = $meta->{$_};
370 18   100     63 $meta->{$_} = $sfp->{$_};
371 18         72 }
372 18         35 }
373 18         31 }
374 18         31  
375 18         30 __inject_entity_v_date($req, $meta);
376 18 50       39  
377 0         0 if ($self->{cache_size} > 0) {
378 0         0 $self->{_meta_cache}{$name} = $meta;
379 0         0 }
380              
381             $req->{-meta} = $meta;
382             return;
383             }
384 33         83  
385             my ($self, $req) = @_;
386 33 50       73  
387 33         134 # because we're lazy, we assume here that type is already function. it
388             # should be okay since get_code() is only called by action_call().
389              
390 33         1357 my $name = $req->{-perl_package} . "::" . $req->{-uri_leaf};
391 33         64 if ($self->{_code_cache}{$name}) {
392             $req->{-code} = $self->{_code_cache}{$name};
393             return;
394             }
395 19     19 0 38  
396             my $res = $self->_load_module($req);
397             return $res if $res;
398              
399             return err(404, "Can't find function $req->{-uri_leaf} in ".
400 19         47 "module $req->{-perl_package}")
401 19 100       75 unless defined &{$name};
402 6         129  
403 6         69 # we get our own meta and not use get_meta() because we want to get the
404             # original metadata
405             my $meta;
406 13         169 {
407 13 50       34 no strict 'refs';
408             my $metas = \%{"$req->{-perl_package}::SPEC"};
409             $meta = $metas->{ $req->{-uri_leaf} || ":package" };
410             }
411 13 50       17  
  13         51  
412             return err(404, "Can't find function metadata $req->{-uri_leaf} in ".
413             "module $req->{-perl_package}")
414             unless $meta;
415 13         25  
416             my $code;
417 3     3   22 GET_CODE:
  3         6  
  3         1009  
  13         20  
418 13         15 {
  13         45  
419 13   50     38 # we don't need to wrap
420             if (!$self->{wrap} ||
421             $meta->{"x.perinci.sub.wrapper.logs"} &&
422 13 50       32 (first {$_->{validate_args}}
423             @{ $meta->{"x.perinci.sub.wrapper.logs"} })
424             ) {
425             $code = \&{$name};
426 13         16 last GET_CODE;
427             }
428              
429             require Perinci::Sub::Wrapper;
430 13 100 66     20 my $sfp = $self->{set_function_properties};
  13   100     55  
431              
432 1     1   7 my $wrapres = Perinci::Sub::Wrapper::wrap_sub(
433 1         5 sub_name=>$name, meta=>$meta,
434             convert=>{args_as=>'hash', result_naked=>0,
435 2         3 ($sfp ? %$sfp : ())
  2         6  
436 2         5 });
437             return err(500, "Can't wrap function", $wrapres)
438             unless $wrapres->[0] == 200;
439 11         1491 $code = $wrapres->[2]{sub};
440 11         11376  
441             if ($self->{cache_size} > 0) {
442 11 50       64 $self->{_code_cache}{$name} = $code;
443             # also put wrapper-generated meta in the cache, so further meta
444             # request can use this. the metadata from wrapper contains wrapper
445             # logs (x.perinci.sub.wrapper.logs) which can be helpful hint for
446             # some uses.
447 11 50       172425 my $newmeta = $wrapres->[2]{meta};
448             $newmeta->{_orig_result_naked} = $meta->{result_naked};
449 11         28 $newmeta->{_orig_args_as} = $meta->{args_as};
450             if ($sfp) {
451 11 50       36 for (keys %$sfp) {
452 11         74 $newmeta->{"_orig_$_"} = $meta->{$_};
453             }
454             }
455             __inject_entity_v_date($req, $newmeta);
456             $self->{_meta_cache}{$name} = $newmeta;
457 11         463 }
458 11         23 }
459 11         19  
460 11 50       34 $req->{-code} = $code;
461 0         0 return;
462 0         0 }
463              
464             no strict 'refs';
465 11         30  
466 11         42 my ($self, $action, $uri, $extra) = @_;
467              
468             return err(400, "Please specify URI") unless $uri;
469              
470 13         603 my $req = { action=>$action, uri=>$uri, %{$extra // {}} };
471 13         28 my $res = $self->check_request($req);
472             return $res if $res;
473              
474             return err(501, "Action '$action' not implemented")
475 3     3   19 unless $self->can("actionmeta_$action");
  3         8  
  3         2413  
476              
477 85     85 1 214905 my $am = $self->${\("actionmeta_$action")};
478              
479 85 50       188 $res = $self->_parse_uri($req);
480             return $res if $res;
481 85   100     133  
  85         397  
482 85         319 return err(501, "Action '$action' not implemented for ".
483 85 50       2670 "'$req->{-type}' entity")
484             unless $am->{applies_to}[0] eq '*' ||
485 85 100       407 $req->{-type} ~~ @{ $am->{applies_to} };
486              
487             my $meth = "action_$action";
488 84         151 # check transaction
  84         302  
489              
490 84         202 $res = $self->$meth($req);
491 84 100       477 if ($self->{debug}) {
492             $res->[3] //= {};
493             $res->[3]{debug} = {
494             req => $req,
495             };
496 78 100 100     226 }
  36         151  
497             $res;
498 76         145 }
499              
500             my ($self, $uri) = @_;
501 76         241 die "Please specify url" unless $uri;
502 76 50       2285 my ($sch, $auth, $path) = uri_split($uri);
503 0   0     0 return {
504             # to mark that we are schemeless
505 0         0 proto=>'',
506             path=>$path,
507             };
508 76         417 }
509              
510             applies_to => ['*'],
511             summary => "Get general information on code entity",
512 2     2 1 8 needs_meta => 0,
513 2 50       6 needs_code => 0,
514 2         6 } }
515              
516             my ($self, $req) = @_;
517 2         25  
518             my $mres = $self->get_meta($req);
519             return $mres if $mres;
520              
521             my $res = {
522             uri => $req->{uri},
523 45     45 0 145 type => $req->{-type},
524             };
525              
526             [200, "OK (info action)", $res];
527             }
528              
529             applies_to => ['*'],
530 7     7 0 16 summary => "List available actions for code entity",
531             needs_meta => 0,
532 7         21 needs_code => 0,
533 7 100       16 } }
534              
535             my ($self, $req) = @_;
536              
537             my $mres = $self->get_meta($req);
538 6         18 return $mres if $mres;
539              
540 6         28 my @res;
541             for my $k (sort keys %{ $self->{_typeacts}{$req->{-type}} }) {
542             my $v = $self->{_typeacts}{$req->{-type}}{$k};
543             if ($req->{detail}) {
544 44     44 0 176 push @res, {name=>$k, summary=>$v->{summary}};
545             } else {
546             push @res, $k;
547             }
548             }
549             [200, "OK (actions action)", \@res];
550             }
551 8     8 0 18  
552             applies_to => ['package'],
553 8         15 summary => "List code entities inside this package code entity",
554 8 100       21 # this means, even if allow_path is '/a/b', we allow request on '/a' or '/'.
555             allow_request_parent_path => 1,
556 4         5 } }
557 4         5  
  4         66  
558 60         86 require Module::List;
559 60 50       110  
560 0         0 my ($self, $req) = @_;
561             my $detail = $req->{detail};
562 60         96 my $f_type = $req->{type} || "";
563              
564             my @res;
565 4         25  
566             my $filter_path = sub {
567             my $path = shift;
568             if (defined($self->{allow_paths}) &&
569 46     46 0 141 !__match_paths2($path, $self->{allow_paths})) {
570             return 0;
571             }
572             if (defined($self->{deny_paths}) &&
573             __match_paths2($path, $self->{deny_paths})) {
574             return 0;
575             }
576 10     10 0 877 1;
577             };
578 10         9893  
579 10         18 my %mem;
580 10   50     33  
581             # get submodules
582 10         15 unless ($f_type && $f_type ne 'package') {
583             my $lres = Module::List::list_modules(
584             $req->{-perl_package} ? "$req->{-perl_package}\::" : "",
585 537     537   631 {list_modules=>1, list_prefixes=>1});
586 537 100 100     1037 my $dir = $req->{-uri_dir};
587             for my $m (sort keys %$lres) {
588 328         637 $m =~ s!::$!!;
589             $m =~ s!.+::!!;
590 209 100 66     346 my $path = "$dir/$m/";
591             next unless $filter_path->($path);
592 49         95 next if $mem{$path}++;
593             if ($detail) {
594 160         277 push @res, {uri=>"$m/", type=>"package"};
595 10         33 } else {
596             push @res, "$m/";
597 10         17 }
598             }
599             }
600 10 50 33     28  
601             my $res = $self->_load_module($req);
602 10 100       57 return $res if $res && $res->[0] != 405;
603              
604 10         19369 # get all entities from this module
605 10         200 no strict 'refs';
606 409         786 my $spec = \%{"$req->{-perl_package}\::SPEC"};
607 409         637 my $dir = $req->{-uri_dir};
608 409         603 for my $e (sort keys %$spec) {
609 409 100       552 next if $e =~ /^:/;
610 62 100       133 my $path = "$dir/$e";
611 58 100       85 next unless $filter_path->($path);
612 18         46 next if $mem{$path}++;
613             my $t = $e =~ /^[%\@\$]/ ? 'variable' : 'function';
614 40         83 next if $f_type && $f_type ne $t;
615             if ($detail) {
616             push @res, {
617             uri=>$e, type=>$t,
618             };
619 10         51 } else {
620 10 100 66     64 push @res, $e;
621             }
622             }
623 3     3   22  
  3         6  
  3         3532  
624 9         13 [200, "OK (list action)", \@res];
  9         38  
625 9         19 }
626 9         108  
627 134 100       221 applies_to => ['*'],
628 128         201 summary => "Get metadata",
629 128 100       160 } }
630 98 50       211  
631 98 100       184 my ($self, $req) = @_;
632 98 50 33     156  
633 98 100       123 my $res = $self->get_meta($req);
634 30         65 return $res if $res;
635              
636             [200, "OK (meta action)", $req->{-meta}];
637             }
638 68         136  
639             applies_to => ['function'],
640             summary => "Call function",
641             } }
642 9         77  
643             require UUID::Random;
644              
645             my ($self, $req) = @_;
646 66     66 0 174  
647             my $res;
648              
649             my $tm; # = does client mention tx_id?
650             if (defined $req->{tx_id}) {
651 26     26 0 50 $res = $self->_pre_tx_action($req);
652             return $res if $res;
653 26         56 $tm = $self->{_tx_manager};
654 26 100       201 $tm->{_tx_id} = $req->{tx_id};
655             }
656 21         55  
657             $res = $self->get_meta($req);
658             return $res if $res;
659             $res = $self->get_code($req);
660 56     56 0 144 return $res if $res;
661              
662             my %args;
663              
664             # try to convert from argv if given argv
665 19     19 0 513 if (exists($req->{argv}) && $self->{accept_argv}) {
666             require Perinci::Sub::GetArgs::Argv;
667 19         212 $res = Perinci::Sub::GetArgs::Argv::get_args_from_argv(
668             argv => [@{ $req->{argv} }],
669 19         29 meta => $req->{-meta},
670             );
671             return err(400, "Can't parse argv", $res) unless $res->[0] == 200;
672 19 50       45 %args = %{ $res->[2] };
673 0         0 } else {
674 0 0       0 %args = %{ $req->{args} // {} };
675 0         0 }
676 0         0  
677             my $risub = risub($req->{-meta});
678              
679 19         43 if ($req->{dry_run}) {
680 19 50       40 return err(412, "Function does not support dry run")
681 19         42 unless $risub->can_dry_run;
682 19 50       45 if ($risub->feature('dry_run')) {
683             $args{-dry_run} = 1;
684 19         28 } else {
685             $args{-dry_run} = 1;
686             $args{-tx_action} = 'check_state';
687 19 100 66     65 $args{-tx_action_id} = UUID::Random::generate();
688 2         1817 undef $tm;
689             }
690 2         9 }
691              
692 2         13899 if ($risub->feature('progress')) {
693 2 100       12013 require Progress::Any;
694 1         3 $args{-progress} = Progress::Any->get_indicator();
  1         4  
695             }
696 17   100     26  
  17         83  
697             if ($tm) {
698             $res = $tm->action(
699 18         70 f => "$req->{-perl_package}::$req->{-uri_leaf}", args=>\%args,
700             confirm => $req->{confirm},
701 18 100       4285 );
702 3 100       9 $tm->{_tx_id} = undef if $tm;
703             } else {
704 2 100       20 $args{-confirm} = 1 if $req->{confirm};
705 1         10 eval { $res = $req->{-code}->(%args) };
706             if ($@) {
707 1         9 die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
708 1         2 $res = err(500, "Function died: $@");
709 1         4 }
710 1         40 }
711              
712             # add hint that result is binary
713             if (defined $res->[2]) {
714 17 100       88 if ($req->{-meta}{result} && $req->{-meta}{result}{schema} &&
715 1         1617 $req->{-meta}{result}{schema}[0] eq 'buf') {
716 1         5645 $res->[3]{'x.hint.result_binary'} = 1;
717             }
718             }
719 17 50       285  
720             $res;
721             }
722              
723 0         0 applies_to => ['function'],
724 0 0       0 summary => "Complete function's argument value"
725             } }
726 17 100       41  
727 17         27 require Perinci::Sub::Complete;
  17         368  
728 17 100       701  
729 1 50       5 my ($self, $req) = @_;
730 1         6 my $arg = $req->{arg} or return err(400, "Please specify arg");
731             my $word = $req->{word} // "";
732             my $ci = $req->{ci};
733              
734             my $res = $self->get_meta($req);
735 17 100       92 return $res if $res;
736 7 50 66     45 [200, "OK (complete_arg_val action)",
      33        
737             Perinci::Sub::Complete::complete_arg_val(meta=>$req->{-meta}, word=>$word,
738 0         0 arg=>$arg, ci=>$ci) // []];
739             }
740              
741             applies_to => ['function'],
742 17         51 summary => "Complete function's argument element value"
743             } }
744              
745             require Perinci::Sub::Complete;
746 41     41 0 119  
747             my ($self, $req) = @_;
748             my $arg = $req->{arg} or return err(400, "Please specify arg");
749             defined(my $index = $req->{index})
750             or return err(400, "Please specify index");
751 5     5 0 1612 my $word = $req->{word} // "";
752             my $ci = $req->{ci};
753 5         13998  
754 5 100       18 my $res = $self->get_meta($req);
755 4   100     12 return $res if $res;
756 4         4 [200, "OK (complete_arg_elem action)",
757             Perinci::Sub::Complete::complete_arg_elem(
758 4         10 meta=>$req->{-meta}, word=>$word, arg=>$arg, ci=>$ci, index=>$index,
759 4 50       11 ) // []];
760             }
761 4   100     12  
762             applies_to => ['package'],
763             summary => "Get metadata of all child entities",
764             } }
765              
766 36     36 0 93 my ($self, $req) = @_;
767              
768             my $res = $self->action_list($req);
769             return $res unless $res->[0] == 200;
770             my $ents = $res->[2];
771 0     0 0 0  
772             my %res;
773 0         0 my %om;
774 0 0       0 my $base = uri_join(
775             $req->{-uri_scheme}, $req->{-uri_auth}, $req->{-uri_dir});
776 0 0       0  
777 0   0     0 for my $ent (@$ents) {
778 0         0 $res = $self->request(meta => "$base/$ent");
779             # ignore failed request
780 0         0 next unless $res->[0] == 200;
781 0 0       0 $res{$ent} = $res->[2];
782             }
783             [200, "OK (child_metas action)", \%res];
784 0   0     0 }
785              
786             applies_to => ['variable'],
787             summary => "Get value of variable",
788             } }
789 37     37 0 101  
790             no strict 'refs';
791              
792             my ($self, $req) = @_;
793             local $req->{-uri_leaf} = $req->{-uri_leaf};
794 1     1 0 3  
795             # extract prefix
796 1         4 $req->{-uri_leaf} =~ s/^([%\@\$])//
797 1 50       5 or return err(500, "BUG: Unknown variable prefix");
798 1         3 my $prefix = $1;
799             my $name = $req->{-perl_package} . "::" . $req->{-uri_leaf};
800 1         3 my $res =
801             $prefix eq '$' ? ${$name} :
802             $prefix eq '@' ? \@{$name} :
803 1         5 $prefix eq '%' ? \%{$name} :
804             undef;
805 1         22 [200, "OK (get action)", $res];
806 6         22 }
807              
808 6 50       13 my ($self, $req) = @_;
809 6         12  
810             return err(501, "Transaction not supported by server")
811 1         5 unless $self->{use_tx};
812              
813             # instantiate custom tx manager, per request if necessary
814             if (ref($self->{custom_tx_manager}) eq 'CODE') {
815 36     36 0 92 eval {
816             $self->{_tx_manager} = $self->{custom_tx_manager}->($self);
817             die $self->{_tx_manager} unless blessed($self->{_tx_manager});
818             };
819             if ($@) {
820 3     3   22 die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
  3         6  
  3         3363  
821             return err(500, "Can't initialize custom tx manager: ".
822 0     0 0 0 "$self->{_tx_manager}: $@");
823 0         0 }
824             } elsif (!blessed($self->{_tx_manager})) {
825             my $tm_cl = $self->{custom_tx_manager} // "Perinci::Tx::Manager";
826 0 0       0 my $tm_cl_p = $tm_cl; $tm_cl_p =~ s!::!/!g; $tm_cl_p .= ".pm";
827             eval {
828 0         0 require $tm_cl_p;
829 0         0 $self->{_tx_manager} = $tm_cl->new(pa => $self);
830             die $self->{_tx_manager} unless blessed($self->{_tx_manager});
831 0         0 };
832 0         0 if ($@) {
833 0 0       0 die if $ENV{PERINCI_ACCESS_SCHEMELESS_DEBUG};
  0 0       0  
    0          
834             return err(500, "Can't initialize tx manager ($tm_cl): $@");
835 0         0 }
836             # we just want to force newer version, we currently can't specify this
837             # in Makefile.PL because peritm's tests use us. this might be rectified
838             # in the future.
839 0     0   0 if ($tm_cl eq 'Perinci::Tx::Manager') {
840             $Perinci::Tx::Manager::VERSION >= 0.29
841             or die "Your Perinci::Tx::Manager is too old, ".
842 0 0       0 "please install v0.29 or later";
843             }
844             }
845 0 0       0  
    0          
846 0         0 return;
847 0         0 }
848 0 0       0  
849             applies_to => ['*'],
850 0 0       0 summary => "Start a new transaction",
851 0 0       0 } }
852 0         0  
853             my ($self, $req) = @_;
854             my $res = $self->_pre_tx_action($req);
855             return $res if $res;
856 0   0     0  
857 0         0 $self->{_tx_manager}->begin(
  0         0  
  0         0  
858 0         0 tx_id => $req->{tx_id},
859 0         0 summary => $req->{summary},
860 0         0 );
861 0 0       0 }
862              
863 0 0       0 applies_to => ['*'],
864 0 0       0 summary => "Commit a transaction",
865 0         0 } }
866              
867             my ($self, $req) = @_;
868             my $res = $self->_pre_tx_action($req);
869             return $res if $res;
870 0 0       0  
871 0 0       0 $self->{_tx_manager}->commit(
872             tx_id => $req->{tx_id},
873             );
874             }
875              
876             applies_to => ['*'],
877 0         0 summary => "Create a savepoint in a transaction",
878             } }
879              
880             my ($self, $req) = @_;
881 36     36 0 100 my $res = $self->_pre_tx_action($req);
882             return $res if $res;
883              
884             $self->{_tx_manager}->savepoint(
885             tx_id => $req->{tx_id},
886 0     0 0 0 sp => $req->{tx_spid},
887 0         0 );
888 0 0       0 }
889              
890             applies_to => ['*'],
891             summary => "Release a transaction savepoint",
892             } }
893 0         0  
894             my ($self, $req) =\ @_;
895             my $res = $self->_pre_tx_action($req);
896             return $res if $res;
897 36     36 0 107  
898             $self->{_tx_manager}->release_savepoint(
899             tx_id => $req->{tx_id},
900             sp => $req->{tx_spid},
901             );
902 0     0 0 0 }
903 0         0  
904 0 0       0 applies_to => ['*'],
905             summary => "Rollback a transaction (optionally to a savepoint)",
906             } }
907              
908 0         0 my ($self, $req) = @_;
909             my $res = $self->_pre_tx_action($req);
910             return $res if $res;
911              
912 36     36 0 86 $self->{_tx_manager}->rollback(
913             tx_id => $req->{tx_id},
914             sp => $req->{tx_spid},
915             );
916             }
917 0     0 0 0  
918 0         0 applies_to => ['*'],
919 0 0       0 summary => "List transactions",
920             } }
921              
922             my ($self, $req) = @_;
923             my $res = $self->_pre_tx_action($req);
924 0         0 return $res if $res;
925              
926             $self->{_tx_manager}->list(
927             detail => $req->{detail},
928 36     36 0 102 tx_status => $req->{tx_status},
929             tx_id => $req->{tx_id},
930             );
931             }
932              
933 0     0 0 0 applies_to => ['*'],
934 0         0 summary => "Undo a committed transaction",
935 0 0       0 } }
936              
937             my ($self, $req) = @_;
938             my $res = $self->_pre_tx_action($req);
939             return $res if $res;
940 0         0  
941             $self->{_tx_manager}->undo(
942             tx_id => $req->{tx_id},
943             confirm => $req->{confirm},
944 36     36 0 104 );
945             }
946              
947             applies_to => ['*'],
948             summary => "Redo an undone committed transaction",
949 0     0 0 0 } }
950 0         0  
951 0 0       0 my ($self, $req) = @_;
952             my $res = $self->_pre_tx_action($req);
953             return $res if $res;
954              
955             $self->{_tx_manager}->redo(
956 0         0 tx_id => $req->{tx_id},
957             confirm => $req->{confirm},
958             );
959             }
960 36     36 0 107  
961             applies_to => ['*'],
962             summary => "Discard (forget) a committed transaction",
963             } }
964              
965 0     0 0 0 my ($self, $req) = @_;
966 0         0 my $res = $self->_pre_tx_action($req);
967 0 0       0 return $res if $res;
968              
969             $self->{_tx_manager}->discard(
970             tx_id => $req->{tx_id},
971             );
972             }
973 0         0  
974             applies_to => ['*'],
975             summary => "Discard (forget) all committed transactions",
976             } }
977 36     36 0 95  
978             my ($self, $req) = @_;
979             my $res = $self->_pre_tx_action($req);
980             return $res if $res;
981              
982 0     0 0 0 $self->{_tx_manager}->discard_all(
983 0         0 # XXX select client
984 0 0       0 );
985             }
986              
987             1;
988             # ABSTRACT: Base class for Perinci::Access::Perl
989 0         0  
990              
991             =pod
992              
993 36     36 0 103 =encoding UTF-8
994              
995             =head1 NAME
996              
997             Perinci::Access::Schemeless - Base class for Perinci::Access::Perl
998 0     0 0 0  
999 0         0 =head1 VERSION
1000 0 0       0  
1001             This document describes version 0.897 of Perinci::Access::Schemeless (from Perl distribution Perinci-Access-Perl), released on 2021-08-01.
1002              
1003             =head1 DESCRIPTION
1004              
1005 0         0 This class is the base class for L<Perinci::Access::Perl>, and by default acts
1006             like Perinci::Access::Perl (e.g. given uri C</Foo/Bar/baz> it will refer to
1007             function C<baz> in Perl package C<Foo::Bar>; it also looks for Rinci metadata in
1008             C<%SPEC> package variables by default). But this class is designed to be
1009 36     36 0 90 flexible: you can override aspects of it so it can map uri to different Perl
1010             packages (e.g. using option like C<package_prefix>), you can retrieve Rinci
1011             metadata from a database or whatever, etc.
1012              
1013             Supported features:
1014 0     0 0 0  
1015 0         0 =over
1016 0 0       0  
1017             =item * Basic Riap actions
1018              
1019             These include C<info>, C<actions>, C<meta>, C<list>, and C<call> actions.
1020 0         0  
1021             =item * Transaction/undo
1022              
1023             According to L<Rinci::Transaction>.
1024 36     36 0 119  
1025             =item * Function wrapping
1026              
1027             Wrapping is used to convert argument passing style, produce result envelope, add
1028             argument validation, as well as numerous other functionalities. See
1029 0     0 0   L<Perinci::Sub::Wrapper> for more details on wrapping. The default behavior will
1030 0           call wrapped functions.
1031 0 0          
1032             =item * Custom location of metadata
1033              
1034             By default, metadata are assumed to be stored embedded in Perl source code in
1035 0           C<%SPEC> package variables (with keys matching function names, C<$variable>
1036             names, or C<:package> for the package metadata itself).
1037              
1038             You can override C<get_meta()> to provide custom behavior. For example, you can
1039             store metadata in separate file or database.
1040              
1041             =item * Custom code entity tree
1042              
1043             By default, tree are formed by traversing Perl packages and their contents, for
1044             example if a C<list> action is requested on uri C</Foo/Bar/> then the contents
1045             of package C<Foo::Bar> and its subpackages will be traversed for the entities.
1046              
1047             You can override C<action_list()> to provide custom behavior. For example, you
1048             can lookup from the database.
1049              
1050             =item * Progress indicator
1051              
1052             Functions can express that they do progress updating through the C<features>
1053             property in its metadata:
1054              
1055             features => {
1056             progress => 1,
1057             ...
1058             }
1059              
1060             For these functions, this class will pass a special argument C<-progress>
1061             containing L<Progress::Any> object. Functions can update progress using this
1062             object.
1063              
1064             =back
1065              
1066             =head2 How request is processed
1067              
1068             User calls C<< $pa->request($action => $uri, \%extras) >>. Internally, the
1069             method creates a hash C<$req> which contains Riap request keys as well as
1070             internal information about the Riap request (the latter will be prefixed with
1071             dash C<->). Initially it will contain C<action> and C<uri> and the C<%extras>
1072             keys from the request() arguments sent by the user.
1073              
1074             Internal C<_parse_uri()> method will be called to parse C<uri> into C<-uri_dir>
1075             (the "dir" part), C<-uri_leaf> (the "basename" part), and C<-perl_package>.
1076             Forbidden or invalid paths will cause this method to return an enveloped error
1077             response and the request to stop. For example, if C<uri> is C</Foo/Bar/> then
1078             C<-uri_dir> is C</Foo/Bar/> and C<-uri_leaf> is an empty string. If C<uri> is
1079             C</Foo/Bar/baz> then C<-uri_dir> is C</Foo/Bar/> while C<-uri_leaf> is C<baz>.
1080             C<-uri_dir> will be used for the C<list> action. In both cases, C<-perl_package>
1081             will be set to C<Foo::Bar>.
1082              
1083             The code entity type is then determined currently using a few simple heuristic
1084             rules: if C<-uri_leaf> is empty string, type is C<package>. If C<-uri_leaf>
1085             begins with C<[$%@]>, type is C<variable>. Otherwise, type is C<function>.
1086             C<-type> will be set.
1087              
1088             After this, the appropriate C<action_ACTION()> method will be called. For
1089             example if action is C<meta> then C<action_meta()> method will be called, with
1090             C<$req> as the argument. This will in turn, depending on the action, either call
1091             C<get_meta()> (for example if action is C<meta>) or C<get_code()> (for example
1092             if action is C<call>), also with C<$req> as the argument. C<get_meta()> and
1093             C<get_code()> should return nothing on success, and set either C<-meta> (a
1094             defhash containing Rinci metadata) or C<-code> (a coderef), respectively. On
1095             error, they must return an enveloped error response.
1096              
1097             C<get_meta()> or C<get_code()> might call C<_load_module()> to load Perl modules
1098             if the C<load> attribute is set to true.
1099              
1100             =for Pod::Coverage ^(actionmeta_.+|action_.+|get_(meta|code))$
1101              
1102             =head1 ADDED RESULT METADATA
1103              
1104             This class might add the following property/attribute in result metadata:
1105              
1106             =head2 x.hint.result_binary => bool
1107              
1108             If result's schema type is C<buf>, then this class will set this attribute to
1109             true, to give hints to result formatters.
1110              
1111             =head1 METHODS
1112              
1113             =head2 PKG->new(%attrs) => OBJ
1114              
1115             Instantiate object. Known attributes:
1116              
1117             =over 4
1118              
1119             =item * load => BOOL (default: 1)
1120              
1121             Whether to load Perl modules that are requested.
1122              
1123             =item * after_load => CODE
1124              
1125             If set, code will be executed the first time Perl module is successfully loaded.
1126              
1127             =item * wrap => BOOL (default: 1)
1128              
1129             If set to false, then wil use original subroutine and metadata instead of
1130             wrapped ones, for example if you are very concerned about performance (do not
1131             want to add another eval {} and subroutine call introduced by wrapping) or do
1132             not need the functionality provided by the wrapper (e.g. your function already
1133             validates its arguments, accepts arguments as hash, and returns enveloped
1134             result).
1135              
1136             Wrapping is implemented inside C<get_code()>.
1137              
1138             =item * set_function_properties => HASH
1139              
1140             If set, will be passed to L<Perinci::Sub::Wrapper> wrap_sub()'s C<convert>
1141             argument when wrapping subroutines. Some applications of this include: changing
1142             C<default_lang> of metadata.
1143              
1144             This is only relevant if you enable C<wrap>.
1145              
1146             =item * cache_size => INT (default: 100)
1147              
1148             Specify cache size (in number of items), for caching metadata and wrapping
1149             result. Setting this to 0 disables caching.
1150              
1151             Caching is implemented inside C<get_meta()> and C<get_code()> so you might want
1152             to implement your own caching if you override those.
1153              
1154             =item * allow_paths => REGEX|STR|ARRAY
1155              
1156             If defined, only requests with C<uri> matching specified path will be allowed.
1157             Can be a string (e.g. C</spanel/api/>) or regex (e.g. C<< qr{^/[^/]+/api/} >>)
1158             or an array of those.
1159              
1160             =item * deny_paths => REGEX|STR|ARRAY
1161              
1162             If defined, requests with C<uri> matching specified path will be denied. Like
1163             C<allow_paths>, value can be a string (e.g. C</spanel/api/>) or regex (e.g. C<<
1164             qr{^/[^/]+/api/} >>) or an array of those.
1165              
1166             =item * allow_schemes => REGEX|STR|ARRAY
1167              
1168             By default this class does not care about schemes, it only looks at the uri
1169             path. You can use this option to limit allowed schemes.
1170              
1171             =item * deny_schemes => REGEX|STR|ARRAY
1172              
1173             By default this class does not care about schemes, it only looks at the uri
1174             path. You can use this option to specify forbidden schemes.
1175              
1176             =item * use_tx => BOOL (default: 0)
1177              
1178             Whether to allow transaction requests from client. Since this can cause the
1179             server to store transaction/undo data, this must be explicitly allowed.
1180              
1181             You need to install L<Perinci::Tx::Manager> for transaction support (unless you
1182             are using another transaction manager).
1183              
1184             =item * custom_tx_manager => STR|CODE
1185              
1186             Can be set to a string (class name) or a code that is expected to return a
1187             transaction manager class.
1188              
1189             By default, L<Perinci::Tx::Manager> is instantiated and maintained (not
1190             reinstantiated on every request), but if C<custom_tx_manager> is a coderef, it
1191             will be called on each request to get transaction manager. This can be used to
1192             instantiate Perinci::Tx::Manager in a custom way, e.g. specifying per-user
1193             transaction data directory and limits, which needs to be done on a per-request
1194             basis.
1195              
1196             =item * accept_argv => BOOL (default: 1)
1197              
1198             From version 0.64, C<argv> key is accepted by the C<call> action and will be
1199             converted to C<args>. This server-side conversion from C<argv> to <args> can
1200             handle coderefs in C<cmdline_aliases> (and probably other things too) compared
1201             when doing conversion at the client-side.
1202              
1203             This option allows disabling this behavior.
1204              
1205             =back
1206              
1207             =head2 $pa->request($action => $server_url, \%extra) => $res
1208              
1209             Process Riap request and return enveloped result. $server_url will be used as
1210             the Riap request key 'uri', as there is no server in this case.
1211              
1212             =head2 $pa->parse_url($server_url) => HASH
1213              
1214             =head1 ENVIRONMENT
1215              
1216             =head2 PERINCI_ACCESS_SCHEMELESS_DEBUG_DIE
1217              
1218             Bool. If set to true, will not return exception (e.g. function dies when
1219             executed) as status in result envelope; but instead will rethrow the error. This
1220             will make checking the error (e.g. with L<Devel::Confess> using C<-d:Confess>
1221             perl flag) slightly easier.
1222              
1223             =head1 HOMEPAGE
1224              
1225             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-Access-Perl>.
1226              
1227             =head1 SOURCE
1228              
1229             Source repository is at L<https://github.com/perlancar/perl-Perinci-Access-Perl>.
1230              
1231             =head1 BUGS
1232              
1233             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Access-Perl>
1234              
1235             When submitting a bug or request, please include a test-file or a
1236             patch to an existing test-file that illustrates the bug or desired
1237             feature.
1238              
1239             =head1 SEE ALSO
1240              
1241             L<Riap>, L<Rinci>
1242              
1243             =head1 AUTHOR
1244              
1245             perlancar <perlancar@cpan.org>
1246              
1247             =head1 COPYRIGHT AND LICENSE
1248              
1249             This software is copyright (c) 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar@cpan.org.
1250              
1251             This is free software; you can redistribute it and/or modify it under
1252             the same terms as the Perl 5 programming language system itself.
1253              
1254             =cut