line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package McBain; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Framework for building portable, auto-validating and self-documenting APIs |
4
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
21639
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
118
|
|
6
|
4
|
|
|
4
|
|
13
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
87
|
|
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
2018
|
use Brannigan; |
|
4
|
|
|
|
|
19281
|
|
|
4
|
|
|
|
|
99
|
|
9
|
4
|
|
|
4
|
|
23
|
use Carp; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
346
|
|
10
|
4
|
|
|
4
|
|
19
|
use File::Spec; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
79
|
|
11
|
4
|
|
|
4
|
|
14
|
use Scalar::Util qw/blessed/; |
|
4
|
|
|
|
|
3
|
|
|
4
|
|
|
|
|
393
|
|
12
|
4
|
|
|
4
|
|
2114
|
use Try::Tiny; |
|
4
|
|
|
|
|
4285
|
|
|
4
|
|
|
|
|
932
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = "2.001000"; |
15
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 NAME |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
McBain - Framework for building portable, auto-validating and self-documenting APIs |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 SYNOPSIS |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
package MyAPI; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use McBain; # imports strict and warnings for you |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
get '/multiply' => ( |
28
|
|
|
|
|
|
|
description => 'Multiplies two integers', |
29
|
|
|
|
|
|
|
params => { |
30
|
|
|
|
|
|
|
one => { required => 1, integer => 1 }, |
31
|
|
|
|
|
|
|
two => { required => 1, integer => 1 } |
32
|
|
|
|
|
|
|
}, |
33
|
|
|
|
|
|
|
cb => sub { |
34
|
|
|
|
|
|
|
my ($api, $params) = @_; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
return $params->{one} * $params->{two}; |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
post '/factorial' => ( |
41
|
|
|
|
|
|
|
description => 'Calculates the factorial of an integer', |
42
|
|
|
|
|
|
|
params => { |
43
|
|
|
|
|
|
|
num => { required => 1, integer => 1, min_value => 0 } |
44
|
|
|
|
|
|
|
}, |
45
|
|
|
|
|
|
|
cb => sub { |
46
|
|
|
|
|
|
|
my ($api, $params) = @_; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# note how this route both uses another |
49
|
|
|
|
|
|
|
# route and calls itself recursively |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
if ($params->{num} <= 1) { |
52
|
|
|
|
|
|
|
return 1; |
53
|
|
|
|
|
|
|
} else { |
54
|
|
|
|
|
|
|
return $api->forward('GET:/multiply', { |
55
|
|
|
|
|
|
|
one => $params->{num}, |
56
|
|
|
|
|
|
|
two => $api->forward('POST:/factorial', { num => $params->{num} - 1 }) |
57
|
|
|
|
|
|
|
}); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
1; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 DESCRIPTION |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
C is a framework for building powerful APIs and applications. Writing an API with C provides the following benefits: |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=over |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=item * B |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
C is extremely lightweight, with minimal dependencies on non-core modules; only two packages; and a succinct, minimal syntax that is easy to remember. Your APIs and applications will require less resources and perform better. Maybe. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=item * B |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
C APIs can be run/used in a variety of ways with absolutely no changes of code. For example, they can be used B (see L), as fully fledged B (see L), as B (see L), or as B (see L). Seriously, no change of code required. More L are yet to come (plus search CPAN to see if more are available), and you can |
77
|
|
|
|
|
|
|
easily create your own, god knows I don't have the time or motivation or talent. Why should I do it |
78
|
|
|
|
|
|
|
for you anyway? |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item * B |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
No more tedious input tests. C will handle input validation for you. All you need to do is define the parameters you expect to get with the simple and easy to remember syntax provided by L. When your API is used, C will automatically validate input. If validation fails, C will return appropriate errors and tell the users of your API that they suck. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=item * B |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
C also eases the burden of having to document your APIs, so that other people can actually use it (and you, two weeks later when you're drunk and can't remember why you wrote the thing in the first place). Using simple descriptions you give to your API's methods, and the parameter definitions, C can automatically create a manual document describing your API (see the L command line utility). |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=item * B |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
APIs written with C are modular and flexible. You can make them object oriented if you want, or not, C won't care, it's unobtrusive like that. APIs are hierarchical, and every module in the API can be used as a complete API all by itself, detached from its siblings, so you can actually load only the parts of the API you need. Why is this useful? I don't know, maybe it isn't, what do I care? It happened by accident anyway. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item * B |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
It'll do that too, just give it a chance. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=back |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head1 FUNCTIONS |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
The following functions are exported: |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 provide( $method, $route, %opts ) |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Define a method and a route. C<$method> is one of C, C, C |
105
|
|
|
|
|
|
|
or C. C<$route> is a string that starts with a forward slash, |
106
|
|
|
|
|
|
|
like a path in a URI. C<%opts> can hold the following keys (only C |
107
|
|
|
|
|
|
|
is required): |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=over |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=item * description |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
A short description of the method and what it does. |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=item * params |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
A hash-ref of parameters in the syntax of L (see L |
118
|
|
|
|
|
|
|
for a complete references). |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=item * cb |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
An anonymous subroutine (or a subroutine reference) to run when the route is |
123
|
|
|
|
|
|
|
called. The method will receive the root topic class (or object, if the |
124
|
|
|
|
|
|
|
topics are written in object oriented style), and a hash-ref of parameters. |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=back |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head2 get( $route, %opts ) |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Shortcut for C |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head2 post( $route, %opts ) |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Shortcut for C |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=head2 put( $route, %opts ) |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Shortcut for C |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=head2 del( $route, %opts ) |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Shortcut for C |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head2 pre_route( $cb->( $self, $meth_and_route, \%params ) ) |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head2 post_route( $cb->( $self, $meth_and_route, \$ret ) ) |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Define a post_route method to run before/after every request to a route in the |
149
|
|
|
|
|
|
|
defining topic. See L"PRE-ROUTES AND POST-ROUTES"> for details. |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=head1 METHODS |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
The following methods will be available on importing classes/objects: |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=head2 call( @args ) |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
Calls the API, requesting the execution of a certain route. This is the |
158
|
|
|
|
|
|
|
main way your API is used. The arguments it expects to receive and its |
159
|
|
|
|
|
|
|
behavior are dependent on the L used. Refer to the docs |
160
|
|
|
|
|
|
|
of the runner you wish to use for more information. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head2 forward( $namespace, [ \%params ] ) |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
For usage from within API methods; this simply calls a method of the |
165
|
|
|
|
|
|
|
the API with the provided parameters (if any) and returns the result. |
166
|
|
|
|
|
|
|
With C, an API method can call other API methods or even |
167
|
|
|
|
|
|
|
itself (for recursive operations). |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
C<$namespace> is the method and route to execute, in the format C<< : >>, |
170
|
|
|
|
|
|
|
where C is one of C, C, C, C, and C |
171
|
|
|
|
|
|
|
starts with a forward slash. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head2 is_root( ) |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Returns a true value if the module is the root topic of the API. |
176
|
|
|
|
|
|
|
Mostly used internally and in L modules. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=cut |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
our %INFO; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub import { |
183
|
7
|
|
|
7
|
|
68
|
my $target = caller; |
184
|
7
|
100
|
|
|
|
33
|
return if $target eq 'main'; |
185
|
6
|
|
|
|
|
6
|
my $me = shift; |
186
|
6
|
|
|
|
|
58
|
strict->import; |
187
|
6
|
|
|
|
|
97
|
warnings->import(FATAL => 'all'); |
188
|
6
|
50
|
|
|
|
10
|
return if $INFO{$target}; |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# find the root of this API (if it's not this class) |
191
|
6
|
|
|
|
|
12
|
my $root = _find_root($target); |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
# create the routes hash for $root |
194
|
6
|
|
100
|
|
|
25
|
$INFO{$root} ||= {}; |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# were there any options passed? |
197
|
6
|
100
|
|
|
|
12
|
if (scalar @_) { |
198
|
3
|
|
|
|
|
5
|
my %opts = map { s/^-//; $_ => 1 } @_; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
10
|
|
199
|
|
|
|
|
|
|
# apply the options to the root package |
200
|
3
|
|
|
|
|
7
|
$INFO{$root}->{_opts} = \%opts; |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# figure out the topic name from this class |
204
|
6
|
|
|
|
|
9
|
my $topic = '/'; |
205
|
6
|
100
|
|
|
|
11
|
unless ($target eq $root) { |
206
|
3
|
|
|
|
|
34
|
my $rel_name = ($target =~ m/^${root}::(.+)$/)[0]; |
207
|
3
|
|
|
|
|
7
|
$topic = '/'.lc($rel_name); |
208
|
3
|
|
|
|
|
7
|
$topic =~ s!::!/!g; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
4
|
|
|
4
|
|
22
|
no strict 'refs'; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
6589
|
|
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# export the is_root() subroutine to the target topic, |
214
|
|
|
|
|
|
|
# so that it knows whether it is the root of the API |
215
|
|
|
|
|
|
|
# or not |
216
|
6
|
|
|
|
|
26
|
*{"${target}::is_root"} = sub { |
217
|
0
|
|
|
0
|
|
0
|
exists $INFO{$target}; |
218
|
6
|
|
|
|
|
24
|
}; |
219
|
|
|
|
|
|
|
|
220
|
6
|
100
|
|
|
|
15
|
if ($target eq $root) { |
221
|
3
|
|
|
|
|
11
|
*{"${target}::import"} = sub { |
222
|
3
|
|
|
3
|
|
36
|
my $t = caller; |
223
|
3
|
|
|
|
|
3
|
shift; |
224
|
3
|
50
|
|
|
|
8
|
my $runner = scalar @_ ? 'McBain::'.ucfirst(substr($_[0], 1)) : 'McBain::Directly'; |
225
|
|
|
|
|
|
|
|
226
|
3
|
|
|
|
|
134
|
eval "require $runner"; |
227
|
3
|
50
|
|
|
|
15
|
croak "Can't load runner module $runner: $@" |
228
|
|
|
|
|
|
|
if $@; |
229
|
|
|
|
|
|
|
|
230
|
3
|
|
|
|
|
12
|
$INFO{$root}->{_runner} = $runner; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# let the runner module do needed initializations, |
233
|
|
|
|
|
|
|
# as the init method usually needs the is_root subroutine, |
234
|
|
|
|
|
|
|
# this statement must come after exporting is_root() |
235
|
3
|
|
|
|
|
16
|
$runner->init($target); |
236
|
3
|
|
|
|
|
9
|
}; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# export the provide subroutine to the target topic, |
240
|
|
|
|
|
|
|
# so that it can define routes and methods. |
241
|
6
|
|
|
|
|
18
|
*{"${target}::provide"} = sub { |
242
|
20
|
|
|
20
|
|
36
|
my ($method, $name) = (shift, shift); |
243
|
20
|
|
|
|
|
39
|
my %opts = @_; |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# make sure the route starts and ends |
246
|
|
|
|
|
|
|
# with a slash, and prefix it with the topic |
247
|
20
|
50
|
|
|
|
62
|
$name = '/'.$name |
248
|
|
|
|
|
|
|
unless $name =~ m{^/}; |
249
|
20
|
100
|
|
|
|
52
|
$name .= '/' |
250
|
|
|
|
|
|
|
unless $name =~ m{/$}; |
251
|
20
|
100
|
|
|
|
34
|
$name = $topic.$name |
252
|
|
|
|
|
|
|
unless $topic eq '/'; |
253
|
|
|
|
|
|
|
|
254
|
20
|
|
50
|
|
|
86
|
$INFO{$root}->{$name} ||= {}; |
255
|
20
|
|
|
|
|
51
|
$INFO{$root}->{$name}->{$method} = \%opts; |
256
|
6
|
|
|
|
|
17
|
}; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# export shortcuts to the provide() subroutine |
259
|
|
|
|
|
|
|
# per http methods |
260
|
6
|
|
|
|
|
18
|
foreach my $meth ( |
261
|
|
|
|
|
|
|
[qw/get GET/], |
262
|
|
|
|
|
|
|
[qw/put PUT/], |
263
|
|
|
|
|
|
|
[qw/post POST/], |
264
|
|
|
|
|
|
|
[qw/del DELETE/] |
265
|
|
|
|
|
|
|
) { |
266
|
24
|
|
|
|
|
74
|
*{$target.'::'.$meth->[0]} = sub { |
267
|
20
|
|
|
20
|
|
118
|
&{"${target}::provide"}($meth->[1], @_); |
|
20
|
|
|
|
|
52
|
|
268
|
24
|
|
|
|
|
44
|
}; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
6
|
|
|
|
|
8
|
my $forward_target = $target; |
272
|
|
|
|
|
|
|
|
273
|
6
|
100
|
100
|
|
|
36
|
if ($target eq $root && $INFO{$root}->{_opts} && $INFO{$root}->{_opts}->{contextual}) { |
|
|
|
66
|
|
|
|
|
274
|
|
|
|
|
|
|
# we're running in contextual mode, which means the API |
275
|
|
|
|
|
|
|
# should have a Context class called $root::Context, and this |
276
|
|
|
|
|
|
|
# is the class to which we should export the forward() method |
277
|
|
|
|
|
|
|
# (the call() method is still exported to the API class). |
278
|
|
|
|
|
|
|
# when call() is, umm, called, we need to create a new instance |
279
|
|
|
|
|
|
|
# of the context class and use forward() on it to handle the |
280
|
|
|
|
|
|
|
# request. |
281
|
|
|
|
|
|
|
# we expect this class to be called $root::Context, but if it |
282
|
|
|
|
|
|
|
# does not exist, we will try going up the hierarchy until we |
283
|
|
|
|
|
|
|
# find one. |
284
|
2
|
|
|
|
|
4
|
my $check = $root.'::Context'; |
285
|
2
|
|
|
|
|
2
|
my $ft; |
286
|
2
|
|
|
|
|
4
|
while ($check) { |
287
|
4
|
|
|
|
|
185
|
eval "require $check"; |
288
|
4
|
100
|
|
|
|
370
|
if ($@) { |
289
|
|
|
|
|
|
|
# go up one level and try again |
290
|
2
|
|
|
|
|
15
|
$check =~ s/[^:]+::Context$/Context/; |
291
|
|
|
|
|
|
|
} else { |
292
|
2
|
|
|
|
|
3
|
$ft = $check; |
293
|
2
|
|
|
|
|
4
|
last; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
2
|
50
|
|
|
|
5
|
croak "No context class found" |
298
|
|
|
|
|
|
|
unless $ft; |
299
|
2
|
50
|
|
|
|
19
|
croak "Context class doesn't have create_from_env() method" |
300
|
|
|
|
|
|
|
unless $ft->can('create_from_env'); |
301
|
|
|
|
|
|
|
|
302
|
2
|
|
|
|
|
4
|
$forward_target = $ft; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# export the pre_route and post_route "constructors" |
306
|
6
|
|
|
|
|
8
|
foreach my $mod (qw/pre_route post_route/) { |
307
|
12
|
|
|
|
|
47
|
*{$target.'::'.$mod} = sub (&) { |
308
|
3
|
|
100
|
3
|
|
20
|
$INFO{$root}->{"_$mod"} ||= {}; |
309
|
3
|
|
|
|
|
7
|
$INFO{$root}->{"_$mod"}->{$topic} = shift; |
310
|
12
|
|
|
|
|
31
|
}; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# export the call method, the one that actually |
314
|
|
|
|
|
|
|
# executes API methods |
315
|
6
|
|
|
|
|
20
|
*{"${target}::call"} = sub { |
316
|
30
|
|
|
30
|
|
13738
|
my ($self, @args) = @_; |
317
|
|
|
|
|
|
|
|
318
|
30
|
|
|
|
|
82
|
my $runner = $INFO{$root}->{_runner}; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
return try { |
321
|
|
|
|
|
|
|
# ask the runner module to generate a standard |
322
|
|
|
|
|
|
|
# env hash-ref |
323
|
30
|
|
|
30
|
|
4538
|
my $env = $runner->generate_env(@args); |
324
|
|
|
|
|
|
|
|
325
|
30
|
100
|
66
|
|
|
162
|
my $ctx = $INFO{$root}->{_opts} && $INFO{$root}->{_opts}->{contextual} ? |
326
|
|
|
|
|
|
|
$forward_target->create_from_env($runner, $env, @args) : |
327
|
|
|
|
|
|
|
$self; |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# handle the request |
330
|
30
|
|
|
|
|
223
|
my $res = $ctx->forward($env->{METHOD}.':'.$env->{ROUTE}, $env->{PAYLOAD}); |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# ask the runner module to generate an appropriate |
333
|
|
|
|
|
|
|
# response with the result |
334
|
19
|
|
|
|
|
200
|
return $runner->generate_res($env, $res); |
335
|
|
|
|
|
|
|
} catch { |
336
|
|
|
|
|
|
|
# an exception was caught, ask the runner module |
337
|
|
|
|
|
|
|
# to format it as it needs |
338
|
11
|
|
|
11
|
|
576
|
my $exp; |
339
|
11
|
100
|
66
|
|
|
101
|
if (ref $_ && ref $_ eq 'HASH' && exists $_->{code} && exists $_->{error}) { |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
340
|
9
|
|
|
|
|
10
|
$exp = $_; |
341
|
|
|
|
|
|
|
} else { |
342
|
2
|
|
|
|
|
8
|
$exp = { code => 500, error => $_ }; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
11
|
|
|
|
|
45
|
return $runner->handle_exception($exp, @args); |
346
|
30
|
|
|
|
|
233
|
}; |
347
|
6
|
|
|
|
|
19
|
}; |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# export the forward method, which is both used internally |
350
|
|
|
|
|
|
|
# in call(), and can be used by API authors within API |
351
|
|
|
|
|
|
|
# methods |
352
|
6
|
|
|
|
|
20
|
*{"${forward_target}::forward"} = sub { |
353
|
46
|
|
|
46
|
|
134
|
my ($ctx, $meth_and_route, $payload) = @_; |
354
|
|
|
|
|
|
|
|
355
|
46
|
|
|
|
|
105
|
my ($meth, $route) = split(/:/, $meth_and_route); |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# make sure route ends with a slash |
358
|
46
|
100
|
|
|
|
144
|
$route .= '/' |
359
|
|
|
|
|
|
|
unless $route =~ m{/$}; |
360
|
|
|
|
|
|
|
|
361
|
46
|
|
|
|
|
44
|
my @captures; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# is there a direct route that equals the request? |
364
|
46
|
|
|
|
|
84
|
my $r = $INFO{$root}->{$route}; |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# if not, is there a regex route that does? |
367
|
46
|
100
|
|
|
|
83
|
unless ($r) { |
368
|
9
|
|
|
|
|
10
|
foreach (keys %{$INFO{$root}}) { |
|
9
|
|
|
|
|
56
|
|
369
|
113
|
100
|
|
|
|
1326
|
next unless @captures = ($route =~ m/^$_$/); |
370
|
4
|
|
|
|
|
15
|
$r = $INFO{$root}->{$_}; |
371
|
4
|
|
|
|
|
9
|
last; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
46
|
100
|
|
|
|
165
|
confess { code => 404, error => "Route $route not found" } |
376
|
|
|
|
|
|
|
unless $r; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# is this an OPTIONS request? |
379
|
41
|
100
|
|
|
|
83
|
if ($meth eq 'OPTIONS') { |
380
|
1
|
|
|
|
|
2
|
my %options; |
381
|
1
|
|
|
|
|
4
|
foreach my $m (keys %$r) { |
382
|
1
|
|
|
|
|
2
|
%{$options{$m}} = map { $_ => $r->{$m}->{$_} } grep($_ ne 'cb', keys(%{$r->{$m}})); |
|
1
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
6
|
|
383
|
|
|
|
|
|
|
} |
384
|
1
|
|
|
|
|
4
|
return \%options; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# does this route have the HTTP method? |
388
|
40
|
100
|
|
|
|
156
|
confess { code => 405, error => "Method $meth not available for route $route" } |
389
|
|
|
|
|
|
|
unless exists $r->{$meth}; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# process parameters |
392
|
38
|
|
|
|
|
183
|
my $params_ret = Brannigan::process({ params => $r->{$meth}->{params} }, $payload); |
393
|
|
|
|
|
|
|
|
394
|
38
|
100
|
|
|
|
5728
|
confess { code => 400, error => "Parameters failed validation", rejects => $params_ret->{_rejects} } |
395
|
|
|
|
|
|
|
if $params_ret->{_rejects}; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# break the path into "directories", run pre_route methods |
398
|
|
|
|
|
|
|
# for each directory (if any) |
399
|
36
|
|
|
|
|
76
|
my @parts = _break_path($route); |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# are there pre_routes? |
402
|
36
|
|
|
|
|
66
|
foreach my $part (@parts) { |
403
|
102
|
100
|
100
|
|
|
613
|
$INFO{$root}->{_pre_route}->{$part}->($ctx, $meth_and_route, $params_ret) |
404
|
|
|
|
|
|
|
if $INFO{$root}->{_pre_route} && $INFO{$root}->{_pre_route}->{$part}; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
|
407
|
34
|
|
|
|
|
143
|
my $res = $r->{$meth}->{cb}->($ctx, $params_ret, @captures); |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# are there post_routes? |
410
|
34
|
|
|
|
|
152
|
foreach my $part (@parts) { |
411
|
99
|
100
|
100
|
|
|
452
|
$INFO{$root}->{_post_route}->{$part}->($ctx, $meth_and_route, \$res) |
412
|
|
|
|
|
|
|
if $INFO{$root}->{_post_route} && $INFO{$root}->{_post_route}->{$part}; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
34
|
|
|
|
|
120
|
return $res; |
416
|
6
|
|
|
|
|
28
|
}; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# we're done with exporting, now lets try to load all |
419
|
|
|
|
|
|
|
# child topics (if any), and collect their method definitions |
420
|
6
|
|
|
|
|
27
|
_load_topics($target, $INFO{$root}->{_opts}); |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# _find_root( $current_class ) |
424
|
|
|
|
|
|
|
# -- finds the root topic of the API, which might |
425
|
|
|
|
|
|
|
# very well be the module we're currently importing into |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
sub _find_root { |
428
|
6
|
|
|
6
|
|
5
|
my $class = shift; |
429
|
|
|
|
|
|
|
|
430
|
6
|
|
|
|
|
5
|
my $copy = $class; |
431
|
6
|
|
|
|
|
27
|
while ($copy =~ m/::[^:]+$/) { |
432
|
6
|
100
|
|
|
|
21
|
return $` |
433
|
|
|
|
|
|
|
if $INFO{$`}; |
434
|
3
|
|
|
|
|
11
|
$copy = $`; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
3
|
|
|
|
|
9
|
return $class; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# _load_topics( $base, [ \%opts ] ) |
441
|
|
|
|
|
|
|
# -- finds and loads the child topics of the class we're |
442
|
|
|
|
|
|
|
# currently importing into, automatically requiring |
443
|
|
|
|
|
|
|
# them and thus importing McBain into them as well |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
sub _load_topics { |
446
|
6
|
|
|
6
|
|
12
|
my ($base, $opts) = @_; |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
# this code is based on code from Module::Find |
449
|
|
|
|
|
|
|
|
450
|
6
|
|
|
|
|
64
|
my $pkg_dir = File::Spec->catdir(split(/::/, $base)); |
451
|
|
|
|
|
|
|
|
452
|
6
|
|
|
|
|
12
|
my @inc_dirs = map { File::Spec->catdir($_, $pkg_dir) } @INC; |
|
60
|
|
|
|
|
193
|
|
453
|
|
|
|
|
|
|
|
454
|
6
|
|
|
|
|
11
|
foreach my $inc_dir (@inc_dirs) { |
455
|
60
|
100
|
|
|
|
1632
|
next unless -d $inc_dir; |
456
|
|
|
|
|
|
|
|
457
|
3
|
|
|
|
|
76
|
opendir DIR, $inc_dir; |
458
|
3
|
100
|
|
|
|
84
|
my @pms = grep { !-d && m/\.pm$/ } readdir DIR; |
|
11
|
|
|
|
|
83
|
|
459
|
3
|
|
|
|
|
22
|
closedir DIR; |
460
|
|
|
|
|
|
|
|
461
|
3
|
|
|
|
|
5
|
foreach my $file (@pms) { |
462
|
4
|
|
|
|
|
17
|
my $pkg = $file; |
463
|
4
|
|
|
|
|
20
|
$pkg =~ s/\.pm$//; |
464
|
4
|
|
|
|
|
40
|
$pkg = join('::', File::Spec->splitdir($pkg)); |
465
|
|
|
|
|
|
|
|
466
|
4
|
|
|
|
|
19
|
my $req = File::Spec->catdir($inc_dir, $file); |
467
|
|
|
|
|
|
|
|
468
|
4
|
50
|
66
|
|
|
31
|
next if $req =~ m!/Context.pm$! |
|
|
|
66
|
|
|
|
|
469
|
|
|
|
|
|
|
&& $opts && $opts->{contextual}; |
470
|
|
|
|
|
|
|
|
471
|
3
|
|
|
|
|
1225
|
require $req; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# _break_path( $path ) |
477
|
|
|
|
|
|
|
# -- breaks a route/path into a list of "directories", |
478
|
|
|
|
|
|
|
# starting from the root and up to the full path |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
sub _break_path { |
481
|
36
|
|
|
36
|
|
51
|
my $path = shift; |
482
|
|
|
|
|
|
|
|
483
|
36
|
|
|
|
|
46
|
my $copy = $path; |
484
|
|
|
|
|
|
|
|
485
|
36
|
|
|
|
|
28
|
my @path; |
486
|
|
|
|
|
|
|
|
487
|
36
|
100
|
|
|
|
91
|
unless ($copy eq '/') { |
488
|
35
|
|
|
|
|
48
|
chop($copy); |
489
|
|
|
|
|
|
|
|
490
|
35
|
|
|
|
|
74
|
while (length($copy)) { |
491
|
68
|
|
|
|
|
99
|
unshift(@path, $copy); |
492
|
68
|
|
|
|
|
334
|
$copy =~ s!/[^/]+$!!; |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
36
|
|
|
|
|
48
|
unshift(@path, '/'); |
497
|
|
|
|
|
|
|
|
498
|
36
|
|
|
|
|
119
|
return @path; |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=head1 MANUAL |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=head2 ANATOMY OF AN API |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
Writing an API with C is easy. The syntax is short and easy to remember, |
506
|
|
|
|
|
|
|
and the feature list is just what it needs to be - short and sweet. |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
The main idea of a C API is this: a client requests the execution of a |
509
|
|
|
|
|
|
|
method provided by the API, sending a hash of parameters. The API then executes the |
510
|
|
|
|
|
|
|
method with the client's parameters, and produces a response. Every L |
511
|
|
|
|
|
|
|
will enforce a different response format (and even request format). When the API is |
512
|
|
|
|
|
|
|
L, for example, whatever the API produces is returned as |
513
|
|
|
|
|
|
|
is. The L and L runners, |
514
|
|
|
|
|
|
|
however, are both JSON-in JSON-out interfaces. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
A C API is built of one or more B, in a hierarchical structure. |
517
|
|
|
|
|
|
|
A topic is a class that provides methods that are categorically similar. For |
518
|
|
|
|
|
|
|
example, an API might have a topic called "math" that provides math-related |
519
|
|
|
|
|
|
|
methods such as add, multiply, divide, etc. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
Since topics are hierarchical, every API will have a root topic, which may have |
522
|
|
|
|
|
|
|
zero or more child topics. The root topic is where your API begins, and it's your |
523
|
|
|
|
|
|
|
decision how to utilize it. If your API is short and simple, with methods that |
524
|
|
|
|
|
|
|
cannot be categorized into different topics, then the entire API can live within the |
525
|
|
|
|
|
|
|
root topic itself, with no child topics at all. If, however, you're building a |
526
|
|
|
|
|
|
|
larger API, then the root topic might be empty, or it can provide general-purpose |
527
|
|
|
|
|
|
|
methods that do not particularly fit in a specific topic, for example maybe a status |
528
|
|
|
|
|
|
|
method that returns the status of the service, or an authentication method. |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
The name of a topic is calculated from the name of the package itself. The root |
531
|
|
|
|
|
|
|
topic is always called C> (forward slash), and its child topics are named |
532
|
|
|
|
|
|
|
like their package names, in lowercase, relative to the root topic, with C> |
533
|
|
|
|
|
|
|
as a separator instead of Perl's C<::>, and starting with a slash. |
534
|
|
|
|
|
|
|
For example, lets look at the following API packages: |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
+------------------------+-------------------+------------------+ |
537
|
|
|
|
|
|
|
| Package Name | Topic Name | Description | |
538
|
|
|
|
|
|
|
+========================+===================+==================+ |
539
|
|
|
|
|
|
|
| MyAPI | "/" | the root topic | |
540
|
|
|
|
|
|
|
| MyAPI::Math | "/math" | a child topic | |
541
|
|
|
|
|
|
|
| MyAPI::Math::Constants | "/math/constants" | a child-of-child | |
542
|
|
|
|
|
|
|
| MyAPI::Strings | "/strings" | a child topic | |
543
|
|
|
|
|
|
|
+------------------------+--------------------------------------+ |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
You will notice that the naming of the topics is similar to paths in HTTP URIs. |
546
|
|
|
|
|
|
|
This is by design, since I wrote C mostly for writing web applications |
547
|
|
|
|
|
|
|
(with the L runner), and the RESTful architecture fits |
548
|
|
|
|
|
|
|
well with APIs whether they are HTTP-based or not. |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head2 CREATING TOPICS |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
To create a topic package, all you need to do is: |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
use McBain; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
This will import C functions into the package, register the package |
557
|
|
|
|
|
|
|
as a topic (possibly the root topic), and attempt to load all child topics, if there |
558
|
|
|
|
|
|
|
are any. For convenience, C will also import L and L for |
559
|
|
|
|
|
|
|
you. |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
Notice that using C doesn't make your package an OO class. If you want your |
562
|
|
|
|
|
|
|
API to be object oriented, you are free to form your classes however you want, for |
563
|
|
|
|
|
|
|
example with L or L: |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
package MyAPI; |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
use McBain; |
568
|
|
|
|
|
|
|
use Moo; |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
has 'some_attr' => ( is => 'ro' ); |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
1; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=head2 CREATING ROUTES AND METHODS |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
The resemblance with HTTP continues as we delve further into methods themselves. An API |
577
|
|
|
|
|
|
|
topic defines B, and one or more B that can be executed on every |
578
|
|
|
|
|
|
|
route. Just like HTTP, these methods are C, C, C and C. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
Route names are like topic names. They begin with a slash, and every topic I |
581
|
|
|
|
|
|
|
have a root route which is just called C>. Every method defined on a route |
582
|
|
|
|
|
|
|
will have a complete name (or path, if you will), in the format |
583
|
|
|
|
|
|
|
C<< : >>. For example, let's say we have a |
584
|
|
|
|
|
|
|
topic called C, and this topic has a route called C, with one |
585
|
|
|
|
|
|
|
C method defined on this route. The complete name (or path) of this method |
586
|
|
|
|
|
|
|
will be C. |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
By using this structure and semantics, it is easy to create CRUD interfaces. Lets |
589
|
|
|
|
|
|
|
say your API has a topic called C, that deals with articles in your |
590
|
|
|
|
|
|
|
blog. Every article has an integer ID. The C topic can have the following |
591
|
|
|
|
|
|
|
routes and methods: |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
+------------------------+--------------------------------------+ |
594
|
|
|
|
|
|
|
| Namespace | Description | |
595
|
|
|
|
|
|
|
+========================+======================================+ |
596
|
|
|
|
|
|
|
| POST:/articles/ | Create a new article (root route /) | |
597
|
|
|
|
|
|
|
| GET:/articles/(\d+) | Read an article | |
598
|
|
|
|
|
|
|
| PUT:/articles/(\d+) | Update an article | |
599
|
|
|
|
|
|
|
| DELETE:/articles/(\d+) | Delete an article | |
600
|
|
|
|
|
|
|
+------------------------+--------------------------------------+ |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
Methods are defined using the L, L, |
603
|
|
|
|
|
|
|
L and L subroutines. |
604
|
|
|
|
|
|
|
The syntax is similar to L's antlers: |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
get '/multiply' => ( |
607
|
|
|
|
|
|
|
description => 'Multiplies two integers', |
608
|
|
|
|
|
|
|
params => { |
609
|
|
|
|
|
|
|
a => { required => 1, integer => 1 }, |
610
|
|
|
|
|
|
|
b => { required => 1, integer => 1 } |
611
|
|
|
|
|
|
|
}, |
612
|
|
|
|
|
|
|
cb => sub { |
613
|
|
|
|
|
|
|
my ($api, $params) = @_; |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
return $params->{a} * $params->{b}; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
); |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
Of the three keys above (C, C and C), only C |
620
|
|
|
|
|
|
|
is required. It takes the actual subroutine to execute when the method is |
621
|
|
|
|
|
|
|
called. The subroutine will get two arguments: first, the root topic (either |
622
|
|
|
|
|
|
|
its package name, or its object, if you're creating an object oriented API), |
623
|
|
|
|
|
|
|
and a hash-ref of parameters provided to the method (if any). |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
You can provide C with a short C of the method, so that |
626
|
|
|
|
|
|
|
C can use it when documenting the API with L. |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
You can also tell C which parameters your method takes. The C |
629
|
|
|
|
|
|
|
key will take a hash-ref of parameters, in the format defined by L |
630
|
|
|
|
|
|
|
(see L for a complete references). These will be both |
631
|
|
|
|
|
|
|
enforced and documented. |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
As you may have noticed in the C example, routes can be defined using |
634
|
|
|
|
|
|
|
regular expressions. This is useful for creating proper RESTful URLs: |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# in topic /articles |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
get '/(\d+)' => ( |
639
|
|
|
|
|
|
|
description => 'Returns an article by its integer ID', |
640
|
|
|
|
|
|
|
cb => sub { |
641
|
|
|
|
|
|
|
my ($api, $params, $id) = @_; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
return $api->db->get_article($id); |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
); |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
If the regular expression contains L, and |
648
|
|
|
|
|
|
|
a call to the API matches the regular expressions, the values captured will |
649
|
|
|
|
|
|
|
be passed to the method, after the parameters hash-ref (even if the method |
650
|
|
|
|
|
|
|
does not define parameters, in which case the parameters hash-ref will be |
651
|
|
|
|
|
|
|
empty - this may change in the future). |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
It is worth understanding how C builds the regular expression. In the |
654
|
|
|
|
|
|
|
above example, the topic is C, and the route is C(\d+)>. Internally, |
655
|
|
|
|
|
|
|
the generated regular expression will be C<^/articles/(\d+)$>. Notice how the topic |
656
|
|
|
|
|
|
|
and route are concatenated, and how the C<^> and C<$> metacharacters are added to |
657
|
|
|
|
|
|
|
the beginning and end of the regex, respectively. This means it is impossible to |
658
|
|
|
|
|
|
|
create partial regexes, which only pose problems in my experience. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head2 OPTIONS REQUESTS |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
Every route defined by the API also automatically gets an C method, |
663
|
|
|
|
|
|
|
again just like HTTP. This method returns a list of HTTP-style methods allowed |
664
|
|
|
|
|
|
|
on the route. The return format depends on the runner module used. The direct |
665
|
|
|
|
|
|
|
runner will return a hash-ref with keys being the HTTP methods, and values being |
666
|
|
|
|
|
|
|
hash-refs holding the C and C definitions (if any). |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
For example, let's look at the following route: |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
get '/something' => ( |
671
|
|
|
|
|
|
|
description => 'Gets something', |
672
|
|
|
|
|
|
|
cb => sub { } |
673
|
|
|
|
|
|
|
); |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
put '/something' => ( |
676
|
|
|
|
|
|
|
description => 'Updates something', |
677
|
|
|
|
|
|
|
params => { new_content => { required => 1 } }, |
678
|
|
|
|
|
|
|
cb => sub { } |
679
|
|
|
|
|
|
|
); |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
Calling C will return: |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
{ |
684
|
|
|
|
|
|
|
GET => { |
685
|
|
|
|
|
|
|
description => "Gets something" |
686
|
|
|
|
|
|
|
}, |
687
|
|
|
|
|
|
|
PUT => { |
688
|
|
|
|
|
|
|
description => "Updates something", |
689
|
|
|
|
|
|
|
params => { |
690
|
|
|
|
|
|
|
new_content => { required => 1 } |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=head2 CALLING METHODS FROM WITHIN METHODS |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
Methods are allowed to call other methods (whether in the same route or not), |
698
|
|
|
|
|
|
|
and even call themselves recursively. This can be accomplished easily with |
699
|
|
|
|
|
|
|
the L method. For example: |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
get '/factorial => ( |
702
|
|
|
|
|
|
|
description => 'Calculates the factorial of a number', |
703
|
|
|
|
|
|
|
params => { |
704
|
|
|
|
|
|
|
num => { required => 1, integer => 1 } |
705
|
|
|
|
|
|
|
}, |
706
|
|
|
|
|
|
|
cb => sub { |
707
|
|
|
|
|
|
|
my ($api, $params) = @_; |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
if ($params->{num} <= 1) { |
710
|
|
|
|
|
|
|
return 1; |
711
|
|
|
|
|
|
|
} else { |
712
|
|
|
|
|
|
|
return $api->forward('GET:/multiply', { |
713
|
|
|
|
|
|
|
one => $params->{num}, |
714
|
|
|
|
|
|
|
two => $api->forward('GET:/factorial', { num => $params->{num} - 1 }) |
715
|
|
|
|
|
|
|
}); |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
); |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
In the above example, notice how the C method calls both |
721
|
|
|
|
|
|
|
C and itself. |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=head2 EXCEPTIONS |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
C APIs handle errors in a graceful way, returning proper error |
726
|
|
|
|
|
|
|
responses to callers. As always, the way errors are returned depends on |
727
|
|
|
|
|
|
|
the L used. When used directly from Perl |
728
|
|
|
|
|
|
|
code, McBain will L (i.e. die) with a hash-ref consisting |
729
|
|
|
|
|
|
|
of two keys: |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=over |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
=item * C - An HTTP status code indicating the type of the error (for |
734
|
|
|
|
|
|
|
example 404 if the route doesn't exist, 405 if the route exists but the method |
735
|
|
|
|
|
|
|
is not allowed, 400 if parameters failed validation, etc.). |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=item * C - The text/description of the error. |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=back |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
Depending on the type of the error, more keys might be added to the exception. |
742
|
|
|
|
|
|
|
For example, the parameters failed validation error will also include a C |
743
|
|
|
|
|
|
|
key holding L's standard rejects hash, describing which parameters failed |
744
|
|
|
|
|
|
|
validation. |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
When writing APIs, you are encouraged to return exceptions in this format to |
747
|
|
|
|
|
|
|
ensure proper handling by C. If C encounters an exception |
748
|
|
|
|
|
|
|
that does not conform to this format, it will generate an exception with |
749
|
|
|
|
|
|
|
C 500 (indicating "Internal Server Error"), and the C key will |
750
|
|
|
|
|
|
|
hold the exception as is. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
=head2 PRE-ROUTES AND POST-ROUTES |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
I |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
Every topic in your API can define pre and post routes. The pre route is called |
757
|
|
|
|
|
|
|
right before a route is executed, while the post route is called immediately after. |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
You should note that the pre and post routes are called on every route execution |
760
|
|
|
|
|
|
|
(when applicable), even when forwarding from one route to another. |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
Pre and post routes are hierarchical. When a route is executed, C will analyze |
763
|
|
|
|
|
|
|
the entire chain of topics leading up to that route, and execute all pre and post routes |
764
|
|
|
|
|
|
|
on the way (if any, of course). So, for example, if the route C is to be |
765
|
|
|
|
|
|
|
executed, C will look for pre and post routes and the root topic (C>), the C |
766
|
|
|
|
|
|
|
topic, and the C topic (if it exists). Whichever ones it finds will be |
767
|
|
|
|
|
|
|
executed, in order. |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
The C subroutine gets as parameters the API package (or object, if writing |
770
|
|
|
|
|
|
|
object-oriented APIs, or the context object, if writing in L), |
771
|
|
|
|
|
|
|
the full route name (the method and the path, e.g. C), and the |
772
|
|
|
|
|
|
|
parameters hash-ref, after validation has occurred. |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
package MyApi::Math; |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
post '/factorial' => ( |
777
|
|
|
|
|
|
|
... |
778
|
|
|
|
|
|
|
); |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
pre_route { |
781
|
|
|
|
|
|
|
my ($self, $meth_and_route, $params) = @_; |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
# do something here |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
The C subroutine gets the same parameters, except the parameters hash-ref, in which |
787
|
|
|
|
|
|
|
place a reference to the result returned by the actual route is passed. So, for example, if |
788
|
|
|
|
|
|
|
the C method returned C<13>, then C will get a reference |
789
|
|
|
|
|
|
|
to a scalar variable whose value is 13. |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
post_route { |
792
|
|
|
|
|
|
|
my ($self, $meth_and_route, $ret) = @_; |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
if ($$ret == 13) { |
795
|
|
|
|
|
|
|
# change the result to 14, because |
796
|
|
|
|
|
|
|
# 13 is an unlucky number |
797
|
|
|
|
|
|
|
$$ret = 14; |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=head2 CONTEXTUAL MODE |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
I<< B contextual mode is an experimental feature introduced in v1.2.0 and |
804
|
|
|
|
|
|
|
may change in the future. >> |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
Contextual mode is an optional way of writing C APIs, reminiscent of |
807
|
|
|
|
|
|
|
web application frameworks such as L and L. The main idea |
808
|
|
|
|
|
|
|
is that a context object is created for every request, and follows it during |
809
|
|
|
|
|
|
|
its entire life. |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
In regular mode, the API methods receive the class of the root package (or its |
812
|
|
|
|
|
|
|
object, if writing object oriented APIs), and a hash-ref of parameters. This is |
813
|
|
|
|
|
|
|
okay for simple APIs, but many APIs need more, like information about the |
814
|
|
|
|
|
|
|
user who sent the request. |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
In contextual mode, the context object can contain user information, methods for |
817
|
|
|
|
|
|
|
checking authorization (think role-based and ability-based authorization systems), |
818
|
|
|
|
|
|
|
database connections, and anything else your API might need in order to fulfill the |
819
|
|
|
|
|
|
|
request. |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
Writing APIs in contextual mode is basically the same as in regular mode, only you |
822
|
|
|
|
|
|
|
need to build a context class. Since C doesn't intrude on your OO system of |
823
|
|
|
|
|
|
|
choice, constructing the class is your responsibility, and you can use whatever you |
824
|
|
|
|
|
|
|
want (like L, L, L). C only requires your |
825
|
|
|
|
|
|
|
context class to implement a subroutine named C. |
826
|
|
|
|
|
|
|
This method will receive the name of the runner module used, the standard environment |
827
|
|
|
|
|
|
|
hash-ref of C (which includes the keys C, C and C), |
828
|
|
|
|
|
|
|
plus all of the arguments that were sent to the L method. These are |
829
|
|
|
|
|
|
|
useful for certain runner modules, such as the L, |
830
|
|
|
|
|
|
|
which gets the L hash-ref, from which you can extract session data, user |
831
|
|
|
|
|
|
|
information, HTTP headers, etc. Note that this means that if you plan to use your API |
832
|
|
|
|
|
|
|
with different runner modules, your C method should be able to parse |
833
|
|
|
|
|
|
|
differently formatted arguments. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
Note that currently, the context class has to be named C<__ROOT__::Context>, where |
836
|
|
|
|
|
|
|
C<__ROOT__> is the name of your API's root package. So, for example, if your API's |
837
|
|
|
|
|
|
|
root package is named C, then C will expect C. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
I<< B since v2.1.0, if C doesn't find a package named C<__ROOT__::Context>, |
840
|
|
|
|
|
|
|
it will go up the package hierarchy until it finds one. For example, if the root package |
841
|
|
|
|
|
|
|
of your API is C, then McBain will try C, then C, |
842
|
|
|
|
|
|
|
then finally C. This was added to allow the sharing of the same context class |
843
|
|
|
|
|
|
|
in a project comprised of several APIs. >> |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
When writing in contextual mode, your API methods will receive the context object |
846
|
|
|
|
|
|
|
instead of the root package/object, and the parameters hash-ref. |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
Let's look at a simple example for writing APIs in contextual mode. Say our API |
849
|
|
|
|
|
|
|
is called C. Let's begin with the context class, C: |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
package MyAPI::Context; |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
use Moo; |
854
|
|
|
|
|
|
|
use Plack::Request; |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
has 'user_agent' => ( |
857
|
|
|
|
|
|
|
is => 'ro', |
858
|
|
|
|
|
|
|
default => sub { 'none' } |
859
|
|
|
|
|
|
|
); |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
sub create_from_env { |
862
|
|
|
|
|
|
|
my ($class, $runner, $mcbain_env, @call_args) = @_; |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
my $user_agent; |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
if ($runner eq 'McBain::WithPSGI') { |
867
|
|
|
|
|
|
|
# extract user agent from the PSGI env, |
868
|
|
|
|
|
|
|
# which will be the first item in @call_args |
869
|
|
|
|
|
|
|
$user_agent = Plack::Request->new($call_args[0])->user_agent; |
870
|
|
|
|
|
|
|
} |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
return $class->new(user_agent => $user_agent); |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
1; |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
Now let's look at the API itself: |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
package MyAPI; |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
use McBain -contextual; |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
get '/' => ( |
884
|
|
|
|
|
|
|
cb => sub { |
885
|
|
|
|
|
|
|
my ($c, $params) = @_; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
if ($c->user_agent =~ m/Android/) { |
888
|
|
|
|
|
|
|
# do it this way |
889
|
|
|
|
|
|
|
} else { |
890
|
|
|
|
|
|
|
# do it that way |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# you can still forward to other methods |
894
|
|
|
|
|
|
|
$c->forward('GET:/something_else', \%other_params); |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
); |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
1; |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
So as you can see, the only real change for API packages is the need |
901
|
|
|
|
|
|
|
to write C |
902
|
|
|
|
|
|
|
"challenge" is writing the context class. |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
=head1 MCBAIN RUNNERS |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
I<< B since v2.0.0 the way runner modules are used has changed. The |
907
|
|
|
|
|
|
|
C environment variable is no longer used. Read on for more |
908
|
|
|
|
|
|
|
information. >> |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
A runner module is in charge of loading C APIs in a specific way. |
911
|
|
|
|
|
|
|
The default runner, L, is the simplest runner there is, |
912
|
|
|
|
|
|
|
and is meant for using APIs directly from Perl code. |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
The runner module is in charge of whatever heavy lifting is required in order |
915
|
|
|
|
|
|
|
to turn your API into a "service", or an "app", or whatever it is you think your |
916
|
|
|
|
|
|
|
API needs to be. |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
The following runners are currently available: |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
=over |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
=item * L - Directly use an API from Perl code. |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
=item * L - Turn an API into a Plack based, JSON-to-JSON |
925
|
|
|
|
|
|
|
RESTful web application. |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
=item * L - Turn an API into a JSON-to-JSON |
928
|
|
|
|
|
|
|
Gearman worker. |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
=item * L - Turn an API into a WebSocket server. |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
=item * L - Turn an API into a JSON-to-JSON ZeroMQ REP worker. |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
=back |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
The latter four completely change the way your API is used, and yet you can |
937
|
|
|
|
|
|
|
see their code is very short. |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
To tell C which runner module to use, you must provide the name of the |
940
|
|
|
|
|
|
|
runner when loading your API: |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
use MyAPI -withPSGI; # can also write -WithPSGI |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
In the above example, C will be the runner module used. |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
The default runner module is C. If you C an API with no |
947
|
|
|
|
|
|
|
parameter, it will be the loaded runner module: |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
use MyAPI; |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
use MyAPI -directly; # the same as above |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
You can easily create your own runner modules, so that your APIs can be used |
954
|
|
|
|
|
|
|
in different ways. A runner module needs to implement the following interface: |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
=head2 init( $runner_class, $target_class ) |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
This method is called when C is first imported into an API topic. |
959
|
|
|
|
|
|
|
C<$target_class> will hold the name of the class currently being imported to. |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
You can do whatever initializations you need to do here, possibly manipulating |
962
|
|
|
|
|
|
|
the target class directly. You will probably only want to do this on the root |
963
|
|
|
|
|
|
|
topic, which is why L"is_root( )"> is available on C<$target_class>. |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
You can look at C and C to see how they're using the |
966
|
|
|
|
|
|
|
C method. For example, in C, L is added |
967
|
|
|
|
|
|
|
to the C<@ISA> array of the root topic, so that it turns into a Plack app. In |
968
|
|
|
|
|
|
|
C, the C method is used to define a C method |
969
|
|
|
|
|
|
|
on the root topic, so that your API can run as any standard Gearman worker. |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=head2 generate_env( $runner_class, @call_args ) |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
This method receives whatever arguments were passed to the L"call( @args )"> |
974
|
|
|
|
|
|
|
method. It is in charge of returning a standard hash-ref that C can use |
975
|
|
|
|
|
|
|
in order to determine which route the caller wants to execute, and with what |
976
|
|
|
|
|
|
|
parameters. Remember that the way C is invoked depends on the runner used. |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
The hash-ref returned I have the following key-value pairs: |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
=over |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
=item * ROUTE - The route to execute (string). |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
=item * METHOD - The method to call on the route (string). |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=item * PAYLOAD - A hash-ref of parameters to provide for the method. If no parameters |
987
|
|
|
|
|
|
|
are provided, an empty hash-ref should be given. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
=back |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
The returned hash-ref is called C<$env>, inspired by L. |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
=head2 generate_res( $runner_class, \%env, $result ) |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
This method formats the result from a route before returning it to the caller. |
996
|
|
|
|
|
|
|
It receives the C<$env> hash-ref (if needed), and the result from the route. In the |
997
|
|
|
|
|
|
|
C runner, for example, this method encodes the result into JSON and |
998
|
|
|
|
|
|
|
returns a proper PSGI response array-ref. |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head2 handle_exception( $runner_class, $error, @args ) |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
This method will be called whenever a route raises an exception, or otherwise your code |
1003
|
|
|
|
|
|
|
fails. The C<$error> variable will always be a standard L, |
1004
|
|
|
|
|
|
|
with C and C keys, and possibly more. Read the discussion above. |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
The method should format the error before returning it to the user, similar to what |
1007
|
|
|
|
|
|
|
C above performs, but it allows you to handle exceptions gracefully. |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
Whatever arguments were provided to C will be provided to this method as-is, |
1010
|
|
|
|
|
|
|
so that you can inspect or use them if need be. C, for example, |
1011
|
|
|
|
|
|
|
will get the L object and call the C method on it, |
1012
|
|
|
|
|
|
|
to properly indicate the job failed. |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
=head1 CONFIGURATION AND ENVIRONMENT |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
No configuration files or environment variables required. |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
C depends on the following CPAN modules: |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
=over |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=item * L |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
=item * L |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=item * L |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=item * L |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
=item * L |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
=back |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
The command line utility, L, depends on the following CPAN modules: |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
=over |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
=item * L |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
=item * L |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=item * L |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
=back |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
=head1 INCOMPATIBILITIES WITH OTHER MODULES |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
None reported. |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
1055
|
|
|
|
|
|
|
C, or through the web interface at |
1056
|
|
|
|
|
|
|
L. |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
=head1 SUPPORT |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
perldoc McBain |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
You can also look for information at: |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
=over 4 |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
L |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
L |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
=item * CPAN Ratings |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
L |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
=item * Search CPAN |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
L |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
=back |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=head1 AUTHOR |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
Ido Perlmuter |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
Copyright (c) 2013-2014, Ido Perlmuter C<< ido@ido50.net >>. |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
This module is free software; you can redistribute it and/or |
1095
|
|
|
|
|
|
|
modify it under the same terms as Perl itself, either version |
1096
|
|
|
|
|
|
|
5.8.1 or any later version. See L |
1097
|
|
|
|
|
|
|
and L. |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
The full text of the license can be found in the |
1100
|
|
|
|
|
|
|
LICENSE file included with this module. |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
=head1 DISCLAIMER OF WARRANTY |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
1105
|
|
|
|
|
|
|
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
1106
|
|
|
|
|
|
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
1107
|
|
|
|
|
|
|
PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER |
1108
|
|
|
|
|
|
|
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
1109
|
|
|
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE |
1110
|
|
|
|
|
|
|
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH |
1111
|
|
|
|
|
|
|
YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL |
1112
|
|
|
|
|
|
|
NECESSARY SERVICING, REPAIR, OR CORRECTION. |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
1115
|
|
|
|
|
|
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
1116
|
|
|
|
|
|
|
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE |
1117
|
|
|
|
|
|
|
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, |
1118
|
|
|
|
|
|
|
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE |
1119
|
|
|
|
|
|
|
THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING |
1120
|
|
|
|
|
|
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A |
1121
|
|
|
|
|
|
|
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF |
1122
|
|
|
|
|
|
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
1123
|
|
|
|
|
|
|
SUCH DAMAGES. |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
=cut |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
1; |
1128
|
|
|
|
|
|
|
__END__ |