Sitelet https://github.com/majuma/aws-cli/commit/a5c5fbad1b5bec37fb412828f7b0310168e2887b
Skip to content

Commit a5c5fba

Browse files
committed
Add attributes to exception in errorhandler
This lets code programmatically process the error attributes.
1 parent b7a130e commit a5c5fba

2 files changed

Lines changed: 111 additions & 10 deletions

File tree

‎awscli/errorhandler.py‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,27 @@
1717
LOG = logging.getLogger(__name__)
1818

1919

20-
class ClientError(Exception):
20+
class BaseOperationError(Exception):
21+
MSG_TEMPLATE = ("A {error_type} error ({error_code}) occurred "
22+
"when calling the {operation_name} operation: "
23+
"{error_message}")
24+
25+
def __init__(self, error_code, error_message, error_type, operation_name):
26+
msg = self.MSG_TEMPLATE.format(
27+
error_code=error_code, error_message=error_message,
28+
error_type=error_type, operation_name=operation_name)
29+
super(BaseOperationError, self).__init__(msg)
30+
self.error_code = error_code
31+
self.error_message = error_message
32+
self.error_type = error_type
33+
self.operation_name = operation_name
34+
35+
36+
class ClientError(BaseOperationError):
2137
pass
2238

2339

24-
class ServerError(Exception):
40+
class ServerError(BaseOperationError):
2541
pass
2642

2743

@@ -38,17 +54,22 @@ class ErrorHandler(object):
3854

3955
def __call__(self, http_response, parsed, operation, **kwargs):
4056
LOG.debug('HTTP Response Code: %d', http_response.status_code)
57+
msg_template = ("A {error_type} error ({error_code}) occurred "
58+
"when calling the {operation_name} operation: "
59+
"{error_message}")
60+
error_type = None
61+
error_class = None
4162
if http_response.status_code >= 500:
42-
code, message = self._get_error_code_and_message(parsed)
43-
msg = "A server error ({error_code}) occurred: {error_message}"
44-
msg = msg.format(error_code=code, error_message=message)
45-
raise ServerError(msg)
63+
error_type = 'server'
64+
error_class = ServerError
4665
if http_response.status_code >= 400 or http_response.status_code == 301:
66+
error_type = 'client'
67+
error_class = ClientError
68+
if error_class is not None:
4769
code, message = self._get_error_code_and_message(parsed)
48-
msg = "A client error ({error_code}) occurred: {error_message}"
49-
msg = msg.format(error_code=code, error_message=message)
50-
raise ClientError(msg)
51-
return 0
70+
raise error_class(
71+
error_code=code, error_message=message,
72+
error_type=error_type, operation_name=operation.name)
5273

5374
def _get_error_code_and_message(self, response):
5475
code = 'Unknown'

‎tests/unit/test_errorhandler.py‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from tests import unittest
14+
15+
import mock
16+
from awscli import errorhandler
17+
18+
19+
class TestErrorHandler(unittest.TestCase):
20+
21+
def create_http_response(self, **kwargs):
22+
response = mock.Mock()
23+
for key, value in kwargs.items():
24+
setattr(response, key, value)
25+
return response
26+
27+
def test_error_handler_client_side(self):
28+
response = {
29+
'CommonPrefixes': [],
30+
'Contents': [],
31+
'Errors': [{'Code': 'AccessDenied',
32+
'HostId': 'foohost',
33+
'Message': 'Access Denied',
34+
'RequestId': 'requestid'}],
35+
'ResponseMetadata': {}}
36+
handler = errorhandler.ErrorHandler()
37+
http_response = self.create_http_response(status_code=403)
38+
# We're manually using the try/except form because
39+
# we want to catch the exception and assert that it has specific
40+
# attributes on it.
41+
operation = mock.Mock()
42+
operation.name = 'OperationName'
43+
try:
44+
handler(http_response, response, operation)
45+
except errorhandler.ClientError as e:
46+
# First, the operation name should be in the error message.
47+
self.assertIn('OperationName', str(e))
48+
# We should state that this is a ClientError.
49+
self.assertIn('client error', str(e))
50+
# And these values should be available on the exception
51+
# so clients can access this information programmatically.
52+
self.assertEqual(e.error_code, 'AccessDenied')
53+
self.assertEqual(e.error_message, 'Access Denied')
54+
self.assertEqual(e.operation_name, 'OperationName')
55+
except Exception as e:
56+
self.fail("Unexpected error raised: %s" % e)
57+
else:
58+
self.fail("Expected errorhandler.ClientError to be raised "
59+
"but no exception was raised.")
60+
61+
def test_no_exception_raised_on_200(self):
62+
response = {
63+
'CommonPrefixes': [],
64+
'Contents': [],
65+
}
66+
handler = errorhandler.ErrorHandler()
67+
http_response = self.create_http_response(status_code=200)
68+
# We're manually using the try/except form because
69+
# we want to catch the exception and assert that it has specific
70+
# attributes on it.
71+
operation = mock.Mock()
72+
operation.name = 'OperationName'
73+
try:
74+
self.assertIsNone(handler(http_response, response, operation))
75+
except errorhandler.BaseOperationError as e:
76+
self.fail("Unexpected error raised: %s" % e)
77+
78+
79+
if __name__ == '__main__':
80+
unittest.main()

0 commit comments

Comments
 (0)