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   3304 use Moo::Role;
  6         22  
  6         66  
4 6     6   23701 use Scalar::Util;
  6         16  
  6         325  
5 6     6   2753 use CatalystX::RequestModel::Utils::BadRequest;
  6         2211  
  6         9315  
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   187 my ($class_or_self, @data) = @_;
14 51 100       189 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
15 51 100       194 if(@data) {
16 18         51 @data = map { split /\./, $_ } @data;
  18         93  
17 18         88 CatalystX::RequestModel::_add_metadata($class, 'namespace', @data);
18             }
19              
20 51 100       450 return $class_or_self->namespace_metadata if $class_or_self->can('namespace_metadata');
21             }
22              
23             sub content_in {
24 51     51   146 my ($class_or_self, $ct) = @_;
25 51 100       146 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
26 51 100       137 CatalystX::RequestModel::_add_metadata($class, 'content_in', $ct) if $ct;
27              
28 51 100       334 if($class_or_self->can('content_in_metadata')) {
29 11         46 my ($ct) = $class_or_self->content_in_metadata; # needed because this returns an array but we only want the first one
30 11 50       69 return $ct if $ct;
31             }
32             }
33              
34             sub get_content_in {
35 45     45 0 920 my $self = shift;
36 45         197 my $ct = $self->content_in;
37 45 100       160 return lc($ct) if $ct;
38 40         196 return 'body';
39             }
40              
41             sub content_type {
42 63     63   279 my ($class_or_self, $ct) = @_;
43 63 100       214 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
44 63 100       298 CatalystX::RequestModel::_add_metadata($class, 'content_type', $ct) if $ct;
45              
46 63 50       408 if($class_or_self->can('content_type_metadata')) {
47 63         233 my ($ct) = $class_or_self->content_type_metadata; # needed because this returns an array but we only want the first one
48 63         287 return $ct;
49             }
50             }
51              
52             sub property {
53 354     354   924 my ($class_or_self, $attr, $data_proto, $options) = @_;
54 354 50       848 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
55 354 50       857 if(defined $data_proto) {
56 354 50 50     1182 my $data = (ref($data_proto)||'') eq 'HASH' ? $data_proto : +{ name => $attr };
57 354 100       965 $data->{name} = $attr unless exists($data->{name});
58 354         1621 CatalystX::RequestModel::_add_metadata($class, 'property_data', +{$attr => $data});
59             }
60             }
61              
62             sub properties {
63 63     63   140 my ($class_or_self, @data) = @_;
64 63 50       163 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
65 63         212 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       416 return $class_or_self->property_data_metadata if $class_or_self->can('property_data_metadata');
73             }
74              
75             sub COMPONENT {
76 84     84 0 918486 my ($class, $app, $args) = @_;
77 84         811 $args = $class->merge_config_hashes($class->config, $args);
78 84         15967 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 2253 my $self = shift;
84 33         68 my $c = shift;
85              
86 33         237 my %args = (%$self, @_);
87 33         171 my %request_args = $self->parse_content_body($c, %args);
88 32         194 my %init_args = (%args, %request_args, ctx=>$c);
89 32         90 my $class = ref($self);
90              
91 32         173 return my $request_model = $self->build_request_model($c, $class, %init_args);
92             }
93              
94             sub build_request_model {
95 32     32 0 128 my ($self, $c, $class, %init_args) = @_;
96             my $request_model = eval {
97             $class->new(%init_args)
98 32   66     67 } || do {
99             CatalystX::RequestModel::Utils::BadRequest->throw(class=>$class, error_trace=>$@);
100             };
101              
102 31         10698 return $request_model;
103             }
104              
105             sub parse_content_body {
106 33     33 0 133 my ($self, $c, %args) = @_;
107              
108 33         166 my @rules = $self->properties;
109 33         139 my @ns = $self->get_namespace(%args);
110 33         149 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       291 $parser_class->new(ctx=>$c, request_model=>$self );
114              
115 32 100       108 $parser->{context} = $args{context} if exists $args{context}; ## TODO ulgy
116              
117 32         199 return my %request_args = $parser->parse(\@ns, \@rules);
118             }
119              
120             sub get_content_type {
121 33     33 0 82 my ($self, $c) = @_;
122 33         114 my $ct = $c->req->content_type;
123 33 100 66     3251 return 'application/x-www-form-urlencoded' if !$ct && $c->req->method eq 'GET';
124 32 100       131 return 'application/x-www-form-urlencoded' if $self->get_content_in eq 'query';
125 30         118 return $ct;
126             }
127              
128             sub get_namespace {
129 33     33 0 107 my ($self, %args) = @_;
130 33 50       114 return @{$args{current_namespace}} if exists($args{current_namespace});
  0         0  
131 33         178 return grep { defined $_ } $self->namespace;
  33         153  
132             }
133              
134             sub get_content_body_parser_class {
135 33     33 0 153 my ($self, $content_type) = @_;
136 33         144 return my $parser_class = CatalystX::RequestModel::content_body_parser_for($content_type);
137             }
138              
139             sub get_attribute_value_for {
140 96     96 0 202 my ($self, $attr) = @_;
141 96         3264 return $self->$attr;
142             }
143              
144             sub nested_params {
145 30     30 1 1022 my $self = shift;
146 30         70 my %return;
147 30         109 foreach my $p ($self->properties) {
148 105         290 my ($attr, $meta) = %$p;
149 105 100       295 if(my $predicate = $meta->{attr_predicate}) {
150 92 100       213 if($meta->{omit_empty}) {
151 88 100       3785 next unless $self->$predicate; # skip empties when omit_empty=>1
152             }
153             }
154              
155 96         258 my $value = $self->get_attribute_value_for($attr);
156 96 100 100     532 if( (ref($value)||'') eq 'ARRAY') {
    100 66        
157 12         27 my @gathered = ();
158 12         30 foreach my $v (@$value) {
159 23 100       69 if(Scalar::Util::blessed($v)) {
160 14         37 my $params = $v->nested_params;
161 14 100       55 push @gathered, $params if keys(%$params);
162             } else {
163 9         23 push @gathered, $v;
164             }
165              
166             }
167 12         50 $return{$attr} = \@gathered;
168             } elsif(Scalar::Util::blessed($value) && $value->can('nested_params')) {
169 2         15 my $params = $value->nested_params;
170 2 50       11 next unless keys(%$params);
171 2         7 $return{$attr} = $params;
172             } else {
173 82         247 $return{$attr} = $value;
174             }
175             }
176 30         183 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