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   1521 use strict;
  17         26  
  17         436  
36 17     17   68 use warnings;
  17         25  
  17         379  
37 17     17   391 use 5.012;
  17         53  
38              
39 17     17   121 use App::CELL::Log qw( $log );
  17         37  
  17         1260  
40 17     17   5811 use App::CELL::Util qw( stringify_args );
  17         35  
  17         730  
41 17     17   98 use Data::Dumper;
  17         24  
  17         563  
42 17     17   77 use Params::Validate qw( :all );
  17         24  
  17         1815  
43 17     17   8445 use Storable qw( dclone );
  17         41418  
  17         983  
44 17     17   109 use Scalar::Util qw( blessed );
  17         27  
  17         936  
45 17     17   90 use Try::Tiny;
  17         25  
  17         736  
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   81 use parent qw( App::CELL::Message );
  17         27  
  17         82  
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 2051 my ( $class, @ARGS ) = @_;
129 206         497 my %ARGS = @ARGS;
130 206         255 my $self;
131              
132             # default to ERR level
133 206 100 66     732 unless ( defined $ARGS{level} and grep { $ARGS{level} eq $_ } $log->permitted_levels ) {
  3090         4216  
134 1         3 $ARGS{level} = 'ERR';
135             }
136              
137             # if caller array not given, create it
138 206 100       504 if ( not $ARGS{caller} ) {
139 124         382 $ARGS{caller} = [ CORE::caller() ];
140             }
141              
142 206 100       467 $ARGS{args} = [] if not defined( $ARGS{args} );
143 206         310 $ARGS{called_from_status} = 1;
144              
145 206 100       376 if ( $ARGS{code} ) {
146             # App::CELL::Message->new returns a status object
147 69         323 my $status = $class->SUPER::new( %ARGS );
148 69 50       202 if ( $status->ok ) {
149 69         138 my $parent = $status->payload;
150 69         132 $ARGS{msgobj} = $parent;
151 69         180 $ARGS{code} = $parent->code;
152 69         162 $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         360 $self = bless \%ARGS, 'App::CELL::Status';
165              
166             # Log the message
167 206 100 100     733 $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         883 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 2766 my $self = shift;
196 6         63 my ( %ARGS ) = validate( @_, { 'to' => 0, 'fd' => 0 } );
197 6         21 my ( $action, $fh );
198 6 100       15 if ( not %ARGS ) {
    100          
    50          
199 2         4 $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         5 $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         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         6 return _prep_dump_string(
219             level => $self->level,
220             code => $self->code,
221             text => $self->text,
222             );
223             } elsif ( $action eq "log" ) {
224 2         5 $log->status_obj( $self );
225             } elsif ( $action eq "fd" ) {
226 2         6 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         22 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         28 my $prepped_string = "$ARGS{'level'}: ";
247 4 50 33     18 if ( $ARGS{'code'} and $ARGS{'code'} ne $ARGS{'text'} ) {
248 4         11 $prepped_string .= "($ARGS{'code'}) ";
249             }
250 4         7 $prepped_string .= "$ARGS{'text'}";
251              
252 4         92 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 1128 my ( $self, $payload ) = @_;
270 152         229 my $ARGS = {};
271              
272 152 100       475 if ( blessed $self )
273             { # instance method
274 116 100       281 return 1 if ( $self->level eq 'OK' );
275 23         107 return 0;
276              
277             }
278 36         116 $ARGS->{level} = 'OK';
279 36 100       78 $ARGS->{payload} = $payload if $payload;
280 36         128 $ARGS->{caller} = [ CORE::caller() ];
281 36         167 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 911 my ( $self, $payload ) = @_;
300 40         61 my $ARGS = {};
301              
302 40 100       125 if ( blessed $self )
303             { # instance method
304 38 100       148 return 1 if $self->{level} ne 'OK';
305 24         94 return 0;
306             }
307 2         5 $ARGS->{level} = 'NOT_OK';
308 2 100       7 $ARGS->{payload} = $payload if $payload;
309 2         7 $ARGS->{caller} = [ CORE::caller() ];
310 2         7 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 354 my $self = shift;
323 219 100       367 $self->{'level'} = $_[0] if @_;
324 219         783 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 127 my $self = shift;
337 83 100       151 $self->{'code'} = $_[0] if @_;
338 83   100     257 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       17 $self->{'args'} = $_[0] if @_;
351 5         17 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 199 return $_[0]->{text} if $_[0]->{text};
364 6         10 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 173 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       267 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         382 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 222 my ( $self ) = @_;
424 1 50       5 return unless blessed( $self );
425              
426 1         2 my ( $clone, $status );
427             try {
428 1     1   151 $clone = dclone( $self );
429             } catch {
430 0     0   0 $status = __PACKAGE__->new(
431             level => 'CRIT',
432             code => $_,
433             );
434 1         6 };
435 1 50       16 return $status->expurgate if $status;
436 1         2 my $udc;
437            
438 1         4 foreach my $key ( keys %$clone ) {
439 7 100       10 next if grep { $key eq $_ } ( 'args', 'called_from_status', 'caller', 'msgobj' );
  28         40  
440 3         6 $udc->{$key} = $clone->{$key};
441             }
442            
443 1         5 return $udc;
444             }
445              
446              
447             1;