File Coverage

blib/lib/Perinci/Access/Lite.pm
Criterion Covered Total %
statement 87 125 69.6
branch 28 68 41.1
condition 20 41 48.7
subroutine 12 12 100.0
pod 2 2 100.0
total 149 248 60.0


line stmt bran cond sub pod time code
1             package Perinci::Access::Lite;
2              
3             our $DATE = '2015-09-03'; # DATE
4             our $VERSION = '0.11'; # VERSION
5              
6 1     1   723 use 5.010001;
  1         4  
7 1     1   5 use strict;
  1         2  
  1         20  
8 1     1   4 use warnings;
  1         2  
  1         23  
9              
10 1     1   681 use Perinci::AccessUtil qw(strip_riap_stuffs_from_res);
  1         2101  
  1         101  
11              
12             sub new {
13 1     1 1 12 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         1  
  1         187  
21              
22 3     3   7 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         21  
27             } else {
28 0         0 return !!$::{$pkg . "::"};
29             }
30             }
31              
32             sub request {
33 1     1   5 no strict 'refs';
  1         2  
  1         277  
34              
35 7     7 1 12330 my ($self, $action, $url, $extra) = @_;
36              
37             #say "D:request($action => $url)";
38              
39 7   50     21 $extra //= {};
40              
41 7   50     37 my $v = $extra->{v} // 1.1;
42 7 50 33     62 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         23 my ($mod_uripath, $func) = ($1, $2);
49 7         27 (my $pkg = $mod_uripath) =~ s!/!::!g;
50 7         13 my $mod_pm = "$mod_uripath.pm";
51              
52 7         11 my $pkg_exists;
53              
54             LOAD:
55             {
56 7 100       8 last if exists $INC{$mod_pm};
  7         22  
57 3         8 $pkg_exists = __package_exists($pkg);
58             # special names
59 3 50       10 last LOAD if $pkg =~ /\A(main)\z/;
60 3 50 33     10 last if $pkg_exists && defined(${"$pkg\::VERSION"});
  0         0  
61             #say "D:Loading $pkg ...";
62 3         5 eval { require $mod_pm };
  3         2758  
63 3 100       14211 return [500, "Can't load module $pkg: $@"] if $@;
64             }
65              
66 6 100 66     44 if ($action eq 'list') {
    50          
    50          
67 2 100       10 return [501, "Action 'list' not implemented for ".
68             "non-package entities"]
69             if length($func);
70 1     1   5 no strict 'refs';
  1         2  
  1         219  
71 1         2 my $spec = \%{"$pkg\::SPEC"};
  1         5  
72 1         5 return [200, "OK (list)", [grep {/\A\w+\z/} sort keys %$spec]];
  2         12  
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     23 return [501, "Action 'call' not implemented for package entity"]
84             if !length($func) && $action eq 'call';
85 3         4 my $meta;
86             {
87 1     1   4 no strict 'refs';
  1         1  
  1         364  
  3         27  
88 3 100       9 if (length $func) {
89 2 0       4 $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         9  
97             }
98 3   66     13 $meta->{entity_v} //= ${"$pkg\::VERSION"};
  2         11  
99 3   66     9 $meta->{entity_date} //= ${"$pkg\::DATE"};
  2         10  
100             }
101              
102 3         743 require Perinci::Sub::Normalize;
103 3         2557 $meta = Perinci::Sub::Normalize::normalize_function_metadata($meta);
104 3 100       3244 return [200, "OK ($action)", $meta] if $action eq 'meta';
105              
106             # form args (and add special args)
107 1   50     2 my $args = { %{$extra->{args} // {}} }; # shallow copy
  1         14  
108 1 0 33     4 if ($meta->{features} && $meta->{features}{progress}) {
109 0         0 require Progress::Any;
110 0         0 $args->{-progress} = Progress::Any->get_indicator;
111             }
112              
113             # convert args
114 1   50     7 my $aa = $meta->{args_as} // 'hash';
115 1         2 my @args;
116 1 50       7 if ($aa =~ /array/) {
    50          
117 0         0 require Perinci::Sub::ConvertArgs::Array;
118 0         0 my $convres = Perinci::Sub::ConvertArgs::Array::convert_args_to_array(
119             args => $args, meta => $meta,
120             );
121 0 0       0 return $convres unless $convres->[0] == 200;
122 0 0       0 if ($aa =~ /ref/) {
123 0         0 @args = ($convres->[2]);
124             } else {
125 0         0 @args = @{ $convres->[2] };
  0         0  
126             }
127             } elsif ($aa eq 'hashref') {
128 0         0 @args = ({ %$args });
129             } else {
130             # hash
131 1         3 @args = %$args;
132             }
133              
134             # call!
135             {
136 1     1   4 no strict 'refs';
  1         1  
  1         617  
  1         2  
137 1         2 $res = &{"$pkg\::$func"}(@args);
  1         9  
138             }
139              
140             # add envelope
141 1 50       53 if ($meta->{result_naked}) {
142 0         0 $res = [200, "OK (envelope added by ".__PACKAGE__.")", $res];
143             }
144              
145             # add hint that result is binary
146 1 50       5 if (defined $res->[2]) {
147 1 50 33     19 if ($meta->{result} && $meta->{result}{schema} &&
      33        
148             $meta->{result}{schema}[0] eq 'buf') {
149 0         0 $res->[3]{'x.hint.result_binary'} = 1;
150             }
151             }
152              
153             } else {
154 0         0 return [501, "Unknown/unsupported action '$action'"];
155             }
156             } elsif ($url =~ m!\Ahttps?:/(/?)!i) {
157 0         0 my $is_unix = !$1;
158 0         0 my $ht;
159 0         0 require JSON;
160 0         0 state $json = JSON->new->allow_nonref;
161 0 0       0 if ($is_unix) {
162 0         0 require HTTP::Tiny::UNIX;
163 0         0 $ht = HTTP::Tiny::UNIX->new;
164             } else {
165 0         0 require HTTP::Tiny;
166 0         0 $ht = HTTP::Tiny->new;
167             }
168             my %headers = (
169             "x-riap-v" => $self->{riap_version},
170 0         0 "x-riap-action" => $action,
171             "x-riap-fmt" => "json",
172             "content-type" => "application/json",
173             );
174 0   0     0 my $args = $extra->{args} // {};
175 0         0 for (keys %$extra) {
176 0 0       0 next if /\Aargs\z/;
177 0         0 $headers{"x-riap-$_"} = $extra->{$_};
178             }
179 0         0 my $htres = $ht->post(
180             $url, {
181             headers => \%headers,
182             content => $json->encode($args),
183             });
184             return [500, "Network error: $htres->{status} - $htres->{reason}"]
185 0 0       0 if $htres->{status} != 200;
186             return [500, "Server error: didn't return JSON (".$htres->{headers}{'content-type'}.")"]
187 0 0       0 unless $htres->{headers}{'content-type'} eq 'application/json';
188             return [500, "Server error: didn't return Riap 1.1 response (".$htres->{headers}{'x-riap-v'}.")"]
189 0 0       0 unless $htres->{headers}{'x-riap-v'} =~ /\A1\.1(\.\d+)?\z/;
190 0         0 $res = $json->decode($htres->{content});
191             } else {
192 0         0 return [501, "Unsupported scheme or bad URL '$url'"];
193             }
194              
195 1         7 strip_riap_stuffs_from_res($res);
196             }
197              
198             1;
199             # ABSTRACT: A lightweight Riap client library
200              
201             __END__