1# Copyright 2014 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Errors for the library.
16
17All exceptions defined by the library
18should be defined in this file.
19"""
20from __future__ import absolute_import
21
22__author__ = "[email protected] (Joe Gregorio)"
23
24import json
25
26from googleapiclient import _helpers as util
27
28
29class Error(Exception):
30    """Base error for this module."""
31
32    pass
33
34
35class HttpError(Error):
36    """HTTP data was invalid or unexpected."""
37
38    @util.positional(3)
39    def __init__(self, resp, content, uri=None):
40        self.resp = resp
41        if not isinstance(content, bytes):
42            raise TypeError("HTTP content should be bytes")
43        self.content = content
44        self.uri = uri
45        self.error_details = ""
46        self.reason = self._get_reason()
47
48    @property
49    def status_code(self):
50        """Return the HTTP status code from the response content."""
51        return self.resp.status
52
53    def _get_reason(self):
54        """Calculate the reason for the error from the response content."""
55        reason = self.resp.reason
56        try:
57            try:
58                data = json.loads(self.content.decode("utf-8"))
59            except json.JSONDecodeError:
60                # In case it is not json
61                data = self.content.decode("utf-8")
62            if isinstance(data, dict):
63                reason = data["error"]["message"]
64                error_detail_keyword = next((kw for kw in ["detail", "details", "errors", "message"] if kw in data["error"]), "")
65                if error_detail_keyword:
66                    self.error_details = data["error"][error_detail_keyword]
67            elif isinstance(data, list) and len(data) > 0:
68                first_error = data[0]
69                reason = first_error["error"]["message"]
70                if "details" in first_error["error"]:
71                    self.error_details = first_error["error"]["details"]
72            else:
73                self.error_details = data
74        except (ValueError, KeyError, TypeError):
75            pass
76        if reason is None:
77            reason = ""
78        return reason.strip()
79
80    def __repr__(self):
81        if self.error_details:
82            return '<HttpError %s when requesting %s returned "%s". Details: "%s">' % (
83                self.resp.status,
84                self.uri,
85                self.reason,
86                self.error_details,
87            )
88        elif self.uri:
89            return '<HttpError %s when requesting %s returned "%s">' % (
90                self.resp.status,
91                self.uri,
92                self.reason,
93            )
94        else:
95            return '<HttpError %s "%s">' % (self.resp.status, self.reason)
96
97    __str__ = __repr__
98
99
100class InvalidJsonError(Error):
101    """The JSON returned could not be parsed."""
102
103    pass
104
105
106class UnknownFileType(Error):
107    """File type unknown or unexpected."""
108
109    pass
110
111
112class UnknownLinkType(Error):
113    """Link type unknown or unexpected."""
114
115    pass
116
117
118class UnknownApiNameOrVersion(Error):
119    """No API with that name and version exists."""
120
121    pass
122
123
124class UnacceptableMimeTypeError(Error):
125    """That is an unacceptable mimetype for this operation."""
126
127    pass
128
129
130class MediaUploadSizeError(Error):
131    """Media is larger than the method can accept."""
132
133    pass
134
135
136class ResumableUploadError(HttpError):
137    """Error occurred during resumable upload."""
138
139    pass
140
141
142class InvalidChunkSizeError(Error):
143    """The given chunksize is not valid."""
144
145    pass
146
147
148class InvalidNotificationError(Error):
149    """The channel Notification is invalid."""
150
151    pass
152
153
154class BatchError(HttpError):
155    """Error occurred during batch operations."""
156
157    @util.positional(2)
158    def __init__(self, reason, resp=None, content=None):
159        self.resp = resp
160        self.content = content
161        self.reason = reason
162
163    def __repr__(self):
164        if getattr(self.resp, "status", None) is None:
165            return '<BatchError "%s">' % (self.reason)
166        else:
167            return '<BatchError %s "%s">' % (self.resp.status, self.reason)
168
169    __str__ = __repr__
170
171
172class UnexpectedMethodError(Error):
173    """Exception raised by RequestMockBuilder on unexpected calls."""
174
175    @util.positional(1)
176    def __init__(self, methodId=None):
177        """Constructor for an UnexpectedMethodError."""
178        super(UnexpectedMethodError, self).__init__(
179            "Received unexpected call %s" % methodId
180        )
181
182
183class UnexpectedBodyError(Error):
184    """Exception raised by RequestMockBuilder on unexpected bodies."""
185
186    def __init__(self, expected, provided):
187        """Constructor for an UnexpectedMethodError."""
188        super(UnexpectedBodyError, self).__init__(
189            "Expected: [%s] - Provided: [%s]" % (expected, provided)
190        )
191