File Coverage

blib/lib/Web/MREST/Entity.pm
Criterion Covered Total %
statement 93 103 90.2
branch 14 22 63.6
condition 3 6 50.0
subroutine 19 20 95.0
pod 8 8 100.0
total 137 159 86.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2022, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             # ------------------------
34             # This package contains methods for dealing with request and response
35             # entities (parts two and four of the FSM as described in the L<Web::MREST>
36             # documentation
37             # ------------------------
38              
39              
40             use strict;
41 21     21   8824 use warnings;
  21         51  
  21         531  
42 21     21   156  
  21         44  
  21         602  
43             use App::CELL qw( $CELL $log $meta $site );
44 21     21   93 use Data::Dumper;
  21         40  
  21         1953  
45 21     21   120 use Try::Tiny;
  21         38  
  21         797  
46 21     21   123 use Web::Machine::FSM::States;
  21         48  
  21         986  
47 21     21   121 use Web::MREST::Util qw( $JSON );
  21         37  
  21         262  
48 21     21   5292  
  21         45  
  21         1611  
49             use parent 'Web::MREST::Resource';
50 21     21   125  
  21         41  
  21         148  
51              
52              
53              
54             =head1 NAME
55              
56             Web::MREST::Entity - Methods for dealing with request, response entities
57              
58              
59              
60              
61             =head1 SYNOPSIS
62              
63             Methods for dealing with request, response entities
64              
65              
66              
67              
68             =head1 METHODS
69              
70              
71             =head2 get_acceptable_content_type_handler
72              
73             The method to use to process the request entity (i.e, the "acceptable content
74             type handler") is set in content_types_accepted. Web::Machine only calls the
75             method on PUT requests and those POST requests for which post_is_create is
76             true. On POST requests where post_is_create is false, we have to call it
77             ourselves, and for that we need a way to get to it.
78              
79             =cut
80              
81             my $self = shift;
82             Web::Machine::FSM::States::_get_acceptable_content_type_handler( $self, $self->request );
83 9     9 1 28 }
84 9         33  
85              
86             =head2 content_types_provided
87              
88             L<Web::Machine> calls this routine to determine how to generate the response
89             body GET requests. (It is not called for PUT, POST, or DELETE requests.)
90              
91             The return value has the following format:
92              
93             [
94             { 'text/html' => 'method_for_html' },
95             { 'application/json' => 'method_for_json' },
96             { 'other/mime' => 'method_for_other_mime' },
97             ]
98              
99             As you can see, this is a list of tuples. The key is a media type and the
100             value is the name of a method. The first tuple is taken as the default.
101              
102             =cut
103            
104             my $self = shift;
105             my @caller = caller;
106             $log->debug( "Entering " . __PACKAGE__ . "::content_types_provided, caller is " . Dumper( \@caller ) );
107 100     100 1 51873  
108 100         297 return [
109 100         322 { 'text/html' => 'mrest_generate_response_html' },
110             { 'application/json' => 'mrest_generate_response_json' },
111             ];
112 100         22273 }
113              
114              
115             =head2 mrest_generate_response_html
116              
117             Normally, clients will communicate with the server via
118             '_render_response_json', but humans need HTML. This method takes the
119             server's JSON response and wraps it up in a nice package.
120             The return value from this method becomes the response entity.
121              
122             =cut
123              
124             my ( $self ) = @_;
125             $log->debug( "Entering " . __PACKAGE__ . "::_render_response_html (response generator)" );
126            
127             my $json = $self->mrest_generate_response_json;
128 1     1 1 241 return $json if ref( $json ) eq 'SCALAR';
129 1         8  
130             my $msgobj = $CELL->msg(
131 1         179 'MREST_RESPONSE_HTML',
132 1 50       6 $site->MREST_APPLICATION_MODULE,
133             $json,
134 1         25 );
135             my $entity = $msgobj
136             ? $msgobj->text
137             : '<html><body><h1>Internal Error</h1><p>See Resource.pm->_render_response_html</p></body></html>';
138              
139 1 50       285 $self->response->header('Content-Type' => 'text/html' );
140             $self->response->content( $entity );
141              
142             return $entity;
143 1         7 }
144 1         37  
145              
146 1         11 =head2 content_types_accepted
147              
148             L<Web::Machine> calls this routine to determine how to handle the request
149             body (e.g. in PUT requests).
150              
151             =cut
152            
153             my $self = shift;
154             my @caller = caller;
155             $log->debug("Entering " . __PACKAGE__ . "::content_types_accepted, caller is " . Dumper( \@caller ) );
156              
157             return [ { 'application/json' => 'mrest_process_request_json' }, ]
158 13     13 1 1328 }
159 13         52  
160 13         51  
161             =head2 mrest_process_request_json
162 13         2877  
163             PUT and POST requests may contain a request body. This is the "handler
164             function" where we process those requests.
165              
166             We associate this function with 'application/json' via
167             C<content_types_accepted>.
168              
169             =cut
170              
171             my $self = shift;
172             my @caller = caller;
173             $log->debug("Entering " . __PACKAGE__ . "::mrest_process_request_json, caller is " . Dumper( \@caller ) );
174              
175             # convert body to JSON
176             my ( $from_json, $status );
177 13     13 1 561 try {
178 13         49 my $content = $self->request->content;
179 13         50 if ( ! defined $content or $content eq '' ) {
180             $log->debug( "There is no request body, assuming JSON null" );
181             $content = 'null';
182 13         2667 }
183             $log->debug( "Attempting to decode JSON request entity $content" );
184 13     13   550 $from_json = $JSON->decode( $content );
185 13 100 66     440 $log->debug( "Success" );
186 4         24 } catch {
187 4         593 $status = \400;
188             $log->error( "Caught JSON decode error; response code should be " . $$status );
189 13         89 $self->mrest_declare_status( 'code' => $$status, explanation => $_ );
190 13         2147 };
191 12         71 return $status if ref( $status ) eq 'SCALAR';
192             $self->push_onto_context( { 'request_entity' => $from_json } );
193 1     1   14  
194 1         6 return $self->mrest_generate_response;
195 1         161 }
196 13         149  
197 13 100       2137  
198 12         68 =head2 mrest_process_request
199              
200 12         71 Used to call the request handler manually in cases when L<Web::Machine> does
201             not call it for us.
202              
203             =cut
204              
205             my $self = shift;
206             $log->debug( "Entering " . __PACKAGE__ . "::mrest_process_request" );
207             my $handler = $self->get_acceptable_content_type_handler;
208             if ( ref( $handler ) eq 'SCALAR' ) {
209             $self->mrest_declare_status( code => $$handler,
210             explanation => 'Could not get acceptable content type handler' );
211             return $CELL->status_not_ok;
212 9     9 1 23 }
213 9         54 $log->debug( "acceptable request handler is: " . Dumper( $handler ) );
214 9         1422 return $self->$handler;
215 9 50       1343 }
216 0         0  
217              
218 0         0 =head2 mrest_generate_response_json
219              
220 9         37 First, run pass 2 of the resource handler, which is expected to return an
221 9         1793 App::CELL::Status object. Second, push that object onto the context. Third,
222             convert that object into JSON and push the JSON onto the context, too. Return
223             the JSON representation of the App::CELL::Status object - this becomes the
224             HTTP response entity.
225              
226             =cut
227              
228             my ( $self ) = @_;
229             my ( $d, %h, $before, $after, $after_utf8 );
230             my @caller = caller;
231             $log->debug( "Entering " . __PACKAGE__ . "::mrest_generate_response_json, caller is " .
232             Dumper( \@caller ) );
233              
234             # run the handler
235             my $handler = $self->context->{'handler'}; # WWWW
236 34     34 1 6147 $log->debug( "mrest_generate_response_json: Calling resource handler $handler for pass two" );
237 34         66 my ( $status, $response_obj, $entity );
238 34         120 try {
239 34         114 $status = $self->$handler(2);
240             if ( ( my $reftype = ref( $status ) ) ne 'App::CELL::Status' ) {
241             die "AAAAHAGGHG! Handler $handler, pass two, returned a ->$reftype<-, " .
242             "which is not an App::CELL::Status object!";
243 34         7090 }
244 34         227 if ( $status->not_ok and ! $self->status_declared ) {
245 34         5037 $status->{'http_code'} = 500;
246             $self->mrest_declare_status( $status );
247 34     34   1415 }
248 34 50       10788 $response_obj = $status->expurgate;
249 0         0 $entity = $JSON->encode( $response_obj );
250             } catch {
251             if ( ! $self->status_declared ) {
252 34 50 33     107 $self->mrest_declare_status( code => 500, explanation => $_ );
253 0         0 }
254 0         0 my $code = $self->mrest_declared_status_code;
255             $code += 0;
256 34         402 $status = \$code;
257 34         7156 };
258             $log->debug( "response generator returned " . Dumper( $status ) );
259 0 0   0   0 return $status if ref( $status ) eq 'SCALAR';
260 0         0  
261             # for PUT requests, we need a Location header if a new resource was created
262 0         0 if ( $self->context->{'method'} eq 'PUT' ) {
263 0         0 my $headers = $self->response->headers;
264 0         0 my $uri_path = $self->context->{'uri_path'};
265 34         327 $headers->header( 'Location' => $uri_path ) unless $self->context->{'resource_exists'};
266 34         556 }
267 34 50       9327  
268             # stage the status object to become the response entity
269             $self->push_onto_context( {
270 34 100       128 'handler_status' => $status,
271 3         9 'response_object' => $response_obj,
272 3         29 'response_entity' => $entity,
273 3 100       6 } );
274              
275             # put the entity into the response
276             $self->response->header('Content-Type' => 'application/json' );
277             $self->response->content( $entity );
278 34         290  
279             $log->debug( "Response will be: " . $self->response->content );
280              
281             return $entity;
282             }
283              
284 34         133  
285 34         1395 =head2 mrest_generate_response
286              
287 34         453 This should somehow get the response handler and run it.
288              
289 34         5718 =cut
290              
291             my $self = shift;
292             return $self->mrest_generate_response_json;
293             }
294              
295              
296             1;