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   1754 use strict;
  17         30  
  17         497  
36 17     17   83 use warnings;
  17         29  
  17         466  
37 17     17   435 use 5.012;
  17         61  
38              
39 17     17   144 use App::CELL::Log qw( $log );
  17         37  
  17         1373  
40 17     17   6804 use App::CELL::Util qw( stringify_args );
  17         45  
  17         878  
41 17     17   109 use Data::Dumper;
  17         33  
  17         683  
42 17     17   89 use Params::Validate qw( :all );
  17         30  
  17         2120  
43 17     17   9751 use Storable qw( dclone );
  17         46823  
  17         1079  
44 17     17   135 use Scalar::Util qw( blessed );
  17         36  
  17         1132  
45 17     17   102 use Try::Tiny;
  17         34  
  17         848  
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   102 use parent qw( App::CELL::Message );
  17         36  
  17         109  
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 2662 my ( $class, @ARGS ) = @_;
129 206         540 my %ARGS = @ARGS;
130 206         292 my $self;
131              
132             # default to ERR level
133 206 100 66     821 unless ( defined $ARGS{level} and grep { $ARGS{level} eq $_ } $log->permitted_levels ) {
  3090         4639  
134 1         3 $ARGS{level} = 'ERR';
135             }
136              
137             # if caller array not given, create it
138 206 100       549 if ( not $ARGS{caller} ) {
139 124         394 $ARGS{caller} = [ CORE::caller() ];
140             }
141              
142 206 100       512 $ARGS{args} = [] if not defined( $ARGS{args} );
143 206         329 $ARGS{called_from_status} = 1;
144              
145 206 100       400 if ( $ARGS{code} ) {
146             # App::CELL::Message->new returns a status object
147 69         346 my $status = $class->SUPER::new( %ARGS );
148 69 50       187 if ( $status->ok ) {
149 69         150 my $parent = $status->payload;
150 69         117 $ARGS{msgobj} = $parent;
151 69         162 $ARGS{code} = $parent->code;
152 69         169 $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         383 $self = bless \%ARGS, 'App::CELL::Status';
165              
166             # Log the message
167 206 100 100     874 $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         969 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 2466 my $self = shift;
196 6         63 my ( %ARGS ) = validate( @_, { 'to' => 0, 'fd' => 0 } );
197 6         21 my ( $action, $fh );
198 6 100       19 if ( not %ARGS ) {
    100          
    50          
199 2         3 $action = 'string';
200             } elsif ( exists $ARGS{'to'} ) {
201 2 50 0     6 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         4 $action = 'fd';
213 2         4 $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         4 return _prep_dump_string(
219             level => $self->level,
220             code => $self->code,
221             text => $self->text,
222             );
223             } elsif ( $action eq "log" ) {
224 2         6 $log->status_obj( $self );
225             } elsif ( $action eq "fd" ) {
226 2         5 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         18 return 1;
236             }
237              
238              
239             sub _prep_dump_string {
240 4     4   44 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     16 if ( $ARGS{'code'} and $ARGS{'code'} ne $ARGS{'text'} ) {
248 4         17 $prepped_string .= "($ARGS{'code'}) ";
249             }
250 4         7 $prepped_string .= "$ARGS{'text'}";
251              
252 4         88 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 1443 my ( $self, $payload ) = @_;
270 152         242 my $ARGS = {};
271              
272 152 100       467 if ( blessed $self )
273             { # instance method
274 116 100       249 return 1 if ( $self->level eq 'OK' );
275 23         129 return 0;
276              
277             }
278 36         83 $ARGS->{level} = 'OK';
279 36 100       91 $ARGS->{payload} = $payload if $payload;
280 36         158 $ARGS->{caller} = [ CORE::caller() ];
281 36         127 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 1200 my ( $self, $payload ) = @_;
300 40         69 my $ARGS = {};
301              
302 40 100       193 if ( blessed $self )
303             { # instance method
304 38 100       166 return 1 if $self->{level} ne 'OK';
305 24         97 return 0;
306             }
307 2         6 $ARGS->{level} = 'NOT_OK';
308 2 100       5 $ARGS->{payload} = $payload if $payload;
309 2         8 $ARGS->{caller} = [ CORE::caller() ];
310 2         8 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 411 my $self = shift;
323 219 100       399 $self->{'level'} = $_[0] if @_;
324 219         838 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 143 my $self = shift;
337 83 100       186 $self->{'code'} = $_[0] if @_;
338 83   100     300 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 15 my $self = shift;
350 5 100       20 $self->{'args'} = $_[0] if @_;
351 5         25 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 219 return $_[0]->{text} if $_[0]->{text};
364 6         14 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 205 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 254 my ( $self, $new_payload ) = @_;
392 139 50       258 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         434 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 9 my $self = $_[0];
409              
410 2 50       13 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 287 my ( $self ) = @_;
424 1 50       7 return unless blessed( $self );
425              
426 1         2 my ( $clone, $status );
427             try {
428 1     1   178 $clone = dclone( $self );
429             } catch {
430 0     0   0 $status = __PACKAGE__->new(
431             level => 'CRIT',
432             code => $_,
433             );
434 1         8 };
435 1 50       21 return $status->expurgate if $status;
436 1         3 my $udc;
437            
438 1         4 foreach my $key ( keys %$clone ) {
439 7 100       11 next if grep { $key eq $_ } ( 'args', 'called_from_status', 'caller', 'msgobj' );
  28         49  
440 3         8 $udc->{$key} = $clone->{$key};
441             }
442            
443 1         6 return $udc;
444             }
445              
446              
447             1;