File Coverage

blib/lib/Dancer/Plugin/Paginate.pm
Criterion Covered Total %
statement 65 74 87.8
branch 20 34 58.8
condition 3 8 37.5
subroutine 8 8 100.0
pod n/a
total 96 124 77.4


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Paginate;
2              
3 2     2   472282 use strict;
  2         5  
  2         81  
4 2     2   10 use warnings;
  2         4  
  2         66  
5              
6 2     2   1771 use Dancer ':syntax';
  2         327648  
  2         15  
7 2     2   2679 use Dancer::Plugin;
  2         2959  
  2         2195  
8              
9             =head1 NAME
10              
11             Dancer::Plugin::Paginate - HTTP 1.1 Range-based Pagination for Dancer apps.
12              
13             =head1 VERSION
14              
15             Version 1.0.2
16              
17             =cut
18              
19             our $VERSION = '1.0.2';
20              
21             =head1 DESCRIPTION
22              
23             HTTP 1.1 Range-based Pagination for Dancer apps.
24              
25             Provides a simple wrapper to provide pagination of results via the HTTP 1.1 Range headers for AJAX requests.
26              
27             =head1 SYNOPSIS
28              
29             To use, simply add the "paginate" keyword to any route that should support HTTP Range headers. The HTTP Request
30             will be processed and Dancer session variables will be populated with the items being requested.
31              
32             use Dancer::Plugin::Paginate;
33              
34             get '/data' => paginate sub { ... }
35             ...
36              
37             =head1 Configuration Options
38              
39             =cut
40              
41             my $settings = plugin_setting;
42              
43             =head2 Ajax-Only
44              
45             Options: I
46              
47             Default: B
48              
49             Determines if paginate should only operate on Ajax requests.
50              
51             =cut
52              
53             my $ajax_only = $settings->{'Ajax-Only'} || true;
54              
55             =head2 Mode
56              
57             Options: I
58              
59             Default: B
60              
61             Controls if paginate will look for the pagination in the headers, parameters, or both.
62              
63             If set to both, headers will be preferred.
64              
65             =head3 Headers Mode
66              
67             Header mode will look for the following 2 Headers:
68              
69             =over
70              
71             =item Content-Range
72              
73             =item Range-Unit
74              
75             =back
76              
77             Both are required.
78              
79             You can read more about these at L and
80             L.
81              
82             Range-Unit will be returned to your app, but is not validated in any way.
83              
84             =head3 Parameters mode
85              
86             Parameters mode will look the following parameters in any of Dancer's
87             parameter sources (query, route, or body):
88              
89             =over
90              
91             =item Start
92              
93             =item End
94              
95             =item Range-Unit
96              
97             =back
98              
99             Start and End are required. Range-Unit will be populated with an empty string if not
100             available.
101              
102             =cut
103              
104             my $mode = $settings->{Mode} || 'headers';
105              
106             =head1 Keywords
107              
108             =head2 paginate
109              
110             The paginate keyword is used to add a pagination processing to a route. It will:
111              
112             =over
113              
114             =item Check if the request is AJAX (and stop processing if set to ajax-only).
115              
116             =item Extract the data from Headers, Parameters, or Both.
117              
118             =item Store these in Dancer Session Variables (defined below).
119              
120             =item Run the provided coderef for the route.
121              
122             =item Add proper headers and change status to 206 if coderef was successful.
123              
124             =back
125              
126             Vars:
127              
128             =over
129              
130             =item range_available - Boolean. Will return true if range was found.
131              
132             =item range - An arrayref of [start, end].
133              
134             =item range_unit - The Range Unit provided in the request.
135              
136             =back
137              
138             In your response, you an optionally provide the following Dancer Session Variables to customize response:
139              
140             =over
141              
142             =item total - The total count of items. Will return '*' if not provided.
143              
144             =item return_range - An arrayref of provided [start, end] values in your response. Original will be reused if not provided.
145              
146             =item return_range_unit - The unit of the range in your response. Original will be reused if not provided.
147              
148             =back
149              
150             =cut
151              
152             sub paginate {
153 4     4   3613 my $coderef = shift;
154             return sub {
155 5 50   5   17935 if ($ajax_only) {
156 5 100       26 return $coderef->() unless request->is_ajax();
157             }
158 4         335 my $range;
159 4 50       44 if ($mode =~ m/headers/i) {
    50          
    50          
160 0         0 $range = _parse_headers(request->header('Range'), request->header('Range-Unit'));
161 0 0       0 return $coderef->() unless defined $range->{Start};
162             }
163             elsif ($mode =~ m/parameters/i) {
164 0         0 $range = _parse_parameters(request->params);
165 0 0       0 return $coderef->() unless defined $range->{Start};
166             }
167             elsif ($mode =~ m/both/i) {
168 4         12 $range = _parse_headers(request->header('Range'), request->header('Range-Unit'));
169 4 100       16 unless (defined $range->{Start}) {
170 1         4 my %params = request->params;
171 1         344 $range = _parse_parameters(\%params);
172             }
173 4 50       14 return $coderef->() unless defined $range->{Start};
174             }
175             else {
176 0         0 Dancer::Logger::warning("[Dancer::Plugin::Paginate] Mode set to an invalid value. Valid values: [headers|parameters|both]");
177 0         0 return $coderef->();
178             }
179 4         29 var range => [$range->{Start}, $range->{End}];
180 4         50 var range_unit => $range->{Unit};
181 4         46 var range_available => true;
182 4         51 my $content = $coderef->();
183 4         2191 my $response = Dancer::SharedData->response;
184 4         33 $response->content($content);
185 4 50       35 unless ( $response->status == 200 ) {
186 0         0 return $response;
187             }
188 4         42 my $total = var 'total';
189 4 100       39 unless ($total) {
190 3         8 $total = '*';
191             }
192 4         12 my $returned_range = var 'return_range';
193 4 100       35 unless ($returned_range) {
194 3         8 $returned_range = var 'range';
195             }
196 4         25 my $returned_range_string = "${$returned_range}[0]-${$returned_range}[1]";
  4         13  
  4         9  
197 4         14 my $returned_range_unit = var 'return_range_unit';
198 4 50       35 unless ($returned_range_unit) {
199 4         9 $returned_range_unit = $range->{Unit};
200             }
201 4         11 my $content_range = "$returned_range_string/$total";
202 4         14 $response->header( 'Content-Range' => $content_range );
203 4         196 $response->header( 'Range-Unit' => $returned_range_unit );
204 4         224 my $accept_ranges = var 'accept_ranges';
205 4 50       40 unless ($accept_ranges) {
206 4         9 $accept_ranges = $range->{Unit};
207             }
208 4         12 $response->header( 'Accept-Ranges' => $accept_ranges );
209 4         165 $response->status(206);
210 4         100 return $response;
211 4         34 };
212             }
213              
214             register paginate => \&paginate;
215              
216             sub _parse_headers {
217 4     4   295 my ($range, $unit) = @_;
218              
219 4 100       14 unless (defined $unit) {
220 1         3 return {}; # Unit is required
221             }
222              
223 3         14 my ($start, $end) = split '-', $range;
224 3 50 33     22 unless (defined $start && defined $end) {
225 0         0 return {}; # If we can't parse the start and end, forget it.
226             }
227              
228 3         18 my $results = {
229             Start => $start,
230             End => $end,
231             Unit => $unit
232             };
233 3         8 return $results;
234             }
235              
236             sub _parse_parameters {
237 1     1   2 my $params = shift;
238 1         4 my $start = $params->{'Start'};
239 1         3 my $end = $params->{'End'};
240 1   50     7 my $unit = $params->{'Range-Unit'} || '';
241 1 50 33     11 unless (defined $start && defined $end) {
242 0         0 return {};
243             }
244 1         5 my $results = {
245             Start => $start,
246             End => $end,
247             Unit => $unit
248             };
249 1         4 return $results;
250             }
251              
252             =head1 AUTHOR
253              
254             Colin Ewen, C<< >>
255              
256             =head1 BUGS
257              
258             Please report any bugs or feature requests to C, or through
259             the web interface at L. I will be notified, and then you'll
260             automatically be notified of progress on your bug as I make changes.
261              
262              
263             =head1 SUPPORT
264              
265             You can find documentation for this module with the perldoc command.
266              
267             perldoc Dancer::Plugin::Paginate
268              
269              
270             This is developed on GitHub - please feel free to raise issues or pull requests
271             against the repo at: L.
272              
273              
274             =head1 ACKNOWLEDGEMENTS
275              
276             My thanks to David Precious, C<< >> for his
277             Dancer::Plugin::Auth::Extensible framework, which provided the Keyword
278             syntax used by this module. Parts were also used for testing purposes.
279              
280              
281             =head1 LICENSE AND COPYRIGHT
282              
283             Copyright 2014 Colin Ewen.
284              
285             This program is free software; you can redistribute it and/or modify it
286             under the terms of the the Artistic License (2.0). You may obtain a
287             copy of the full license at:
288              
289             L
290              
291             Any use, modification, and distribution of the Standard or Modified
292             Versions is governed by this Artistic License. By using, modifying or
293             distributing the Package, you accept this license. Do not use, modify,
294             or distribute the Package, if you do not accept this license.
295              
296             If your Modified Version has been derived from a Modified Version made
297             by someone other than you, you are nevertheless required to ensure that
298             your Modified Version complies with the requirements of this license.
299              
300             This license does not grant you the right to use any trademark, service
301             mark, tradename, or logo of the Copyright Holder.
302              
303             This license includes the non-exclusive, worldwide, free-of-charge
304             patent license to make, have made, use, offer to sell, sell, import and
305             otherwise transfer the Package with respect to any patent claims
306             licensable by the Copyright Holder that are necessarily infringed by the
307             Package. If you institute patent litigation (including a cross-claim or
308             counterclaim) against any party alleging that the Package constitutes
309             direct or contributory patent infringement, then this Artistic License
310             to you shall terminate on the date that such litigation is filed.
311              
312             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
313             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
314             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
315             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
316             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
317             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
318             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
319             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
320              
321              
322             =cut
323              
324             register_plugin;
325              
326             true;
327              
328             # End of Dancer::Plugin::Paginate