File Coverage

blib/lib/McBain.pm
Criterion Covered Total %
statement 159 160 99.3
branch 50 58 86.2
condition 27 36 75.0
subroutine 20 21 95.2
pod n/a
total 256 275 93.0


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