File Coverage

blib/lib/Net/Duo/Exception.pm
Criterion Covered Total %
statement 60 60 100.0
branch 8 8 100.0
condition 4 4 100.0
subroutine 16 16 100.0
pod 11 11 100.0
total 99 99 100.0


line stmt bran cond sub pod time code
1             # Rich exception object for Net::Duo actions.
2             #
3             # All Net::Duo APIs throw Net::Duo::Exception objects on any failure,
4             # including internal errors, protocol errors, HTTP errors, and failures
5             # returned by the Duo API. This is a rich exception object that carries all
6             # available details about the failure and can be inspected by the caller to
7             # recover additional information. If the caller doesn't care about the
8             # details, it provides a stringification that is suitable for simple error
9             # messages.
10              
11             package Net::Duo::Exception 1.01;
12              
13 11     11   69129 use 5.014;
  11         47  
14 11     11   64 use strict;
  11         25  
  11         277  
15 11     11   61 use warnings;
  11         23  
  11         371  
16              
17 11     11   61 use HTTP::Response;
  11         21  
  11         550  
18              
19             # Enable this object to be treated like a string scalar.
20 11     11   70 use overload '""' => \&to_string, 'cmp' => \&spaceship;
  11         21  
  11         145  
21              
22             ##############################################################################
23             # Constructors
24             ##############################################################################
25              
26             # Construct an exception from a Duo error reply. If the provided object
27             # does not have a stat key with a value of FAIL, this call will be
28             # converted automatically to a call to protocol().
29             #
30             # $class - Class of the exception to create
31             # $object - The decoded JSON object representing the error reply
32             # $content - The undecoded content of the server reply
33             #
34             # Returns: Newly-constructed exception
35             sub api {
36 4     4 1 9 my ($class, $object, $content) = @_;
37              
38             # Ensure that we have a valid stat key.
39 4 100       18 if (!defined($object->{stat})) {
    100          
40 1         3 return $class->protocol('missing stat value in JSON reply', $content);
41             } elsif ($object->{stat} ne 'FAIL') {
42 1         4 my $e = $class->protocol('invalid stat value', $content);
43 1         3 $e->{detail} = $object->{stat};
44 1         3 return $e;
45             }
46              
47             # Set the exception information from the JSON object.
48             my $self = {
49             code => $object->{code} // 50000,
50             message => $object->{message} // 'missing error message',
51             detail => $object->{message_detail},
52 2   100     21 content => $content,
      100        
53             };
54              
55             # Create the object and return it.
56 2         4 bless($self, $class);
57 2         6 return $self;
58             }
59              
60             # Construct an exception from an HTTP::Response object.
61             #
62             # $class - Class of the exception to create
63             # $response - An HTTP::Response object representing the failure
64             #
65             # Returns: Newly-constructed exception
66             sub http {
67 1     1 1 88 my ($class, $response) = @_;
68 1         4 my $self = {
69             code => $response->code() . '00',
70             message => $response->message(),
71             content => $response->decoded_content(),
72             };
73 1         181 bless($self, $class);
74 1         3 return $self;
75             }
76              
77             # Construct an exception for an internal error from a simple message.
78             #
79             # $class - Class of the exception to create
80             # $message - The error message
81             #
82             # Returns: Newly-constructed exception
83             sub internal {
84 2     2 1 10 my ($class, $message) = @_;
85 2         7 my $self = {
86             code => 50000,
87             message => $message,
88             };
89 2         4 bless($self, $class);
90 2         6 return $self;
91             }
92              
93             # Construct an exception that propagates another internal exception.
94             # Convert it to a string when propagating it, and remove the file and line
95             # information if present.
96             #
97             # $class - Class of the exception to create
98             # $exception - Exception to propagate
99             #
100             # Returns: Newly-constructed exception
101             sub propagate {
102 1     1 1 3 my ($class, $exception) = @_;
103 1         6 $exception =~ s{ [ ] at [ ] \S+ [ ] line [ ] \d+[.]? \n+ \z }{}xms;
104 1         3 return $class->internal($exception);
105             }
106              
107             # Construct an exception for a protocol failure, where we got an HTTP
108             # success code but couldn't parse the result or couldn't find the JSON
109             # keys that we were expecting.
110             #
111             # $class - Class of the exception to create
112             # $message - Error message indicating what's wrong
113             # $reply - The content of the HTTP reply
114             #
115             # Returns: Newly-created exception
116             sub protocol {
117 3     3 1 7 my ($class, $message, $reply) = @_;
118 3         9 my $self = {
119             code => 50000,
120             message => $message,
121             content => $reply,
122             };
123 3         7 bless($self, $class);
124 3         8 return $self;
125             }
126              
127             ##############################################################################
128             # Accessors and overloads
129             ##############################################################################
130              
131             # Basic accessors.
132 8     8 1 3726 sub code { my $self = shift; return $self->{code} }
  8         60  
133 8     8 1 14 sub content { my $self = shift; return $self->{content} }
  8         32  
134 8     8 1 14 sub detail { my $self = shift; return $self->{detail} }
  8         31  
135 8     8 1 16 sub message { my $self = shift; return $self->{message} }
  8         36  
136              
137             # The cmp implmenetation converts the exception to a string and then compares
138             # it to the other argument.
139             #
140             # $self - Net::Duo::Exception object
141             # $other - The other object (generally a string) to which to compare it
142             # $swap - True if the order needs to be swapped for a proper comparison
143             #
144             # Returns: -1, 0, or 1 per the cmp interface contract
145             sub spaceship {
146 4     4 1 13 my ($self, $other, $swap) = @_;
147 4         10 my $string = $self->to_string;
148 4 100       11 if ($swap) {
149 2         9 return ($other cmp $string);
150             } else {
151 2         9 return ($string cmp $other);
152             }
153             }
154              
155             # A verbose message with all the information from the exception except for
156             # the full content of the reply.
157             #
158             # $self - Net::Duo::Exception
159             #
160             # Returns: A string version of the exception information.
161             sub to_string {
162 13     13 1 22 my ($self) = @_;
163 13         25 my $code = $self->{code};
164 13         19 my $detail = $self->{detail};
165 13         20 my $message = $self->{message};
166              
167             # Our verbose format is the message, followed by the detail in
168             # parentheses if available, and then the error code in brackets.
169 13         18 my $result = $message;
170 13 100       78 if (defined($detail)) {
171 2         7 $result .= " ($detail)";
172             }
173 13         32 $result .= " [$code]";
174 13         45 return $result;
175             }
176              
177             1;
178             __END__