File Coverage

blib/lib/CatalystX/RequestModel/DoesRequestModel.pm
Criterion Covered Total %
statement 96 105 91.4
branch 51 64 79.6
condition 9 15 60.0
subroutine 18 19 94.7
pod 2 11 18.1
total 176 214 82.2


line stmt bran cond sub pod time code
1             package CatalystX::RequestModel::DoesRequestModel;
2              
3 6     6   3260 use Moo::Role;
  6         18  
  6         56  
4 6     6   23436 use Scalar::Util;
  6         16  
  6         328  
5 6     6   2626 use CatalystX::RequestModel::Utils::BadRequest;
  6         2288  
  6         9259  
6              
7             has ctx => (is=>'ro');
8             has current_namespace => (is=>'ro', predicate=>'has_current_namespace');
9             has current_parser => (is=>'ro', predicate=>'has_current_parser');
10             has catalyst_component_name => (is=>'ro');
11              
12             sub namespace {
13 51     51   190 my ($class_or_self, @data) = @_;
14 51 100       200 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
15 51 100       181 if(@data) {
16 18         57 @data = map { split /\./, $_ } @data;
  18         120  
17 18         106 CatalystX::RequestModel::_add_metadata($class, 'namespace', @data);
18             }
19              
20 51 100       411 return $class_or_self->namespace_metadata if $class_or_self->can('namespace_metadata');
21             }
22              
23             sub content_in {
24 51     51   208 my ($class_or_self, $ct) = @_;
25 51 100       167 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
26 51 100       153 CatalystX::RequestModel::_add_metadata($class, 'content_in', $ct) if $ct;
27              
28 51 100       350 if($class_or_self->can('content_in_metadata')) {
29 11         42 my ($ct) = $class_or_self->content_in_metadata; # needed because this returns an array but we only want the first one
30 11 50       65 return $ct if $ct;
31             }
32             }
33              
34             sub get_content_in {
35 45     45 0 932 my $self = shift;
36 45         187 my $ct = $self->content_in;
37 45 100       190 return lc($ct) if $ct;
38 40         205 return 'body';
39             }
40              
41             sub content_type {
42 63     63   305 my ($class_or_self, @ct) = @_;
43 63 100       224 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
44 63 100       377 CatalystX::RequestModel::_add_metadata($class, 'content_type', @ct) if @ct;
45              
46 63 50       389 if($class_or_self->can('content_type_metadata')) {
47 63         232 my (@ct) = $class_or_self->content_type_metadata; # needed because this returns an array but we only want the first onei
48 63         254 return @ct;
49             }
50             }
51              
52             sub property {
53 354     354   972 my ($class_or_self, $attr, $data_proto, $options) = @_;
54 354 50       850 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
55 354 50       846 if(defined $data_proto) {
56 354 50 50     1216 my $data = (ref($data_proto)||'') eq 'HASH' ? $data_proto : +{ name => $attr };
57 354 100       914 $data->{name} = $attr unless exists($data->{name});
58 354         1572 CatalystX::RequestModel::_add_metadata($class, 'property_data', +{$attr => $data});
59             }
60             }
61              
62             sub properties {
63 63     63   152 my ($class_or_self, @data) = @_;
64 63 50       195 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
65 63         213 while(@data) {
66 0         0 my $attr = shift(@data);
67 0 0 0     0 my $data = (ref($data[0])||'') eq 'HASH' ? shift(@data) : +{ name => $attr };
68 0 0       0 $data->{name} = $attr unless exists($data->{name});
69 0         0 CatalystX::RequestModel::_add_metadata($class, 'property_data', +{$attr => $data});
70             }
71              
72 63 50       374 return $class_or_self->property_data_metadata if $class_or_self->can('property_data_metadata');
73             }
74              
75             sub COMPONENT {
76 84     84 0 946781 my ($class, $app, $args) = @_;
77 84         845 $args = $class->merge_config_hashes($class->config, $args);
78 84         15992 return bless $args, $class;
79             }
80              
81             ## TODO handle if we are wrapping a model that already does ACCEPT_CONTEXT
82             sub ACCEPT_CONTEXT {
83 33     33 0 2364 my $self = shift;
84 33         67 my $c = shift;
85              
86 33         288 my %args = (%$self, @_);
87 33         185 my %request_args = $self->parse_content_body($c, %args);
88 32         208 my %init_args = (%args, %request_args, ctx=>$c);
89 32         102 my $class = ref($self);
90              
91 32         141 return my $request_model = $self->build_request_model($c, $class, %init_args);
92             }
93              
94             sub build_request_model {
95 32     32 0 163 my ($self, $c, $class, %init_args) = @_;
96             my $request_model = eval {
97             $class->new(%init_args)
98 32   66     86 } || do {
99             CatalystX::RequestModel::Utils::BadRequest->throw(class=>$class, error_trace=>$@);
100             };
101              
102 31         10329 return $request_model;
103             }
104              
105             sub parse_content_body {
106 33     33 0 113 my ($self, $c, %args) = @_;
107              
108 33         196 my @rules = $self->properties;
109 33         145 my @ns = $self->get_namespace(%args);
110 33         152 my $parser_class = $self->get_content_body_parser_class($self->get_content_type($c));
111             my $parser = exists($args{current_parser}) ?
112             $args{current_parser} :
113 33 100       284 $parser_class->new(ctx=>$c, request_model=>$self );
114              
115 32 100       117 $parser->{context} = $args{context} if exists $args{context}; ## TODO ulgy
116              
117 32         225 return my %request_args = $parser->parse(\@ns, \@rules);
118             }
119              
120             sub get_content_type {
121 33     33 0 107 my ($self, $c) = @_;
122 33         110 my $ct = $c->req->content_type;
123 33 100 66     3427 return 'application/x-www-form-urlencoded' if !$ct && $c->req->method eq 'GET';
124 32 100       137 return 'application/x-www-form-urlencoded' if $self->get_content_in eq 'query';
125 30         124 return $ct;
126             }
127              
128             sub get_namespace {
129 33     33 0 105 my ($self, %args) = @_;
130 33 50       109 return @{$args{current_namespace}} if exists($args{current_namespace});
  0         0  
131 33         161 return grep { defined $_ } $self->namespace;
  33         166  
132             }
133              
134             sub get_content_body_parser_class {
135 33     33 0 143 my ($self, $content_type) = @_;
136 33         138 return my $parser_class = CatalystX::RequestModel::content_body_parser_for($content_type);
137             }
138              
139             sub get_attribute_value_for {
140 96     96 0 196 my ($self, $attr) = @_;
141 96         3133 return $self->$attr;
142             }
143              
144             sub nested_params {
145 30     30 1 895 my $self = shift;
146 30         57 my %return;
147 30         100 foreach my $p ($self->properties) {
148 105         284 my ($attr, $meta) = %$p;
149 105 100       273 if(my $predicate = $meta->{attr_predicate}) {
150 92 100       231 if($meta->{omit_empty}) {
151 88 100       3725 next unless $self->$predicate; # skip empties when omit_empty=>1
152             }
153             }
154              
155 96         260 my $value = $self->get_attribute_value_for($attr);
156 96 100 100     561 if( (ref($value)||'') eq 'ARRAY') {
    100 66        
157 12         28 my @gathered = ();
158 12         26 foreach my $v (@$value) {
159 23 100       84 if(Scalar::Util::blessed($v)) {
160 14         48 my $params = $v->nested_params;
161 14 100       176 push @gathered, $params if keys(%$params);
162             } else {
163 9         23 push @gathered, $v;
164             }
165              
166             }
167 12         41 $return{$attr} = \@gathered;
168             } elsif(Scalar::Util::blessed($value) && $value->can('nested_params')) {
169 2         15 my $params = $value->nested_params;
170 2 50       10 next unless keys(%$params);
171 2         7 $return{$attr} = $params;
172             } else {
173 82         250 $return{$attr} = $value;
174             }
175             }
176 30         175 return \%return;
177             }
178              
179             sub get {
180 0     0 1   my ($self, @fields) = @_;
181 0           my $p = $self->nested_params;
182 0           my @got = @$p{@fields};
183 0           return @got;
184             }
185              
186             1;
187              
188             =head1 NAME
189              
190             CatalystX::RequestModel::DoesRequestModel - Role to provide request model API
191              
192             =head1 SYNOPSIS
193              
194             Generally you will apply this role via L<CatalystX::RequestModel>
195              
196             package Example::Model::AccountRequest;
197              
198             use Moose;
199             use CatalystX::RequestModel;
200              
201             extends 'Catalyst::Model';
202             namespace 'person';
203             content_type 'application/x-www-form-urlencoded';
204              
205             has username => (is=>'ro', property=>{always_array=>1});
206             has first_name => (is=>'ro', property=>1);
207             has last_name => (is=>'ro', property=>1);
208             has notes => (is=>'ro', property=>+{ expand=>'JSON' });
209              
210             See L<CatalystX::RequestModel> for a more general overview.
211              
212             =head1 DESCRIPTION
213              
214             A role that gives a L<Catalyst::Model> the ability to indicate which of its attributes should be
215             consider request model data, as well as additional need meta data so that we can process it
216             properly.
217              
218             Since we need to wrap C<has> you should never apply this role manually but rather instead use
219             L<CatalystX::RequestModel> to apply it for you. If you need to customize this role you will
220             also need to subclass L<CatalystX::RequestModel> and have that new subclass apply you custom
221             role. Please ping me if you really need this since I guess we could change L<CatalystX::RequestModel>
222             to make it easier to supply a custom role, just let me know your use case.
223              
224             =head1 METHODS
225              
226             This class defines the following public API
227              
228             =head2 nested_params
229              
230             Returns all the attributes marked as request properties in the form of a hashref. If any of the
231             properties refer to an array or indexed value, or an object, we automatically follow that to
232             return all the property data below.
233              
234             Attributes that are empty will be left out of the return data structure.
235              
236             Easiest way to get all your data but then again you get a structure that is very tightly tied to
237             your request model.
238              
239             =head2 get
240              
241             Accepts a list of attributes that refer to request properties and returns their values. In the case
242             when the attribute listed has no value, you will instead get an C<undef>.
243              
244             =head1 EXCEPTIONS
245              
246             This class can throw the following exceptions:
247              
248             =head2 Invalid Request Content Body
249              
250             If we can't create an instance of the request model we throw a L<CatalystX::RequestModel::Utils::BadRequest>.
251             This will get interpretated as an HTTP 400 status client error if you are using L<CatalystX::Errors>.
252              
253             =head1 AUTHOR
254              
255             See L<CatalystX::RequestModel>.
256            
257             =head1 COPYRIGHT
258            
259             See L<CatalystX::RequestModel>.
260              
261             =head1 LICENSE
262            
263             See L<CatalystX::RequestModel>.
264            
265             =cut
266