1#!/usr/bin/env python
2#
3# Copyright 2015 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Exceptions for generated client libraries."""
18
19
20class Error(Exception):
21
22    """Base class for all exceptions."""
23
24
25class TypecheckError(Error, TypeError):
26
27    """An object of an incorrect type is provided."""
28
29
30class NotFoundError(Error):
31
32    """A specified resource could not be found."""
33
34
35class UserError(Error):
36
37    """Base class for errors related to user input."""
38
39
40class InvalidDataError(Error):
41
42    """Base class for any invalid data error."""
43
44
45class CommunicationError(Error):
46
47    """Any communication error talking to an API server."""
48
49
50class HttpError(CommunicationError):
51
52    """Error making a request. Soon to be HttpError."""
53
54    def __init__(self, response, content, url,
55                 method_config=None, request=None):
56        error_message = HttpError._build_message(response, content, url)
57        super(HttpError, self).__init__(error_message)
58        self.response = response
59        self.content = content
60        self.url = url
61        self.method_config = method_config
62        self.request = request
63
64    def __str__(self):
65        return HttpError._build_message(self.response, self.content, self.url)
66
67    @staticmethod
68    def _build_message(response, content, url):
69        if isinstance(content, bytes):
70            content = content.decode('ascii', 'replace')
71        return 'HttpError accessing <%s>: response: <%s>, content <%s>' % (
72            url, response, content)
73
74    @property
75    def status_code(self):
76        # TODO(user): Turn this into something better than a
77        # KeyError if there is no status.
78        return int(self.response['status'])
79
80    @classmethod
81    def FromResponse(cls, http_response, **kwargs):
82        try:
83            status_code = int(http_response.info.get('status'))
84            error_cls = _HTTP_ERRORS.get(status_code, cls)
85        except ValueError:
86            error_cls = cls
87        return error_cls(http_response.info, http_response.content,
88                         http_response.request_url, **kwargs)
89
90
91class HttpBadRequestError(HttpError):
92    """HTTP 400 Bad Request."""
93
94
95class HttpUnauthorizedError(HttpError):
96    """HTTP 401 Unauthorized."""
97
98
99class HttpForbiddenError(HttpError):
100    """HTTP 403 Forbidden."""
101
102
103class HttpNotFoundError(HttpError):
104    """HTTP 404 Not Found."""
105
106
107class HttpConflictError(HttpError):
108    """HTTP 409 Conflict."""
109
110
111_HTTP_ERRORS = {
112    400: HttpBadRequestError,
113    401: HttpUnauthorizedError,
114    403: HttpForbiddenError,
115    404: HttpNotFoundError,
116    409: HttpConflictError,
117}
118
119
120class InvalidUserInputError(InvalidDataError):
121
122    """User-provided input is invalid."""
123
124
125class InvalidDataFromServerError(InvalidDataError, CommunicationError):
126
127    """Data received from the server is malformed."""
128
129
130class BatchError(Error):
131
132    """Error generated while constructing a batch request."""
133
134
135class ConfigurationError(Error):
136
137    """Base class for configuration errors."""
138
139
140class GeneratedClientError(Error):
141
142    """The generated client configuration is invalid."""
143
144
145class ConfigurationValueError(UserError):
146
147    """Some part of the user-specified client configuration is invalid."""
148
149
150class ResourceUnavailableError(Error):
151
152    """User requested an unavailable resource."""
153
154
155class CredentialsError(Error):
156
157    """Errors related to invalid credentials."""
158
159
160class TransferError(CommunicationError):
161
162    """Errors related to transfers."""
163
164
165class TransferRetryError(TransferError):
166
167    """Retryable errors related to transfers."""
168
169
170class TransferInvalidError(TransferError):
171
172    """The given transfer is invalid."""
173
174
175class RequestError(CommunicationError):
176
177    """The request was not successful."""
178
179
180class RetryAfterError(HttpError):
181
182    """The response contained a retry-after header."""
183
184    def __init__(self, response, content, url, retry_after, **kwargs):
185        super(RetryAfterError, self).__init__(response, content, url, **kwargs)
186        self.retry_after = int(retry_after)
187
188    @classmethod
189    def FromResponse(cls, http_response, **kwargs):
190        return cls(http_response.info, http_response.content,
191                   http_response.request_url, http_response.retry_after,
192                   **kwargs)
193
194
195class BadStatusCodeError(HttpError):
196
197    """The request completed but returned a bad status code."""
198
199
200class NotYetImplementedError(GeneratedClientError):
201
202    """This functionality is not yet implemented."""
203
204
205class StreamExhausted(Error):
206
207    """Attempted to read more bytes from a stream than were available."""
208