File Coverage

blib/lib/CatalystX/QueryModel/QueryParser.pm
Criterion Covered Total %
statement 31 32 96.8
branch 6 10 60.0
condition 1 3 33.3
subroutine 6 7 85.7
pod 0 4 0.0
total 44 56 78.5


line stmt bran cond sub pod time code
1             package CatalystX::QueryModel::QueryParser;
2              
3 6     6   51 use warnings;
  6         26  
  6         202  
4 6     6   48 use strict;
  6         15  
  6         164  
5 6     6   59 use base 'CatalystX::RequestModel::ContentBodyParser';
  6         15  
  6         3724  
6              
7 0     0 0 0 sub content_type { 'application/x-www-form-urlencoded' }
8              
9             sub default_attr_rules {
10 20     20 0 58 my ($self, $attr_rules) = @_;
11 20         119 return +{ flatten=>1, %$attr_rules };
12             }
13              
14             sub expand_cgi {
15 9     9 0 22 my ($self) = shift;
16 9         31 my $params = $self->{ctx}->req->query_parameters;
17 9         600 my $data = +{};
18 9         36 foreach my $param (keys %$params) {
19 17         56 my (@segments) = split /\./, $param;
20 17         48 my $data_ref = \$data;
21 17         30 foreach my $segment (@segments) {
22 20 100       49 $$data_ref = {} unless defined $$data_ref;
23              
24 20         57 my ($prefix,$i) = ($segment =~m/^(.+)?\[(\d*)\]$/);
25 20 50       45 $segment = $prefix if defined $prefix;
26              
27 20 50       47 die "CGI param clash for $param=$_" unless ref $$data_ref eq 'HASH';
28 20         54 $data_ref = \($$data_ref->{$segment});
29 20 50       58 $data_ref = \($$data_ref->{$i}) if defined $i;
30             }
31 17 50       39 die "CGI param clash for $param value $params->{$param}" if defined $$data_ref;
32 17         48 $$data_ref = $params->{$param};
33             }
34              
35 9         35 return $data;
36             }
37              
38             sub new {
39 9     9 0 37 my ($class, %args) = @_;
40 9         22 my $self = bless \%args, $class;
41 9   33     73 $self->{context} ||= $self->expand_cgi;
42              
43 9         24 return $self;
44             }
45              
46             1;
47              
48             =head1 NAME
49              
50             CatalystX::QueryModel::QueryParser - Parse URL query parameters
51              
52             =head1 SYNOPSIS
53              
54             TBD
55              
56             =head1 DESCRIPTION
57              
58             Given a flat list of query parameters will attempt to convert it to a hash of values,
59             with nested and arrays of nested values as needed. For example you can convert something like:
60              
61             .-------------------------------------+--------------------------------------.
62             | Parameter | Value |
63             +-------------------------------------+--------------------------------------+
64             | person.username | jjn |
65             | person.first_name [multiple] | 2, John |
66             | person.last_name | Napiorkowski |
67             | person.password | abc123 |
68             | person.password_confirmation | abc123 |
69             '-------------------------------------+--------------------------------------'
70              
71             Into:
72              
73             {
74             first_name => "John",
75             last_name => "Napiorkowski",
76             username => "jjn",
77             }
78              
79             Or:
80              
81             .-------------------------------------+--------------------------------------.
82             | Parameter | Value |
83             +-------------------------------------+--------------------------------------+
84             | person.first_name [multiple] | 2, John |
85             | person.last_name | Napiorkowski |
86             | person.person_roles[0]._nop | 1 |
87             | person.person_roles[1].role_id | 1 |
88             | person.person_roles[2].role_id | 2 |
89             | person.username | jjn |
90             '-------------------------------------+--------------------------------------'
91              
92             Into:
93              
94             {
95             first_name => "John",
96             last_name => "Napiorkowski",
97             username => "jjn",
98             person_roles => [
99             {
100             role_id => 1,
101             },
102             {
103             role_id => 2,
104             },
105             ],
106             }
107              
108             We define some settings described below to help you deal with some of the issues you find when trying
109             to parse HTML form posted body content. For now please see the test cases for more examples.
110              
111             =head1 VALUE PARSER CONFIGURATION
112              
113             This parser defines the following attribute properties which effect how a value is parsed.
114              
115             =head2 flatten
116              
117             If the value associated with a field is an array, flatten it to a single value. Its really a hack to deal
118             with HTML form POST and Query parameters since the way those formats work you can't be sure if a value is
119             flat or an array.
120              
121             =head2 always_array
122              
123             Similar to C<flatten> but opposite, it forces a value into an array even if there's just one value.
124              
125             B<NOTE>: The attribute property settings C<flatten> and C<always_array> are currently exclusive (only one of
126             the two will apply if you supply both. The C<always_array> property always takes precedence. At some point
127             in the future supplying both might generate an exception so its best not to do that. I'm only leaving it
128             allowed for now since I'm not sure there's a use case for both.
129              
130             =head1 INDEXING
131              
132             When using deeply nested parameters with repeated elements you can use a naming convention to indicate ordering:
133              
134             param[index]...
135              
136             For example:
137              
138             .-------------------------------------+--------------------------------------.
139             | Parameter | Value |
140             +-------------------------------------+--------------------------------------+
141             | person.person_roles[0]._nop | 1 |
142             | person.person_roles[1].role_id | 1 |
143             | person.person_roles[2].role_id | 2 |
144             | person.person_roles[].role_id | 3 |
145             '-------------------------------------+--------------------------------------'
146              
147             Could convert to:
148              
149             [
150             {
151             role_id => 1,
152             },
153             {
154             role_id => 2,
155             },
156             ]
157              
158             Please note the the index value is just used for ordering purposed, the actual value is tossed after its
159             used to do the sorting. Also if you just need to add a new item to the end of the indexed list you can use an
160             empty index '[]' as in the example above. You might find this useful if you are building HTML forms and need
161             to tack on an extra value but don't know the last index.
162              
163             =head1 EXCEPTIONS
164              
165             See L<CatalystX::RequestModel::ContentBodyParser> for exceptions.
166              
167             =head1 AUTHOR
168              
169             See L<CatalystX::QueryModel>.
170            
171             =head1 COPYRIGHT
172            
173             See L<CatalystX::QueryModel>.
174              
175             =head1 LICENSE
176            
177             See L<CatalystX::QueryModel>.
178            
179             =cut
180