File Coverage

blib/lib/Perinci/Access/Lite.pm
Criterion Covered Total %
statement 92 130 70.7
branch 28 68 41.1
condition 20 41 48.7
subroutine 12 12 100.0
pod 2 2 100.0
total 154 253 60.8


line stmt bran cond sub pod time code
1             package Perinci::Access::Lite;
2              
3             our $DATE = '2015-12-17'; # DATE
4             our $VERSION = '0.12'; # VERSION
5              
6 1     1   744 use 5.010001;
  1         3  
7 1     1   5 use strict;
  1         1  
  1         20  
8 1     1   5 use warnings;
  1         1  
  1         26  
9              
10 1     1   5632 use Perinci::AccessUtil qw(strip_riap_stuffs_from_res);
  1         2477  
  1         118  
11              
12             sub new {
13 1     1 1 14 my ($class, %args) = @_;
14 1   50     9 $args{riap_version} //= 1.1;
15 1         4 bless \%args, $class;
16             }
17              
18             # copy-pasted from SHARYANTO::Package::Util
19             sub __package_exists {
20 1     1   7 no strict 'refs';
  1         2  
  1         164  
21              
22 3     3   6 my $pkg = shift;
23              
24 3 50       15 return unless $pkg =~ /\A\w+(::\w+)*\z/;
25 3 50       17 if ($pkg =~ s/::(\w+)\z//) {
26 3         5 return !!${$pkg . "::"}{$1 . "::"};
  3         20  
27             } else {
28 0         0 return !!$::{$pkg . "::"};
29             }
30             }
31              
32             sub request {
33 1     1   6 no strict 'refs';
  1         2  
  1         282  
34              
35 7     7 1 12444 my ($self, $action, $url, $extra) = @_;
36              
37             #say "D:request($action => $url)";
38              
39 7   50     22 $extra //= {};
40              
41 7   50     38 my $v = $extra->{v} // 1.1;
42 7 50 33     63 if ($v ne '1.1' && $v ne '1.2') {
43 0         0 return [501, "Riap protocol not supported, must be 1.1 or 1.2"];
44             }
45              
46 7         10 my $res;
47 7 50       60 if ($url =~ m!\A(?:pl:)?/(\w+(?:/\w+)*)/(\w*)\z!) {
    0          
48 7         27 my ($mod_uripath, $func) = ($1, $2);
49 7         29 (my $pkg = $mod_uripath) =~ s!/!::!g;
50 7         16 my $mod_pm = "$mod_uripath.pm";
51              
52 7         10 my $pkg_exists;
53              
54             LOAD:
55             {
56 7 100       9 last if exists $INC{$mod_pm};
  7         27  
57 3         9 $pkg_exists = __package_exists($pkg);
58             # special names
59 3 50       11 last LOAD if $pkg =~ /\A(main)\z/;
60 3 50 33     8 last if $pkg_exists && defined(${"$pkg\::VERSION"});
  0         0  
61             #say "D:Loading $pkg ...";
62 3         5 eval { require $mod_pm };
  3         3109149  
63 3 100       1551940 return [500, "Can't load module $pkg: $@"] if $@;
64             }
65              
66 6 100 66     46 if ($action eq 'list') {
    50          
    50          
67 2 100       18 return [501, "Action 'list' not implemented for ".
68             "non-package entities"]
69             if length($func);
70 1     1   6 no strict 'refs';
  1         2  
  1         262  
71 1         4 my $spec = \%{"$pkg\::SPEC"};
  1         8  
72 1         13 return [200, "OK (list)", [grep {/\A\w+\z/} sort keys %$spec]];
  2         26  
73             } elsif ($action eq 'info') {
74 0 0       0 my $data = {
    0          
    0          
75             uri => "$mod_uripath/$func",
76             type => (!length($func) ? "package" :
77             $func =~ /\A\w+\z/ ? "function" :
78             $func =~ /\A[\@\$\%]/ ? "variable" :
79             "?"),
80             };
81 0         0 return [200, "OK (info)", $data];
82             } elsif ($action eq 'meta' || $action eq 'call') {
83 4 100 100     22 return [501, "Action 'call' not implemented for package entity"]
84             if !length($func) && $action eq 'call';
85 3         6 my $meta;
86             {
87 1     1   5 no strict 'refs';
  1         2  
  1         390  
  3         41  
88 3 100       9 if (length $func) {
89 2 0       3 $meta = ${"$pkg\::SPEC"}{$func}
  2 50       15  
90             or return [
91             500, "No metadata for '$url' (".
92             ($pkg_exists ? "package '$pkg' exists, perhaps you mentioned '$pkg' somewhere without actually loading the module, or perhaps '$func' is a typo?" :
93             "package '$pkg' doesn't exist, perhaps '$mod_uripath' or '$func' is a typo?") .
94             ")"];
95             } else {
96 1   50     2 $meta = ${"$pkg\::SPEC"}{':package'} // {v=>1.1};
  1         11  
97             }
98 3   66     14 $meta->{entity_v} //= ${"$pkg\::VERSION"};
  2         12  
99 3   66     11 $meta->{entity_date} //= ${"$pkg\::DATE"};
  2         11  
100             }
101              
102 3         1063 require Perinci::Sub::Normalize;
103 3         1229360 $meta = Perinci::Sub::Normalize::normalize_function_metadata($meta);
104 3 100       706462 if ($action eq 'meta') {
105 2         5 $meta->{_orig_args_as} = $meta->{args_as};
106 2         6 $meta->{args_as} = 'hash';
107 2         4 $meta->{_orig_result_naked} = $meta->{result_naked};
108 2         5 $meta->{result_naked} = 0;
109 2         10 return [200, "OK ($action)", $meta];
110             }
111              
112             # form args (and add special args)
113 1   50     3 my $args = { %{$extra->{args} // {}} }; # shallow copy
  1         17  
114 1 0 33     4 if ($meta->{features} && $meta->{features}{progress}) {
115 0         0 require Progress::Any;
116 0         0 $args->{-progress} = Progress::Any->get_indicator;
117             }
118              
119             # convert args
120 1   50     9 my $aa = $meta->{args_as} // 'hash';
121 1         2 my @args;
122 1 50       8 if ($aa =~ /array/) {
    50          
123 0         0 require Perinci::Sub::ConvertArgs::Array;
124 0         0 my $convres = Perinci::Sub::ConvertArgs::Array::convert_args_to_array(
125             args => $args, meta => $meta,
126             );
127 0 0       0 return $convres unless $convres->[0] == 200;
128 0 0       0 if ($aa =~ /ref/) {
129 0         0 @args = ($convres->[2]);
130             } else {
131 0         0 @args = @{ $convres->[2] };
  0         0  
132             }
133             } elsif ($aa eq 'hashref') {
134 0         0 @args = ({ %$args });
135             } else {
136             # hash
137 1         4 @args = %$args;
138             }
139              
140             # call!
141             {
142 1     1   5 no strict 'refs';
  1         2  
  1         686  
  1         2  
143 1         2 $res = &{"$pkg\::$func"}(@args);
  1         11  
144             }
145              
146             # add envelope
147 1 50       69 if ($meta->{result_naked}) {
148 0         0 $res = [200, "OK (envelope added by ".__PACKAGE__.")", $res];
149             }
150              
151             # add hint that result is binary
152 1 50       5 if (defined $res->[2]) {
153 1 50 33     26 if ($meta->{result} && $meta->{result}{schema} &&
      33        
154             $meta->{result}{schema}[0] eq 'buf') {
155 0         0 $res->[3]{'x.hint.result_binary'} = 1;
156             }
157             }
158              
159             } else {
160 0         0 return [501, "Unknown/unsupported action '$action'"];
161             }
162             } elsif ($url =~ m!\Ahttps?:/(/?)!i) {
163 0         0 my $is_unix = !$1;
164 0         0 my $ht;
165 0         0 require JSON;
166 0         0 state $json = JSON->new->allow_nonref;
167 0 0       0 if ($is_unix) {
168 0         0 require HTTP::Tiny::UNIX;
169 0         0 $ht = HTTP::Tiny::UNIX->new;
170             } else {
171 0         0 require HTTP::Tiny;
172 0         0 $ht = HTTP::Tiny->new;
173             }
174             my %headers = (
175             "x-riap-v" => $self->{riap_version},
176 0         0 "x-riap-action" => $action,
177             "x-riap-fmt" => "json",
178             "content-type" => "application/json",
179             );
180 0   0     0 my $args = $extra->{args} // {};
181 0         0 for (keys %$extra) {
182 0 0       0 next if /\Aargs\z/;
183 0         0 $headers{"x-riap-$_"} = $extra->{$_};
184             }
185 0         0 my $htres = $ht->post(
186             $url, {
187             headers => \%headers,
188             content => $json->encode($args),
189             });
190             return [500, "Network error: $htres->{status} - $htres->{reason}"]
191 0 0       0 if $htres->{status} != 200;
192             return [500, "Server error: didn't return JSON (".$htres->{headers}{'content-type'}.")"]
193 0 0       0 unless $htres->{headers}{'content-type'} eq 'application/json';
194             return [500, "Server error: didn't return Riap 1.1 response (".$htres->{headers}{'x-riap-v'}.")"]
195 0 0       0 unless $htres->{headers}{'x-riap-v'} =~ /\A1\.1(\.\d+)?\z/;
196 0         0 $res = $json->decode($htres->{content});
197             } else {
198 0         0 return [501, "Unsupported scheme or bad URL '$url'"];
199             }
200              
201 1         6 strip_riap_stuffs_from_res($res);
202             }
203              
204             1;
205             # ABSTRACT: A lightweight Riap client library
206              
207             __END__