Merge branch 'main' into avm99963-monorail
Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266
GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/third_party/endpoints/errors.py b/third_party/endpoints/errors.py
new file mode 100644
index 0000000..e98c76d
--- /dev/null
+++ b/third_party/endpoints/errors.py
@@ -0,0 +1,285 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Error handling and exceptions used in the local Cloud Endpoints server."""
+
+# pylint: disable=g-bad-name
+from __future__ import absolute_import
+
+import json
+import logging
+
+from . import generated_error_info
+
+__all__ = ['BackendError',
+ 'BasicTypeParameterError',
+ 'EnumRejectionError',
+ 'InvalidParameterError',
+ 'RequestError',
+ 'RequestRejectionError']
+
+_logger = logging.getLogger(__name__)
+
+_INVALID_ENUM_TEMPLATE = 'Invalid string value: %r. Allowed values: %r'
+_INVALID_BASIC_PARAM_TEMPLATE = 'Invalid %s value: %r.'
+
+
+class RequestError(Exception):
+ """Base class for errors that happen while processing a request."""
+
+ def status_code(self):
+ """HTTP status code number associated with this error.
+
+ Subclasses must implement this, returning an integer with the status
+ code number for the error.
+
+ Example: 400
+
+ Raises:
+ NotImplementedError: Subclasses must override this function.
+ """
+ raise NotImplementedError
+
+ def message(self):
+ """Text message explaining the error.
+
+ Subclasses must implement this, returning a string that explains the
+ error.
+
+ Raises:
+ NotImplementedError: Subclasses must override this function.
+ """
+ raise NotImplementedError
+
+ def reason(self):
+ """Get the reason for the error.
+
+ Error reason is a custom string in the Cloud Endpoints server. When
+ possible, this should match the reason that the live server will generate,
+ based on the error's status code. If this returns None, the error formatter
+ will attempt to generate a reason from the status code.
+
+ Returns:
+ None, by default. Subclasses can override this if they have a specific
+ error reason.
+ """
+ raise NotImplementedError
+
+ def domain(self):
+ """Get the domain for this error.
+
+ Returns:
+ The string 'global' by default. Subclasses can override this if they have
+ a different domain.
+ """
+ return 'global'
+
+ def extra_fields(self):
+ """Return a dict of extra fields to add to the error response.
+
+ Some errors have additional information. This provides a way for subclasses
+ to provide that information.
+
+ Returns:
+ None, by default. Subclasses can return a dict with values to add
+ to the error response.
+ """
+ return None
+
+ def __format_error(self, error_list_tag):
+ """Format this error into a JSON response.
+
+ Args:
+ error_list_tag: A string specifying the name of the tag to use for the
+ error list.
+
+ Returns:
+ A dict containing the reformatted JSON error response.
+ """
+ error = {'domain': self.domain(),
+ 'reason': self.reason(),
+ 'message': self.message()}
+ error.update(self.extra_fields() or {})
+ return {'error': {error_list_tag: [error],
+ 'code': self.status_code(),
+ 'message': self.message()}}
+
+ def rest_error(self):
+ """Format this error into a response to a REST request.
+
+ Returns:
+ A string containing the reformatted error response.
+ """
+ error_json = self.__format_error('errors')
+ return json.dumps(error_json, indent=1, sort_keys=True)
+
+ def rpc_error(self):
+ """Format this error into a response to a JSON RPC request.
+
+
+ Returns:
+ A dict containing the reformatted JSON error response.
+ """
+ return self.__format_error('data')
+
+
+class RequestRejectionError(RequestError):
+ """Base class for invalid/rejected requests.
+
+ To be raised when parsing the request values and comparing them against the
+ generated discovery document.
+ """
+
+ def status_code(self):
+ return 400
+
+
+class InvalidParameterError(RequestRejectionError):
+ """Base class for invalid parameter errors.
+
+ Child classes only need to implement the message() function.
+ """
+
+ def __init__(self, parameter_name, value):
+ """Constructor for InvalidParameterError.
+
+ Args:
+ parameter_name: String; the name of the parameter which had a value
+ rejected.
+ value: The actual value passed in for the parameter. Usually string.
+ """
+ super(InvalidParameterError, self).__init__()
+ self.parameter_name = parameter_name
+ self.value = value
+
+ def reason(self):
+ """Returns the server's reason for this error.
+
+ Returns:
+ A string containing a short error reason.
+ """
+ return 'invalidParameter'
+
+ def extra_fields(self):
+ """Returns extra fields to add to the error response.
+
+ Returns:
+ A dict containing extra fields to add to the error response.
+ """
+ return {'locationType': 'parameter',
+ 'location': self.parameter_name}
+
+
+class BasicTypeParameterError(InvalidParameterError):
+ """Request rejection exception for basic types (int, float)."""
+
+ def __init__(self, parameter_name, value, type_name):
+ """Constructor for BasicTypeParameterError.
+
+ Args:
+ parameter_name: String; the name of the parameter which had a value
+ rejected.
+ value: The actual value passed in for the enum. Usually string.
+ type_name: Descriptive name of the data type expected.
+ """
+ super(BasicTypeParameterError, self).__init__(parameter_name, value)
+ self.type_name = type_name
+
+ def message(self):
+ """A descriptive message describing the error."""
+ return _INVALID_BASIC_PARAM_TEMPLATE % (self.type_name, self.value)
+
+
+class EnumRejectionError(InvalidParameterError):
+ """Custom request rejection exception for enum values."""
+
+ def __init__(self, parameter_name, value, allowed_values):
+ """Constructor for EnumRejectionError.
+
+ Args:
+ parameter_name: String; the name of the enum parameter which had a value
+ rejected.
+ value: The actual value passed in for the enum. Usually string.
+ allowed_values: List of strings allowed for the enum.
+ """
+ super(EnumRejectionError, self).__init__(parameter_name, value)
+ self.allowed_values = allowed_values
+
+ def message(self):
+ """A descriptive message describing the error."""
+ return _INVALID_ENUM_TEMPLATE % (self.value, self.allowed_values)
+
+
+class BackendError(RequestError):
+ """Exception raised when the backend returns an error code."""
+
+ def __init__(self, body, status):
+ super(BackendError, self).__init__()
+ # Convert backend error status to whatever the live server would return.
+ status_code = self._get_status_code(status)
+ self._error_info = generated_error_info.get_error_info(status_code)
+
+ try:
+ error_json = json.loads(body)
+ self._message = error_json.get('error_message')
+ except TypeError:
+ self._message = body
+
+ def _get_status_code(self, http_status):
+ """Get the HTTP status code from an HTTP status string.
+
+ Args:
+ http_status: A string containing a HTTP status code and reason.
+
+ Returns:
+ An integer with the status code number from http_status.
+ """
+ try:
+ return int(http_status.split(' ', 1)[0])
+ except TypeError:
+ _logger.warning('Unable to find status code in HTTP status %r.',
+ http_status)
+ return 500
+
+ def status_code(self):
+ """Return the HTTP status code number for this error.
+
+ Returns:
+ An integer containing the status code for this error.
+ """
+ return self._error_info.http_status
+
+ def message(self):
+ """Return a descriptive message for this error.
+
+ Returns:
+ A string containing a descriptive message for this error.
+ """
+ return self._message
+
+ def reason(self):
+ """Return the short reason for this error.
+
+ Returns:
+ A string with the reason for this error.
+ """
+ return self._error_info.reason
+
+ def domain(self):
+ """Return the remapped domain for this error.
+
+ Returns:
+ A string containing the remapped domain for this error.
+ """
+ return self._error_info.domain