File Coverage

blib/lib/Raisin/API.pm
Criterion Covered Total %
statement 97 111 87.3
branch 11 14 78.5
condition 6 10 60.0
subroutine 44 50 88.0
pod 0 41 0.0
total 158 226 69.9


line stmt bran cond sub pod time code
1             #!perl
2             #PODNAME: Raisin::API
3             #ABSTACT: Provides Raisin DSL.
4              
5 6     6   645343 use strict;
  6         56  
  6         179  
6 6     6   34 use warnings;
  6         11  
  6         328  
7              
8             package Raisin::API;
9             $Raisin::API::VERSION = '0.93';
10 6     6   556 use parent 'Exporter';
  6         304  
  6         39  
11              
12 6     6   377 use Carp;
  6         13  
  6         369  
13 6     6   3198 use Hash::Merge qw(merge);
  6         58862  
  6         381  
14              
15 6     6   3388 use Raisin;
  6         114  
  6         335  
16 6     6   2708 use Raisin::Entity;
  6         20  
  6         46  
17             # use Raisin::Util qw(merge);
18              
19             my @APP_CONF_METHODS = qw(
20             app
21             api_default_format api_format api_version
22             middleware mount plugin
23             register_decoder register_encoder
24             );
25             my @APP_EXEC_METHODS = qw(new run);
26             my @APP_METHODS = qw(req res param include_missing session present error);
27             my @HOOKS_METHODS = qw(before before_validation after_validation after);
28             my @HTTP_METHODS = qw(del get head options patch post put);
29             my @ROUTES_METHODS =
30             qw(resource namespace route_param params requires optional group);
31             my @SWAGGER_MERTHODS = qw(desc entity summary tags produces);
32              
33             our @EXPORT = (
34             @APP_CONF_METHODS,
35             @APP_EXEC_METHODS,
36             @APP_METHODS,
37             @HOOKS_METHODS,
38             @HTTP_METHODS,
39             @ROUTES_METHODS,
40             @SWAGGER_MERTHODS,
41             );
42              
43             my %SETTINGS = ();
44             my @NS = ('');
45              
46             my $app;
47              
48             sub import {
49 8     8   67 my $class = shift;
50 8         1418 $class->export_to_level(1, $class, @_);
51              
52 8         60 strict->import;
53 8         140 warnings->import;
54              
55 8         21 my $caller = caller;
56 8   66     159 $app ||= Raisin->new(caller => $caller);
57             }
58              
59 105     105 0 27787 sub app { $app }
60              
61             #
62             # Execution
63             #
64 1     1 0 4 sub new { app->run }
65 6     6 0 931 sub run { app->run }
66              
67             #
68             # Compile
69             #
70 2     2 0 4 sub mount { app->mount_package(@_) }
71 2     2 0 10 sub middleware { app->add_middleware(@_) }
72              
73             #
74             # Hooks
75             #
76 1     1 0 99 sub before { app->add_hook('before', shift) }
77 1     1 0 8 sub before_validation { app->add_hook('before_validation', shift) }
78              
79 1     1 0 8 sub after_validation { app->add_hook('after_validation', shift) }
80 1     1 0 7 sub after { app->add_hook('after', shift) }
81              
82             #
83             # Resource
84             #
85             sub resource {
86 67     67 0 10461 my ($name, $code, @args) = @_;
87 67 100       190 if (scalar(@args) % 2) {
88 1         30 croak "Odd-sized hash supplied to resource(). Is the previous resource missing a semicolon?";
89             }
90 66         120 my %args = @args;
91              
92 66 100       133 if ($name) {
93 28         71 $name =~ s{^/}{}msx;
94 28         59 push @NS, $name;
95              
96 28 100       76 if ($SETTINGS{desc}) {
97 7         13 app->resource_desc($NS[-1], delete $SETTINGS{desc});
98             }
99              
100 28         65 my %prev_settings = %SETTINGS;
101 28         105 Hash::Merge::set_clone_behavior(undef);
102 28         1236 %SETTINGS = %{ merge(\%SETTINGS, \%args) };
  28         80  
103              
104             # Going deeper
105 28         1382 $code->();
106              
107 27         49 pop @NS;
108 27         53 %SETTINGS = ();
109 27         69 %SETTINGS = %prev_settings;
110             }
111              
112 65 100       264 (join '/', @NS) || '/';
113             }
114 1     1 0 1955 sub namespace { resource(@_) }
115              
116             sub route_param {
117 9     9 0 2512 my ($param, $code) = @_;
118 9         32 resource(":$param", $code, named => delete $SETTINGS{params});
119             }
120              
121             #
122             # Serialization
123             #
124             sub register_decoder {
125 0     0 0 0 my ($format, $class) = @_;
126 0         0 app->decoder->register($format => $class);
127             }
128              
129             sub register_encoder {
130 1     1 0 6 my ($format, $class) = @_;
131 1         3 app->encoder->register($format => $class);
132             }
133              
134             #
135             # Actions
136             #
137 4     4 0 23 sub del { _add_route('delete', @_) }
138 18     18 0 102 sub get { _add_route('get', @_) }
139 1     1 0 8 sub head { _add_route('head', @_) }
140 1     1 0 7 sub options { _add_route('options', @_) }
141 4     4 0 17 sub patch { _add_route('patch', @_) }
142 5     5 0 23 sub post { _add_route('post', @_) }
143 5     5 0 21 sub put { _add_route('put', @_) }
144              
145 16     16 0 69 sub params { $SETTINGS{params} = \@_ }
146              
147 10     10 0 64 sub requires { (requires => { name => @_ }) }
148 12     12 0 76 sub optional { (optional => { name => @_ }) }
149              
150 0     0 0 0 sub group(&) { (encloses => [shift->()]) }
151              
152             # Swagger
153 9     9 0 3859 sub desc { $SETTINGS{desc} = shift }
154 0     0 0 0 sub entity { $SETTINGS{entity} = shift }
155 16     16 0 34 sub summary { $SETTINGS{summary} = shift }
156 3     3 0 10 sub tags { $SETTINGS{tags} = \@_ }
157 2     2 0 13 sub produces {$SETTINGS{produces} = shift }
158              
159             sub _add_route {
160 38     38   91 my @params = @_;
161              
162 38         60 my $code = pop @params;
163              
164 38         74 my ($method, $path) = @params;
165 38         115 my $r = resource();
166 38 50 33     135 if ($r eq '/' && $path) {
167 0         0 $path = $r . $path;
168             }
169             else {
170 38 100       93 $path = $r . ($path ? "/$path" : '');
171             }
172              
173             app->add_route(
174             code => $code,
175             method => $method,
176             path => $path,
177             params => delete $SETTINGS{params},
178              
179             desc => delete $SETTINGS{desc},
180             entity => delete $SETTINGS{entity},
181             summary => delete $SETTINGS{summary},
182             tags => delete $SETTINGS{tags},
183             produces => delete $SETTINGS{produces},
184              
185 38         131 %SETTINGS,
186             );
187              
188 38         371 join '/', @NS;
189             }
190              
191             #
192             # Request and Response shortcuts
193             #
194 1     1 0 1987 sub req { app->req }
195 5     5 0 2309 sub res { app->res }
196             sub param {
197 0     0 0 0 my $name = shift;
198 0 0       0 return app->req->raisin_parameters->{$name} if $name;
199 0         0 app->req->raisin_parameters;
200             }
201 0     0 0 0 sub session { app->session(@_) }
202              
203             sub present {
204 2     2 0 14 my ($key, $data, %params) = @_;
205              
206 2   100     9 my $entity = $params{with} || 'Raisin::Entity::Default';
207 2         13 my $value = Raisin::Entity->compile($entity, $data);
208              
209 2   50     5 my $body = res->body || {};
210 2         18 my $representation = { $key => $value, %$body };
211              
212 2         5 res->body($representation);
213              
214 2         15 return;
215             }
216              
217             sub include_missing {
218 0     0 0 0 my $p = shift;
219             # TODO: replace app->req->{'raisin.declared'}, if it is possible, to app->route->params
220 0         0 my %pp = map { $_->name, $p->{ $_->name } } @{ app->req->{'raisin.declared'} };
  0         0  
  0         0  
221 0         0 \%pp;
222             }
223              
224             #
225             # System
226             #
227 3     3 0 14 sub plugin { app->load_plugin(@_) }
228              
229 3     3 0 1950 sub api_default_format { app->default_format(@_) }
230 3     3 0 3057 sub api_format { app->format(@_) }
231              
232             # TODO:
233             # prepend a resource with a version number
234             # http://example.com/api/1
235 2     2 0 1974 sub api_version { app->api_version(@_) }
236              
237             #
238             # Render
239             #
240             sub error {
241 1     1 0 1916 my ($code, $message) = @_;
242 1         4 app->res->status($code);
243 1         7 app->res->body($message);
244             }
245              
246             1;
247              
248             __END__