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   3137 use Moo::Role;
  6         17  
  6         47  
4 6     6   23347 use Scalar::Util;
  6         19  
  6         362  
5 6     6   2578 use CatalystX::RequestModel::Utils::BadRequest;
  6         2202  
  6         8977  
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   195 my ($class_or_self, @data) = @_;
14 51 100       170 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
15 51 100       194 if(@data) {
16 18         54 @data = map { split /\./, $_ } @data;
  18         90  
17 18         85 CatalystX::RequestModel::_add_metadata($class, 'namespace', @data);
18             }
19              
20 51 100       427 return $class_or_self->namespace_metadata if $class_or_self->can('namespace_metadata');
21             }
22              
23             sub content_in {
24 51     51   132 my ($class_or_self, $ct) = @_;
25 51 100       176 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
26 51 100       150 CatalystX::RequestModel::_add_metadata($class, 'content_in', $ct) if $ct;
27              
28 51 100       330 if($class_or_self->can('content_in_metadata')) {
29 11         41 my ($ct) = $class_or_self->content_in_metadata; # needed because this returns an array but we only want the first one
30 11 50       63 return $ct if $ct;
31             }
32             }
33              
34             sub get_content_in {
35 45     45 0 890 my $self = shift;
36 45         186 my $ct = $self->content_in;
37 45 100       168 return lc($ct) if $ct;
38 40         189 return 'body';
39             }
40              
41             sub content_type {
42 63     63   274 my ($class_or_self, @ct) = @_;
43 63 100       265 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
44 63 100       321 CatalystX::RequestModel::_add_metadata($class, 'content_type', @ct) if @ct;
45              
46 63 50       392 if($class_or_self->can('content_type_metadata')) {
47 63         224 my (@ct) = $class_or_self->content_type_metadata; # needed because this returns an array but we only want the first onei
48 63         223 return @ct;
49             }
50             }
51              
52             sub property {
53 354     354   948 my ($class_or_self, $attr, $data_proto, $options) = @_;
54 354 50       861 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
55 354 50       863 if(defined $data_proto) {
56 354 50 50     1150 my $data = (ref($data_proto)||'') eq 'HASH' ? $data_proto : +{ name => $attr };
57 354 100       981 $data->{name} = $attr unless exists($data->{name});
58 354         1570 CatalystX::RequestModel::_add_metadata($class, 'property_data', +{$attr => $data});
59             }
60             }
61              
62             sub properties {
63 63     63   135 my ($class_or_self, @data) = @_;
64 63 50       209 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
65 63         180 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       366 return $class_or_self->property_data_metadata if $class_or_self->can('property_data_metadata');
73             }
74              
75             sub COMPONENT {
76 84     84 0 927651 my ($class, $app, $args) = @_;
77 84         790 $args = $class->merge_config_hashes($class->config, $args);
78 84         15983 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 2173 my $self = shift;
84 33         89 my $c = shift;
85              
86 33         240 my %args = (%$self, @_);
87 33         146 my %request_args = $self->parse_content_body($c, %args);
88 32         197 my %init_args = (%args, %request_args, ctx=>$c);
89 32         88 my $class = ref($self);
90              
91 32         142 return my $request_model = $self->build_request_model($c, $class, %init_args);
92             }
93              
94             sub build_request_model {
95 32     32 0 137 my ($self, $c, $class, %init_args) = @_;
96             my $request_model = eval {
97             $class->new(%init_args)
98 32   66     77 } || do {
99             CatalystX::RequestModel::Utils::BadRequest->throw(class=>$class, error_trace=>$@);
100             };
101              
102 31         10088 return $request_model;
103             }
104              
105             sub parse_content_body {
106 33     33 0 108 my ($self, $c, %args) = @_;
107              
108 33         159 my @rules = $self->properties;
109 33         151 my @ns = $self->get_namespace(%args);
110 33         121 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       249 $parser_class->new(ctx=>$c, request_model=>$self );
114              
115 32 100       114 $parser->{context} = $args{context} if exists $args{context}; ## TODO ulgy
116              
117 32         195 return my %request_args = $parser->parse(\@ns, \@rules);
118             }
119              
120             sub get_content_type {
121 33     33 0 99 my ($self, $c) = @_;
122 33         115 my $ct = $c->req->content_type;
123 33 100 66     3362 return 'application/x-www-form-urlencoded' if !$ct && $c->req->method eq 'GET';
124 32 100       135 return 'application/x-www-form-urlencoded' if $self->get_content_in eq 'query';
125 30         120 return $ct;
126             }
127              
128             sub get_namespace {
129 33     33 0 96 my ($self, %args) = @_;
130 33 50       99 return @{$args{current_namespace}} if exists($args{current_namespace});
  0         0  
131 33         141 return grep { defined $_ } $self->namespace;
  33         150  
132             }
133              
134             sub get_content_body_parser_class {
135 33     33 0 146 my ($self, $content_type) = @_;
136 33         128 return my $parser_class = CatalystX::RequestModel::content_body_parser_for($content_type);
137             }
138              
139             sub get_attribute_value_for {
140 96     96 0 220 my ($self, $attr) = @_;
141 96         3217 return $self->$attr;
142             }
143              
144             sub nested_params {
145 30     30 1 890 my $self = shift;
146 30         55 my %return;
147 30         106 foreach my $p ($self->properties) {
148 105         286 my ($attr, $meta) = %$p;
149 105 100       265 if(my $predicate = $meta->{attr_predicate}) {
150 92 100       198 if($meta->{omit_empty}) {
151 88 100       3643 next unless $self->$predicate; # skip empties when omit_empty=>1
152             }
153             }
154              
155 96         257 my $value = $self->get_attribute_value_for($attr);
156 96 100 100     526 if( (ref($value)||'') eq 'ARRAY') {
    100 66        
157 12         26 my @gathered = ();
158 12         26 foreach my $v (@$value) {
159 23 100       69 if(Scalar::Util::blessed($v)) {
160 14         38 my $params = $v->nested_params;
161 14 100       61 push @gathered, $params if keys(%$params);
162             } else {
163 9         22 push @gathered, $v;
164             }
165              
166             }
167 12         37 $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         8 $return{$attr} = $params;
172             } else {
173 82         245 $return{$attr} = $value;
174             }
175             }
176 30         158 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