File Coverage

blib/lib/App/CELL/Status.pm
Criterion Covered Total %
statement 119 133 89.4
branch 50 66 75.7
condition 11 18 61.1
subroutine 25 26 96.1
pod 12 12 100.0
total 217 255 85.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2020, 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             package App::CELL::Status;
34              
35 17     17   1616 use strict;
  17         31  
  17         432  
36 17     17   77 use warnings;
  17         30  
  17         490  
37 17     17   478 use 5.012;
  17         58  
38              
39 17     17   131 use App::CELL::Log qw( $log );
  17         33  
  17         1314  
40 17     17   6156 use App::CELL::Util qw( stringify_args );
  17         38  
  17         819  
41 17     17   101 use Data::Dumper;
  17         27  
  17         605  
42 17     17   86 use Params::Validate qw( :all );
  17         27  
  17         1995  
43 17     17   9113 use Storable qw( dclone );
  17         43790  
  17         977  
44 17     17   112 use Scalar::Util qw( blessed );
  17         30  
  17         1017  
45 17     17   97 use Try::Tiny;
  17         29  
  17         787  
46              
47              
48              
49             =head1 NAME
50              
51             App::CELL::Status - class for return value objects
52              
53              
54              
55             =head1 SYNOPSIS
56              
57             use App::CELL::Status;
58              
59             # simplest usage
60             my $status = App::CELL::Status->ok;
61             print "ok" if ( $status->ok );
62             $status = App::CELL::Status->not_ok;
63             print "NOT ok" if ( $status->not_ok );
64              
65             # as a return value: in the caller
66             my $status = $XYZ( ... );
67             return $status if not $status->ok; # handle failure
68             my $payload = $status->payload; # handle success
69              
70              
71              
72             =head1 INHERITANCE
73              
74             This module inherits from C
75              
76             =cut
77              
78 17     17   96 use parent qw( App::CELL::Message );
  17         26  
  17         113  
