File Coverage

blib/lib/Catalyst/TraitFor/Request/QueryFromJSONY.pm
Criterion Covered Total %
statement 20 22 90.9
branch 6 6 100.0
condition 5 9 55.5
subroutine 6 7 85.7
pod n/a
total 37 44 84.0


line stmt bran cond sub pod time code
1             package Catalyst::TraitFor::Request::QueryFromJSONY;
2              
3 2     2   388415 use Moo::Role;
  2         56430  
  2         15  
4 2     2   5480 use JSONY;
  2         71071  
  2         860  
5              
6             our $VERSION = '0.002';
7              
8             has query_data_options => (
9             is=>'ro',
10             required=>1,
11             lazy=>1,
12             builder=>'build_query_data_options');
13              
14             sub build_query_data_options {
15             return +{
16 1     1   11 param_missing => sub { my ($req, $param) = @_; return '{}' },
  1         3  
17 0     0   0 parse_error => sub { my ($req, $param, $err) = @_; die $err },
  0         0  
18 7     7   162 };
19             }
20              
21             has _jsony => (
22             is=>'ro',
23             required=>1,
24             lazy=>1,
25             builder=>'build_jsony');
26              
27 7     7   154 sub build_jsony { JSONY->new }
28              
29             has _query_data_cache => (
30             is=>'ro',
31             required=>1,
32             init_arg=>undef,
33             lazy=>1,
34             default=>sub { +{} });
35              
36             sub query_data {
37 7     7   210855 my ($self, @params) = @_;
38 7         19 my $proto = +{};
39              
40 7 100 66     95 $proto = pop @params if (@params && ref($params[-1]) eq 'HASH');
41 7 100       38 @params = ('q') unless @params;
42              
43 7         15 my %local_options = (%{$self->query_data_options}, %$proto);
  7         41  
44              
45             return map {
46 7         72 my $val = exists $self->query_parameters->{$_} ?
47             $self->query_parameters->{$_} :
48 11 100       407 $local_options{param_missing}->($self, $_);
49              
50             my $deserialized = eval { $self->_jsony->load($val) }
51 11   66     383 || $local_options{parse_error}->($self, $val, $@);
52              
53 11   33     53404 $self->_query_data_cache->{$_} ||= $deserialized;
54             } @params;
55             }
56              
57             1;
58              
59             =head1 NAME
60              
61             Catalyst::TraitFor::Request::QueryFromJSONY - Handle complex query parameters using JSONY
62              
63             =head1 SYNOPSIS
64              
65             For L<Catalyst> v5.90090+
66              
67             package MyApp;
68              
69             use Catalyst;
70              
71             MyApp->request_class_traits(['Catalyst::TraitFor::Request::QueryFromJSONY']);
72             MyApp->setup;
73              
74             For L<Catalyst> older than v5.90090
75              
76             package MyApp;
77              
78             use Catalyst;
79             use CatalystX::RoleApplicator;
80              
81             MyApp->apply_request_class_roles('Catalyst::TraitFor::Request::QueryFromJSONY');
82             MyApp->setup;
83              
84             In a controller:
85              
86             package MyApp::Controller::Example;
87              
88             use Moose;
89             use MooseX::MethodAttributes;
90             use Data::Dumper;
91              
92             sub echo :Local {
93             my ($self, $c) = @_;
94             $c->res->body( Dumper $c->req->query_data );
95             }
96              
97             Example test case:
98              
99             ok my $res = request GET "/example/echo?q={'id':100,'age':['>',10]}";
100             is_deeply eval $res->content, {
101             'id' => 100,
102             'age' => [ '>', 10 ]
103             };
104              
105             =head1 DESCRIPTION
106              
107             This is an early access release of this module. Experimentation as to the best
108             approach is ongoing.
109              
110             There are cases when you'd like to express complex data structures in your URL
111             query part (tha bit after the '?'). There's been a number of attempts at this,
112             this module is yet another. In this version we allow for a query parameter 'q'
113             to be a L<JSONY> serialized string (L<JSONY> is basically JSON relaxed a bit to
114             reduce a bit of verbosity and smooth over common errors that are more pedantic
115             that useful). We deserialize this string and place its value in 'query_data'.
116              
117             This only happens if you request the query_data attribute, so there's no overhead
118             to simply having this installed.
119              
120             You can have other 'classic' query parameters mixed in with the 'q' parameter, but
121             for no only 'q' is deserialized. The original value of 'q' is preserved in the
122             original query_parameter method.
123              
124             =head1 METHODS
125              
126             This role defines the following methods.
127              
128             =head2 query_data (?@query_params, ?\%options)
129              
130             For each item in @query_params that exists in $request->query_parameters deserialize
131             using L<JSONY> and return the data references (could be a hashref, or arrayref
132             depending on the query construction.
133              
134             If no @query_params are submitted, assume 'q' as the default.
135              
136             The %options hash allows you to set callback to handle exceptional conditions. All callbacks
137             get invoked with two parameters, the current $request object, and the name of the
138             query parameter that caused the condition. For example the follow substitutes the string
139             '[]' when a $key is missing from %{$c->req->query_parameters}:
140              
141             $c->req->query_data(qw/a b c/, +{
142             param_missing => sub {
143             my ($req, $key) = @_;
144             return '[]';
145             }
146             });
147              
148             Currently we support the following exceptional conditions:
149              
150             =head3 param_missing
151              
152             Gets $request, $key
153              
154             This is the callback that gets invoked when $c->req->query_paramerters->{$key} does not
155             exist. The default behavior is to return an empty string, which JSONY deserialized into
156             a hashref. This allows you to request parameters that are optional and not product an
157             exception.
158              
159             =head3 parse_error
160              
161             Gets $request, $key, $error_message
162              
163             This callback is called when JSONY throws an exception trying to parse the value
164             associated with $key. The default is to just rethrow the error.
165              
166             =head1 AUTHOR
167            
168             John Napiorkowski L<email:jjnapiork@cpan.org>
169            
170             =head1 SEE ALSO
171            
172             L<Catalyst>, L<Catalyst::Request>, L<JSONY>
173              
174             =head1 COPYRIGHT & LICENSE
175            
176             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
177            
178             This library is free software; you can redistribute it and/or modify it under
179             the same terms as Perl itself.
180              
181             =cut