File Coverage

blib/lib/CatalystX/RequestModel/ContentBodyParser/JSON.pm
Criterion Covered Total %
statement 22 22 100.0
branch n/a
condition 4 6 66.6
subroutine 8 8 100.0
pod 0 3 0.0
total 34 39 87.1


line stmt bran cond sub pod time code
1             package CatalystX::RequestModel::ContentBodyParser::JSON;
2              
3 6     6   3167 use warnings;
  6         18  
  6         229  
4 6     6   63 use strict;
  6         27  
  6         152  
5 6     6   2682 use CatalystX::RequestModel::Utils::InvalidJSON;
  6         2196  
  6         230  
6              
7 6     6   71 use base 'CatalystX::RequestModel::ContentBodyParser';
  6         20  
  6         2027  
8              
9 84     84 0 244 sub content_type { 'application/json' }
10              
11             sub default_attr_rules {
12 33     33 0 74 my ($self, $attr_rules) = @_;
13 33         178 return +{ flatten=>0, %$attr_rules };
14             }
15              
16             sub new {
17 3     3 0 18 my ($class, %args) = @_;
18 3         10 my $self = bless \%args, $class;
19 3   66     50 $self->{context} ||= $self->_parse_json_body;
20 2         9 return $self;
21             }
22              
23             sub _parse_json_body {
24 3     3   9 my ($self) = @_;
25             my $json = eval {
26             $self->{ctx}->req->body_data;
27 3   66     6 } || do {
28             CatalystX::RequestModel::Utils::InvalidJSON->throw(parsing_error=>$@);
29             };
30              
31 2         1075 return $json;
32             }
33              
34             1;
35              
36             =head1 NAME
37              
38             CatalystX::RequestModel::ContentBodyParser::JSON
39              
40             =head1 SYNOPSIS
41              
42             TBD
43              
44             =head1 DESCRIPTION
45              
46             Given a valid JSON request body, parse it and inflate request models as defined or throw various
47             exceptions otherwise. for example given the following nested request models:
48              
49             package Example::Model::API::AccountRequest;
50              
51             use Moose;
52             use CatalystX::RequestModel;
53              
54             extends 'Catalyst::Model';
55             namespace 'person';
56             content_type 'application/json';
57              
58             has username => (is=>'ro', property=>1);
59             has first_name => (is=>'ro', property=>1);
60             has last_name => (is=>'ro', property=>1);
61             has profile => (is=>'ro', property=>+{model=>'API::AccountRequest::Profile' });
62             has person_roles => (is=>'ro', property=>+{ indexed=>1, model=>'API::AccountRequest::PersonRole' });
63             has credit_cards => (is=>'ro', property=>+{ indexed=>1, model=>'API::AccountRequest::CreditCard' });
64              
65             __PACKAGE__->meta->make_immutable();
66              
67             package Example::Model::API::AccountRequest::Profile;
68              
69             use Moose;
70             use CatalystX::RequestModel;
71              
72             extends 'Catalyst::Model';
73              
74             has id => (is=>'ro', property=>1);
75             has address => (is=>'ro', property=>1);
76             has city => (is=>'ro', property=>1);
77             has state_id => (is=>'ro', property=>1);
78             has zip => (is=>'ro', property=>1);
79             has phone_number => (is=>'ro', property=>1);
80             has birthday => (is=>'ro', property=>1);
81             has registered => (is=>'ro', property=>+{ boolean=>1 });
82              
83             __PACKAGE__->meta->make_immutable();
84              
85             package Example::Model::API::AccountRequest::PersonRole;
86              
87             use Moose;
88             use CatalystX::RequestModel;
89              
90             extends 'Catalyst::Model';
91              
92             has role_id => (is=>'ro', property=>1);
93              
94             __PACKAGE__->meta->make_immutable();
95              
96             package Example::Model::API::AccountRequest::CreditCard;
97              
98             use Moose;
99             use CatalystX::RequestModel;
100              
101             extends 'Catalyst::Model';
102              
103             has id => (is=>'ro', property=>1);
104             has card_number => (is=>'ro', property=>1);
105             has expiration => (is=>'ro', property=>1);
106              
107             __PACKAGE__->meta->make_immutable();
108              
109             And the following POSTed JSON request body:
110              
111             {
112             "person":{
113             "username": "jjn",
114             "first_name": "john",
115             "last_name": "napiorkowski",
116             "profile": {
117             "id": 1,
118             "address": "1351 Miliary Road",
119             "city": "Little Falls",
120             "state_id": 7,
121             "zip": "42342",
122             "phone_number": 6328641827,
123             "birthday": "2222-01-01",
124             "registered": false
125             },
126             "person_roles": [
127             { "role_id": 1 },
128             { "role_id": 2 }
129             ],
130             "credit_cards": [
131             { "id":100, "card_number": 111222333444, "expiration": "2222-02-02" },
132             { "id":200, "card_number": 888888888888, "expiration": "3333-02-02" },
133             { "id":300, "card_number": 333344445555, "expiration": "4444-02-02" }
134             ]
135             }
136             }
137              
138             Will inflate a request model that provides:
139              
140             my $request_model = $c->model('API::AccountRequest');
141             Dumper $request_model->nested_params;
142              
143             +{
144             'person_roles' => [
145             {
146             'role_id' => 1
147             },
148             {
149             'role_id' => 2
150             }
151             ],
152             'profile' => {
153             'address' => '1351 Miliary Road',
154             'birthday' => '2222-01-01',
155             'id' => 1,
156             'state_id' => 7,
157             'phone_number' => 6328641827,
158             'registered' => 0,
159             'zip' => '42342',
160             'city' => 'Little Falls'
161             },
162             'credit_cards' => [
163             {
164             'card_number' => '111222333444',
165             'expiration' => '2222-02-02',
166             'id' => 100
167             },
168             {
169             'id' => 200,
170             'card_number' => '888888888888',
171             'expiration' => '3333-02-02'
172             },
173             {
174             'id' => 300,
175             'card_number' => '333344445555',
176             'expiration' => '4444-02-02'
177             }
178             ],
179             'first_name' => 'john',
180             'username' => 'jjn',
181             'last_name' => 'napiorkowski'
182             };
183              
184             =head1 VALUE PARSER CONFIGURATION
185              
186             This parser defines the following attribute properties which effect how a value is parsed.
187              
188             =head2 flatten
189              
190             Defaults to false. If enabled it will flatten an array value to a scalar which is the value of the
191             last item in the list. Probably not useful in JSON, it's more of a hack for HTML Form posts, which
192             makes it hard to be consistent in array versus scalar values, but no reason to not offer the feature
193             if you need it.
194              
195             =head2 always_array
196              
197             Similar to C<flatten> but opposite, it forces a value into an array even if there's just one value. Also
198             defaults to FALSE.
199              
200             =head1 EXCEPTIONS
201              
202             See L<CatalystX::RequestModel::ContentBodyParser> for inherited exceptions.
203              
204             =head2 Invalid JSON Content Body
205              
206             If the JSON in the request content body is invalid, we throw a L<CatalystX::RequestModel::Utils::InvalidJSON>
207             exception.
208              
209             =head1 AUTHOR
210              
211             See L<CatalystX::RequestModel>.
212            
213             =head1 COPYRIGHT
214            
215             See L<CatalystX::RequestModel>.
216              
217             =head1 LICENSE
218            
219             See L<CatalystX::RequestModel>.
220            
221             =cut