79              
80              
81              
82             =head1 DESCRIPTION
83              
84             An App::CELL::Status object is a reference to a hash containing some or
85             all of the following keys (attributes):
86              
87             =over
88              
89             =item C - the status level (see L, below)
90              
91             =item C - message explaining the status
92              
93             =item C - an array reference containing the three-item list
94             generated by the C function
95              
96             =back
97              
98             The typical use cases for this object are:
99              
100             =over
101              
102             =item As a return value from a function call
103              
104             =item To trigger a higher-severity log message
105              
106             =back
107              
108             All calls to C<< App::CELL::Status->new >> with a status other than OK
109             trigger a log message.
110              
111              
112              
113             =head1 PUBLIC METHODS
114              
115             This module provides the following public methods:
116              
117              
118              
119             =head2 new
120            
121             Construct a status object and trigger a log message if the level is anything
122             other than "OK". Always returns a status object. If no level is specified, the
123             level will be 'ERR'. If no code is given, the code will be undefined (I think).
124              
125             =cut
126              
127             sub new {
128 206     206 1 2257 my ( $class, @ARGS ) = @_;
129 206         567 my %ARGS = @ARGS;
130 206         267 my $self;
131              
132             # default to ERR level
133 206 100 66     755 unless ( defined $ARGS{level} and grep { $ARGS{level} eq $_ } $log->permitted_levels ) {
  3090         4514  
134 1         2 $ARGS{level} = 'ERR';
135             }
136              
137             # if caller array not given, create it
138 206 100       505 if ( not $ARGS{caller} ) {
139 124         373 $ARGS{caller} = [ caller ];
140             }
141              
142 206 100       521 $ARGS{args} = [] if not defined( $ARGS{args} );
143 206         378 $ARGS{called_from_status} = 1;
144              
145 206 100       380 if ( $ARGS{code} ) {
146             # App::CELL::Message->new returns a status object
147 69         329 my $status = $class->SUPER::new( %ARGS );
148 69 50       224 if ( $status->ok ) {
149 69         181 my $parent = $status->payload;
150 69         117 $ARGS{msgobj} = $parent;
151 69         159 $ARGS{code} = $parent->code;
152 69         173 $ARGS{text} = $parent->text;
153             } else {
154 0         0 $ARGS{code} = $status->code;
155 0 0       0 if ( $ARGS{args} ) {
156 0         0 $ARGS{text} = $status->text . stringify_args( $ARGS{args} );
157             } else {
158 0         0 $ARGS{text} = $status->text;
159             }
160             }
161             }
162              
163             # bless into objecthood
164 206         368 $self = bless \%ARGS, 'App::CELL::Status';
165              
166             # Log the message
167 206 100 100     756 $log->status_obj( $self, cell => ( $ARGS{cell} || 0 ) ) if ( $ARGS{level} ne 'OK' and $ARGS{level} ne 'NOT_OK' );
      100        
168              
169             # return the created object
170 206         951 return $self;
171             }
172              
173              
174             =head2 dump
175              
176             Dump an existing status object. Takes: PARAMHASH. Parameter 'to' determines
177             destination, which can be 'string' (default), 'log' or 'fd'.
178              
179             # dump object to string
180             my $dump_str = $status->dump();
181             $dump_str = $status->dump( to => 'string' );
182              
183             # dump object to log
184             $status->dump( to => 'log' );
185              
186             # dump object to file descriptor
187             $status->dump( fd => STDOUT );
188             $status->dump( to => 'fd', fd => \*STDOUT );
189              
190             Always returns a true value.
191              
192             =cut
193              
194             sub dump {
195 6     6 1 993 my $self = shift;
196 6         58 my ( %ARGS ) = validate( @_, { 'to' => 0, 'fd' => 0 } );
197 6         17 my ( $action, $fh );
198 6 100       18 if ( not %ARGS ) {
    100          
    50          
199 2         3 $action = 'string';
200             } elsif ( exists $ARGS{'to'} ) {
201 2 50 0     7 if ( $ARGS{'to'} eq 'string' ) {
    50          
    0          
202 0         0 $action = 'string';
203             } elsif ( $ARGS{'to'} eq 'log' ) {
204 2         3 $action = 'log';
205             } elsif ( $ARGS{'to'} eq 'fd' and exists $ARGS{'fd'} ) {
206 0         0 $action = 'fd';
207 0         0 $fh = $ARGS{'fd'};
208             } else {
209 0         0 die "App::CELL->Status->dump() doing nothing (bad arguments)";
210             }
211             } elsif ( exists $ARGS{'fd'} ) {
212 2         3 $action = 'fd';
213 2         3 $fh = $ARGS{'fd'};
214             } else {
215 0         0 die "App::CELL->Status->dump() doing nothing (bad arguments)";
216             }
217 6 100       16 if ( $action eq "string" ) {
    100          
    50          
218 2         5 return _prep_dump_string(
219             level => $self->level,
220             code => $self->code,
221             text => $self->text,
222             );
223             } elsif ( $action eq "log" ) {
224 2         4 $log->status_obj( $self );
225             } elsif ( $action eq "fd" ) {
226 2         4 print $fh _prep_dump_string(
227             level => $self->level,
228             code => $self->code,
229             text => $self->text,
230             ), "\n";
231             } else {
232 0         0 die "App::CELL->Status->dump() doing nothing (bad things happening)";
233             }
234              
235 4         13 return 1;
236             }
237              
238              
239             sub _prep_dump_string {
240 4     4   40 my %ARGS = validate( @_, {
241             'level' => 1,
242             'code' => 0,
243             'text' => 1,
244             } );
245              
246 4         17 my $prepped_string = "$ARGS{'level'}: ";
247 4 50 33     14 if ( $ARGS{'code'} and $ARGS{'code'} ne $ARGS{'text'} ) {
248 4         15 $prepped_string .= "($ARGS{'code'}) ";
249             }
250 4         8 $prepped_string .= "$ARGS{'text'}";
251              
252 4         32 return $prepped_string;
253             }
254              
255              
256             =head2 ok
257              
258             If the first argument is blessed, assume we're being called as an
259             instance method: return true if status is OK, false otherwise.
260              
261             Otherwise, assume we're being called as a class method: return a
262             new OK status object with optional payload (optional parameter to the
263             method call, must be a scalar).
264              
265             =cut
266              
267             sub ok {
268              
269 152     152 1 1220 my ( $self, $payload ) = @_;
270 152         223 my $ARGS = {};
271              
272 152 100       467 if ( blessed $self )
273             { # instance method
274 116 100       243 return 1 if ( $self->level eq 'OK' );
275 23         120 return 0;
276              
277             }
278 36         82 $ARGS->{level} = 'OK';
279 36 100       93 $ARGS->{payload} = $payload if $payload;
280 36         161 $ARGS->{caller} = [ caller ];
281 36         159 return bless $ARGS, __PACKAGE__;
282             }
283              
284              
285             =head2 not_ok
286              
287             Similar method to 'ok', except it handles 'NOT_OK' status.
288              
289             When called as an instance method, returns a true value if the status level
290             is anything other than 'OK'. Otherwise false.
291              
292             When called as a class method, returns a 'NOT_OK' status object.
293             Optionally, a payload can be supplied as an argument.
294              
295             =cut
296              
297             sub not_ok {
298              
299 40     40 1 961 my ( $self, $payload ) = @_;
300 40         63 my $ARGS = {};
301              
302 40 100       139 if ( blessed $self )
303             { # instance method
304 38 100       156 return 1 if $self->{level} ne 'OK';
305 24         108 return 0;
306             }
307 2         5 $ARGS->{level} = 'NOT_OK';
308 2 100       6 $ARGS->{payload} = $payload if $payload;
309 2         7 $ARGS->{caller} = [ caller ];
310 2         6 return bless $ARGS, __PACKAGE__;
311             }
312              
313              
314             =head2 level
315              
316             Accessor method, returns level of status object in ALL-CAPS. All status
317             objects must have a level attribute.
318              
319             =cut
320              
321             sub level {
322 219     219 1 359 my $self = shift;
323 219 100       385 $self->{'level'} = $_[0] if @_;
324 219         811 return $self->{'level'};
325             }
326              
327              
328             =head2 code
329              
330             Accesor method, returns code of status object, or "C<< >>" if none
331             present.
332              
333             =cut
334              
335             sub code {
336 83     83 1 128 my $self = shift;
337 83 100       176 $self->{'code'} = $_[0] if @_;
338 83   100     262 return $self->{'code'} || "";
339             }
340            
341              
342             =head2 args
343              
344             Accessor method - returns value of the 'args' property.
345              
346             =cut
347              
348             sub args {
349 5     5 1 14 my $self = shift;
350 5 100       18 $self->{'args'} = $_[0] if @_;
351 5         24 return $self->{'args'};
352             }
353              
354              
355             =head2 text
356              
357             Accessor method, returns text of status object, or the code if no text
358             present. If neither code nor text are present, returns "C<< >>"
359              
360             =cut
361              
362             sub text {
363 68 100   68 1 230 return $_[0]->{text} if $_[0]->{text};
364 6         12 return $_[0]->code;
365             }
366              
367              
368             =head2 caller
369              
370             Accessor method. Returns array reference containing output of C
371             function associated with this status object, or C<[]> if not present.
372              
373             =cut
374              
375 63   50 63 1 175 sub caller { return $_[0]->{caller} || []; }
376              
377              
378             =head2 payload
379              
380             When called with no arguments, acts like an accessor method.
381             When called with a scalar argument, either adds that as the payload or
382             changes the payload to that.
383              
384             Logs a warning if an existing payload is changed.
385              
386             Returns the (new) payload or undef.
387              
388             =cut
389              
390             sub payload {
391 139     139 1 250 my ( $self, $new_payload ) = @_;
392 139 50       261 if ( defined $new_payload ) {
393             $log->warn( "Changing payload of status object. Old payload was " .
394 0 0       0 "->$self->{payload}<-", cell => 1 ) if $self->{payload};
395 0         0 $self->{payload} = $new_payload;
396             }
397 139         431 return $self->{payload};
398             }
399              
400              
401             =head2 msgobj
402              
403             Accessor method (returns the parent message object)
404              
405             =cut
406              
407             sub msgobj {
408 2     2 1 7 my $self = $_[0];
409              
410 2 50       12 return $self->{msgobj} if exists $self->{msgobj};
411 0         0 return; # returns undef in scalar context
412             }
413              
414              
415             =head2 expurgate
416              
417             Make a deep copy of the status object, unbless it, and remove certain
418             attributes deemed "extraneous".
419              
420             =cut
421              
422             sub expurgate {
423 1     1 1 250 my ( $self ) = @_;
424 1 50       4 return unless blessed( $self );
425              
426 1         3 my ( $clone, $status );
427             try {
428 1     1   138 $clone = dclone( $self );
429             } catch {
430 0     0   0 $status = __PACKAGE__->new(
431             level => 'CRIT',
432             code => $_,
433             );
434 1         8 };
435 1 50       17 return $status->expurgate if $status;
436 1         1 my $udc;
437            
438 1         5 foreach my $key ( keys %$clone ) {
439 7 100       9 next if grep { $key eq $_ } ( 'args', 'called_from_status', 'caller', 'msgobj' );
  28         41  
440 3         5 $udc->{$key} = $clone->{$key};
441             }
442            
443 1         5 return $udc;
444             }
445              
446              
447             1;