File Coverage

blib/lib/Dancer/Plugin/SporeDefinitionControl.pm
Criterion Covered Total %
statement 128 138 92.7
branch 40 50 80.0
condition 19 23 82.6
subroutine 14 16 87.5
pod 1 1 100.0
total 202 228 88.6


line stmt bran cond sub pod time code
1             package Dancer::Plugin::SporeDefinitionControl;
2              
3 6     6   1440196 use warnings;
  6         43  
  6         168  
4 6     6   29 use strict;
  6         12  
  6         107  
5              
6 6     6   24 use Dancer ':syntax';
  6         10  
  6         29  
7 6     6   3900 use Dancer::Plugin;
  6         7404  
  6         343  
8 6     6   2289 use Dancer::Plugin::REST '0.04';
  6         92637  
  6         1960  
9 6     6   2834 use YAML qw/LoadFile DumpFile/;
  6         35524  
  6         322  
10 6     6   45 use File::Spec;
  6         13  
  6         8328  
11              
12             =head1 NAME
13              
14             Dancer::Plugin::SporeDefinitionControl
15              
16             Dancer Plugin to control validity of route from a Spore configuration file
17              
18             =head1 VERSION
19              
20             Version 0.18
21              
22             =cut
23              
24             our $VERSION = '0.18';
25              
26             =head1 SYNOPSIS
27              
28             Dancer required version : 1.3002
29              
30             in your Dancer project, use this plugin and register :
31              
32             package MyDancer::Server;
33              
34             use Dancer::Plugin::SporeDefinitionControl;
35              
36             check_spore_definition();
37              
38             In your config file :
39              
40             plugins:
41             SporeDefinitionControl:
42             spore_spec_path: path/to/route_config.yaml
43              
44             The yaml path file can be relative (root project base) or absolute.
45              
46             in your file path/to/route_config.yaml, put your SPORE config :
47              
48             base_url: http://localhost:4500
49             version: 0.2
50             format:
51             - json
52             - xml
53             - yml
54             methods:
55             get_object:
56             required_params:
57             - id
58             - name_object
59             optional_params:
60             - created_at
61             path: /object/:id
62             method: GET
63             create_object:
64             required_params:
65             - name_object
66             optional_params:
67             - created_at
68             path: /object/create
69             method: POST
70             update_object:
71             required_params:
72             - id
73             - name_object
74             optional_params:
75             - created_at
76             path: /object/:id
77             method: PUT
78             delete_object:
79             required_params:
80             - id
81             - name_object
82             optional_params:
83             - created_at
84             path: /object/:id
85             method: DELETE
86              
87             =head1 INITIALISATION
88              
89             Load yaml config file
90              
91             =cut
92              
93              
94             #Load definition spore file from plugin config
95              
96             our $path_validation;
97             #_load_path_validation();
98             $path_validation =_load_path_validation();
99             sub _load_path_validation
100             {
101 6     6   16 my $rh_file = {};
102              
103 6         40 my $path_to_spore_def = plugin_setting->{'spore_spec_path'};
104 6         173 my $build_options_route = plugin_setting->{'build_options_route'};
105 6 50       86 if ($path_to_spore_def)
106             {
107 6 50       80 $path_to_spore_def = File::Spec->catfile( setting('appdir') , $path_to_spore_def) unless (File::Spec->file_name_is_absolute($path_to_spore_def));
108 6         204 $rh_file = LoadFile($path_to_spore_def);
109             }
110              
111 6         178769 my $path_valid;
112              
113             #load validation hash
114 6         19 foreach my $method_name (keys(%{$rh_file->{'methods'}}))
  6         46  
115             {
116 28         68 my $method = $rh_file->{'methods'}->{$method_name}->{'method'};
117 28         54 my $complet_path = $rh_file->{'methods'}->{$method_name}->{'path'};
118 28         92 my ($path, $query_params) = split(/\?/, $complet_path);
119 28         51 my @additional_params;
120 28 100       66 if (defined $query_params)
121             {
122 2         10 @additional_params = map { $_ =~ s/=.*//g; $_ } split( /\&/, $query_params) ;
  2         12  
  2         9  
123 2         9 push @{$rh_file->{'methods'}->{$method_name}->{'required_params'}}, @additional_params;
  2         8  
124             }
125              
126 28         40 push @{$path_valid->{path}->{$path}}, $method;
  28         76  
127 28         177 push @{$path_valid->{method}->{$method}->{$path}->{params}},
128             {
129             required_params => $rh_file->{'methods'}->{$method_name}->{'required_params'},
130             optional_params => $rh_file->{'methods'}->{$method_name}->{'optional_params'},
131 28         54 form_data_params => $rh_file->{'methods'}->{$method_name}->{'form-data'},
132             };
133 28         96 $path_valid->{method}->{$method}->{$path}->{functions}->{$method_name} = 1;
134              
135             }
136 6 100       34 init_routes_options($path_valid->{path}) if (defined $build_options_route);
137 6         406 return $path_valid;
138             };
139              
140             =head2 init_routes_options
141              
142             force the routes on options method
143              
144             =cut
145              
146              
147              
148             sub init_routes_options(){
149 2     2 1 5 my $paths = shift;
150 2         4 foreach my $path (keys %{$paths}){
  2         7  
151       0     options $path => sub {
152 5         1057 };
153              
154             }
155             }
156              
157              
158              
159              
160             =head1 SUBROUTINES/METHODS
161              
162             =head2 check_spore_definition
163              
164             define spore validation to do on entered request
165              
166             =cut
167              
168             register 'check_spore_definition' => sub {
169             hook before => sub {
170 73     73   170692 my $req = request;
171 73         451 my %req_params = params;
172 73 50       14869 die "method request must be defined" unless (defined( $req->method() ) );
173 73 100       618 _returned_error( "route pattern request must be defined", 404) unless (defined( $req->{_route_pattern} ) );
174              
175             # my $all_route_pattern = $req->{_route_pattern};
176             #my $detail_route_pattern = split /?/, $route_pattern;
177 70 50       183 $path_validation = _load_path_validation() if !$path_validation;
178 70 100 100     202 unless (defined( $path_validation->{method}->{$req->method()}) || uc($req->method()) eq "OPTIONS" )
179             {
180 8         133 my $req_method = $req->method();
181 8         77 return _returned_error("no route define with method `$req_method'", 404);
182             }
183              
184             #TODO : return an error because path does not exist in specification
185 62 100 66     462 unless (defined( $path_validation->{method}->{$req->method()}->{$req->{_route_pattern}} )
      66        
186             || (uc($req->method()) eq "OPTIONS" && defined($path_validation->{path}->{$req->{_route_pattern}}))
187             )
188             {
189 8         105 my $req_route_pattern = $req->{_route_pattern};
190 8         23 return _returned_error("route pattern `$req_route_pattern' is not defined",404);
191             }
192              
193 54         490 my $is_ok = 0;
194             #IF method is OPTIONS list of methods set the headers and return ok
195 54 100       112 _returned_options_methods($path_validation->{path}->{$req->{_route_pattern}}) if (uc($req->method()) eq "OPTIONS" );
196 38         268 my $error;
197 38         54 foreach my $route_defined (@{$path_validation->{method}->{$req->method()}->{$req->{_route_pattern}}->{params}})
  38         84  
198             {
199 46         564 my $ko;
200 46         99 my $ra_required_params = $route_defined->{'required_params'};
201 46         72 my $ra_optional_params = $route_defined->{'optional_params'};
202 46         63 my $ra_form_data_params;
203 46 100       103 if ($route_defined->{'form_data_params'}){
204 3         4 foreach my $k (keys %{$route_defined->{'form_data_params'}}){
  3         10  
205 3         5 push @{$ra_form_data_params}, $k;
  3         9  
206             }
207             }
208              
209             # check if required params are present
210              
211 46         65 foreach my $required_param (@{$ra_required_params})
  46         92  
212             {
213 81 100       578 if (!defined params->{$required_param})
214             {
215 18         236 $error = "required params `$required_param' is not defined";
216 18         35 $ko = 1;
217             }
218             }
219 46 100       400 next if $ko;
220              
221 30         61 my @list_total = ('format');
222 30 50       82 @list_total = (@list_total, @{$ra_required_params}) if defined($ra_required_params);
  30         71  
223 30 50       71 @list_total = (@list_total, @{$ra_optional_params}) if defined($ra_optional_params);
  30         73  
224 30 100       64 @list_total = (@list_total, @{$ra_form_data_params}) if defined($ra_form_data_params);
  3         6  
225             # check for each params if they are specified in spore spec
226            
227 30         74 foreach my $param (keys %req_params)
228             {
229 79 100       136 if (!(grep {/^$param$/} @list_total))
  303         1393  
230             {
231 10         25 $error = "parameter `$param' is unknown";
232 10         14 $ko = 1 ;
233             }
234             }
235 30 100       81 next if $ko;
236 20         53 $is_ok = 1;
237             }
238 38 100       98 return _returned_error($error,400) unless $is_ok;
239            
240             #set the access-control-allow-credentials if needed
241 20         81 _set_access_control_header($path_validation->{path}->{$req->{_route_pattern}});
242 5     5   1318 };
243             };
244              
245              
246             =head2 get_functions_from_request
247              
248             return the hash of functions available from method and path.
249              
250             =cut
251              
252             register 'get_functions_from_request' => sub {
253 0     0   0 my $req = request;
254              
255 0 0       0 $path_validation = _load_path_validation() if !$path_validation;
256 0         0 my $method = $req->method();
257 0         0 my $path = $req->{_route_pattern};
258 0         0 my $functions = $path_validation->{method}->{$method}->{$path}->{functions};
259 0         0 return $functions;
260             };
261              
262             # format the error returned
263             sub _returned_error
264             {
265 37     37   70 my $str_error = shift;
266 37         58 my $code_error = shift;
267 37   50     81 $code_error ||= 400;
268 37         122 set serializer => 'JSON';
269 37         106433 debug $str_error."\n";
270             #return halt(send_error($str_error,400));
271 37 100       1918 if ($code_error == 400)
    50          
272             {
273 18         56 return halt(status_bad_request($str_error));
274             }
275             elsif ($code_error == 404)
276             {
277 19         91 return halt(status_not_found($str_error));
278             }
279             else
280 0         0 {die "Unknown code";}
281             }
282              
283             # Format and return the options method
284             sub _returned_options_methods
285             {
286 16     16   126 my $methods = shift;
287 16 50       54 if (defined $methods){
288 16         46 set serializer => 'JSON';
289 16         3696 _set_access_control_header($methods);
290 16         827 status 200;
291 16         335 return halt('{"status":200,"message":"OK"}');
292             }
293             else{
294 0         0 set serializer => 'JSON';
295 0         0 status 404;
296 0         0 return halt('{"status":404,"message":"no route exists"}');
297             }
298             }
299              
300             # Set the access control header of each request following the configuration
301             sub _set_access_control_header
302             {
303 36     36   78 my $methods = shift;
304 36         84 my $req = request;
305 36         193 my %seen = ();
306 36         110 my $build_options_route = plugin_setting->{'build_options_route'};
307 36         627 my @unique_methods = grep { !$seen{$_}++ } @{$methods};
  100         297  
  36         78  
308 36         57 my $origin_allowed;
309             #check that header contain origin and that url is permit by api
310             $origin_allowed = $req->header('Origin') if ( defined $req->header('Origin')
311             && defined $build_options_route->{'header_allow_allow_origins'}
312 36 100 66     112 && $req->header('Origin') ~~ @{$build_options_route->{'header_allow_allow_origins'}}
  11   100     757  
313             );
314 36   100     1557 header 'access-control-allow-credentials' => $build_options_route->{'header_allow_credentials'} || '';
315 36   100     2202 header 'access-control-allow-headers' => $build_options_route->{'header_allow_headers'} || '';
316 36         1919 header 'access-control-allow-methods' => join(",",@unique_methods,'OPTIONS');
317 36 100       1812 header 'access-control-allow-origin' => $origin_allowed if defined $origin_allowed;
318 36   100     626 header 'access-control-max-age' => $build_options_route->{'header_max_age'} || '';
319             }
320              
321              
322             =head1 AUTHOR
323              
324             Nicolas Oudard, C<< >>
325              
326             =head1 BUGS
327              
328             Please report any bugs or feature requests to C, or through
329             the web interface at L. I will be notified, and then you'll
330             automatically be notified of progress on your bug as I make changes.
331              
332              
333             =head1 SUPPORT
334              
335             You can find documentation for this module with the perldoc command.
336              
337             perldoc Dancer::Plugin::SporeDefinitionControl
338              
339              
340             You can also look for information at:
341              
342             =over 4
343              
344             =item * RT: CPAN's request tracker
345              
346             L
347              
348             =item * AnnoCPAN: Annotated CPAN documentation
349              
350             L
351              
352             =item * CPAN Ratings
353              
354             L
355              
356             =item * Search CPAN
357              
358             L
359              
360             =back
361              
362              
363             =head1 ACKNOWLEDGEMENTS
364              
365              
366             =head1 LICENSE AND COPYRIGHT
367              
368             Copyright 2010 Nicolas Oudard.
369              
370             This program is free software; you can redistribute it and/or modify it
371             under the terms of either: the GNU General Public License as published
372             by the Free Software Foundation; or the Artistic License.
373              
374             See http://dev.perl.org/licenses/ for more information.
375              
376              
377             =cut
378              
379             register_plugin;
380             1; # End of Dancer::Plugin::SporeDefinitionControl