2017-07-21 02:17:28 +02:00
|
|
|
from enum import Enum
|
|
|
|
from typing import Any, Dict, List, Optional, Text, Type
|
2013-10-10 21:37:26 +02:00
|
|
|
|
2013-05-29 23:58:07 +02:00
|
|
|
from django.core.exceptions import PermissionDenied
|
2018-03-22 21:43:28 +01:00
|
|
|
from django.utils.translation import ugettext as _
|
2013-05-29 23:58:07 +02:00
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
class AbstractEnum(Enum):
|
|
|
|
'''An enumeration whose members are used strictly for their names.'''
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __new__(cls: Type['AbstractEnum']) -> 'AbstractEnum':
|
2017-07-21 02:17:28 +02:00
|
|
|
obj = object.__new__(cls)
|
2017-08-25 20:01:20 +02:00
|
|
|
obj._value_ = len(cls.__members__) + 1
|
2017-07-21 02:17:28 +02:00
|
|
|
return obj
|
|
|
|
|
|
|
|
# Override all the `Enum` methods that use `_value_`.
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __repr__(self) -> str:
|
2017-07-21 02:17:28 +02:00
|
|
|
return str(self)
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def value(self) -> None:
|
2017-07-21 02:17:28 +02:00
|
|
|
assert False
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __reduce_ex__(self, proto: int) -> None:
|
2017-07-21 02:17:28 +02:00
|
|
|
assert False
|
|
|
|
|
|
|
|
class ErrorCode(AbstractEnum):
|
|
|
|
BAD_REQUEST = () # Generic name, from the name of HTTP 400.
|
|
|
|
REQUEST_VARIABLE_MISSING = ()
|
|
|
|
REQUEST_VARIABLE_INVALID = ()
|
2017-07-21 02:18:33 +02:00
|
|
|
BAD_IMAGE = ()
|
2018-01-26 16:13:33 +01:00
|
|
|
REALM_UPLOAD_QUOTA = ()
|
2017-07-21 02:17:28 +02:00
|
|
|
BAD_NARROW = ()
|
2018-04-24 20:22:38 +02:00
|
|
|
MISSING_HTTP_EVENT_HEADER = ()
|
2018-03-22 21:43:28 +01:00
|
|
|
STREAM_DOES_NOT_EXIST = ()
|
2017-07-21 02:17:28 +02:00
|
|
|
UNAUTHORIZED_PRINCIPAL = ()
|
2017-07-21 02:20:31 +02:00
|
|
|
BAD_EVENT_QUEUE_ID = ()
|
2017-07-25 03:30:13 +02:00
|
|
|
CSRF_FAILED = ()
|
2017-07-25 02:02:30 +02:00
|
|
|
INVITATION_FAILED = ()
|
2017-10-12 03:02:35 +02:00
|
|
|
INVALID_ZULIP_SERVER = ()
|
2017-07-21 02:17:28 +02:00
|
|
|
|
JsonableError: Move into a normally-typed file.
The file `zerver/lib/request.py` doesn't have type annotations
of its own; if they did, they would duplicate the annotations that
exist in its stub file `zerver/lib/request.pyi`. The latter exists
so that we can provide types for the highly dynamic `REQ` and
`has_request_variables`, which are beyond the type-checker's ken
to type-check, but we should minimize the scope of code that gets
that kind of treatment and `JsonableError` is not at all the sort of
code that needs it.
So move the definition of `JsonableError` into a file that does
get type-checked.
In doing so, the type-checker points out one issue already:
`__str__` should return a `str`, but we had it returning a `Text`,
which on Python 2 is not the same thing. Indeed, because the
message we pass to the `JsonableError` constructor is generally
translated, it may well be a Unicode string stuffed full of
non-ASCII characters. This is potentially a bit of a landmine.
But (a) it can only possibly matter in Python 2 which we intend to
be off before long, and (b) AFAIK it hasn't been biting us in
practice, so we've probably reasonably well worked around it where
it could matter. Leave it as is.
2017-07-20 00:51:54 +02:00
|
|
|
class JsonableError(Exception):
|
2017-07-21 02:17:28 +02:00
|
|
|
'''A standardized error format we can turn into a nice JSON HTTP response.
|
|
|
|
|
|
|
|
This class can be invoked in several ways.
|
|
|
|
|
|
|
|
* Easiest, but completely machine-unreadable:
|
|
|
|
|
|
|
|
raise JsonableError(_("No such widget: {}").format(widget_name))
|
|
|
|
|
|
|
|
The message may be passed through to clients and shown to a user,
|
|
|
|
so translation is required. Because the text will vary depending
|
|
|
|
on the user's language, it's not possible for code to distinguish
|
|
|
|
this error from others in a non-buggy way.
|
|
|
|
|
|
|
|
* Partially machine-readable, with an error code:
|
|
|
|
|
|
|
|
raise JsonableError(_("No such widget: {}").format(widget_name),
|
|
|
|
ErrorCode.NO_SUCH_WIDGET)
|
|
|
|
|
|
|
|
Now the error's `code` attribute can be used, both in server
|
|
|
|
and client code, to identify this type of error. The data
|
|
|
|
(here, the widget name) is still embedded inside a translated
|
|
|
|
string, and can't be accessed by code.
|
|
|
|
|
|
|
|
* Fully machine-readable, with an error code and structured data:
|
|
|
|
|
|
|
|
class NoSuchWidgetError(JsonableError):
|
|
|
|
code = ErrorCode.NO_SUCH_WIDGET
|
|
|
|
data_fields = ['widget_name']
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, widget_name: str) -> None:
|
2017-07-21 02:17:28 +02:00
|
|
|
self.widget_name = widget_name # type: str
|
|
|
|
|
|
|
|
@staticmethod
|
2017-11-05 11:15:10 +01:00
|
|
|
def msg_format() -> str:
|
2017-07-21 02:17:28 +02:00
|
|
|
return _("No such widget: {widget_name}")
|
|
|
|
|
|
|
|
raise NoSuchWidgetError(widget_name)
|
|
|
|
|
|
|
|
Now both server and client code see a `widget_name` attribute.
|
|
|
|
|
|
|
|
Subclasses may also override `http_status_code`.
|
|
|
|
'''
|
|
|
|
|
|
|
|
# Override this in subclasses, or just pass a `code` argument
|
|
|
|
# to the JsonableError constructor.
|
|
|
|
code = ErrorCode.BAD_REQUEST # type: ErrorCode
|
|
|
|
|
|
|
|
# Override this in subclasses if providing structured data.
|
|
|
|
data_fields = [] # type: List[str]
|
|
|
|
|
|
|
|
# Optionally override this in subclasses to return a different HTTP status,
|
|
|
|
# like 403 or 404.
|
2017-07-20 01:23:12 +02:00
|
|
|
http_status_code = 400 # type: int
|
JsonableError: Move into a normally-typed file.
The file `zerver/lib/request.py` doesn't have type annotations
of its own; if they did, they would duplicate the annotations that
exist in its stub file `zerver/lib/request.pyi`. The latter exists
so that we can provide types for the highly dynamic `REQ` and
`has_request_variables`, which are beyond the type-checker's ken
to type-check, but we should minimize the scope of code that gets
that kind of treatment and `JsonableError` is not at all the sort of
code that needs it.
So move the definition of `JsonableError` into a file that does
get type-checked.
In doing so, the type-checker points out one issue already:
`__str__` should return a `str`, but we had it returning a `Text`,
which on Python 2 is not the same thing. Indeed, because the
message we pass to the `JsonableError` constructor is generally
translated, it may well be a Unicode string stuffed full of
non-ASCII characters. This is potentially a bit of a landmine.
But (a) it can only possibly matter in Python 2 which we intend to
be off before long, and (b) AFAIK it hasn't been biting us in
practice, so we've probably reasonably well worked around it where
it could matter. Leave it as is.
2017-07-20 00:51:54 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, msg: Text, code: Optional[ErrorCode]=None) -> None:
|
2017-07-21 02:17:28 +02:00
|
|
|
if code is not None:
|
|
|
|
self.code = code
|
JsonableError: Move into a normally-typed file.
The file `zerver/lib/request.py` doesn't have type annotations
of its own; if they did, they would duplicate the annotations that
exist in its stub file `zerver/lib/request.pyi`. The latter exists
so that we can provide types for the highly dynamic `REQ` and
`has_request_variables`, which are beyond the type-checker's ken
to type-check, but we should minimize the scope of code that gets
that kind of treatment and `JsonableError` is not at all the sort of
code that needs it.
So move the definition of `JsonableError` into a file that does
get type-checked.
In doing so, the type-checker points out one issue already:
`__str__` should return a `str`, but we had it returning a `Text`,
which on Python 2 is not the same thing. Indeed, because the
message we pass to the `JsonableError` constructor is generally
translated, it may well be a Unicode string stuffed full of
non-ASCII characters. This is potentially a bit of a landmine.
But (a) it can only possibly matter in Python 2 which we intend to
be off before long, and (b) AFAIK it hasn't been biting us in
practice, so we've probably reasonably well worked around it where
it could matter. Leave it as is.
2017-07-20 00:51:54 +02:00
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
# `_msg` is an implementation detail of `JsonableError` itself.
|
|
|
|
self._msg = msg # type: Text
|
JsonableError: Move into a normally-typed file.
The file `zerver/lib/request.py` doesn't have type annotations
of its own; if they did, they would duplicate the annotations that
exist in its stub file `zerver/lib/request.pyi`. The latter exists
so that we can provide types for the highly dynamic `REQ` and
`has_request_variables`, which are beyond the type-checker's ken
to type-check, but we should minimize the scope of code that gets
that kind of treatment and `JsonableError` is not at all the sort of
code that needs it.
So move the definition of `JsonableError` into a file that does
get type-checked.
In doing so, the type-checker points out one issue already:
`__str__` should return a `str`, but we had it returning a `Text`,
which on Python 2 is not the same thing. Indeed, because the
message we pass to the `JsonableError` constructor is generally
translated, it may well be a Unicode string stuffed full of
non-ASCII characters. This is potentially a bit of a landmine.
But (a) it can only possibly matter in Python 2 which we intend to
be off before long, and (b) AFAIK it hasn't been biting us in
practice, so we've probably reasonably well worked around it where
it could matter. Leave it as is.
2017-07-20 00:51:54 +02:00
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
@staticmethod
|
2017-11-05 11:15:10 +01:00
|
|
|
def msg_format() -> Text:
|
2017-07-21 02:17:28 +02:00
|
|
|
'''Override in subclasses. Gets the items in `data_fields` as format args.
|
|
|
|
|
|
|
|
This should return (a translation of) a string literal.
|
|
|
|
The reason it's not simply a class attribute is to allow
|
|
|
|
translation to work.
|
|
|
|
'''
|
|
|
|
# Secretly this gets one more format arg not in `data_fields`: `_msg`.
|
|
|
|
# That's for the sake of the `JsonableError` base logic itself, for
|
|
|
|
# the simplest form of use where we just get a plain message string
|
|
|
|
# at construction time.
|
|
|
|
return '{_msg}'
|
|
|
|
|
|
|
|
#
|
|
|
|
# Infrastructure -- not intended to be overridden in subclasses.
|
|
|
|
#
|
|
|
|
|
|
|
|
@property
|
2017-11-05 11:15:10 +01:00
|
|
|
def msg(self) -> Text:
|
2017-07-21 02:17:28 +02:00
|
|
|
format_data = dict(((f, getattr(self, f)) for f in self.data_fields),
|
|
|
|
_msg=getattr(self, '_msg', None))
|
|
|
|
return self.msg_format().format(**format_data)
|
|
|
|
|
|
|
|
@property
|
2017-11-05 11:15:10 +01:00
|
|
|
def data(self) -> Dict[str, Any]:
|
2017-07-21 02:17:28 +02:00
|
|
|
return dict(((f, getattr(self, f)) for f in self.data_fields),
|
|
|
|
code=self.code.name)
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def to_json(self) -> Dict[str, Any]:
|
2017-07-21 02:17:28 +02:00
|
|
|
d = {'result': 'error', 'msg': self.msg}
|
|
|
|
d.update(self.data)
|
|
|
|
return d
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __str__(self) -> str:
|
2017-08-25 20:01:20 +02:00
|
|
|
return self.msg
|
JsonableError: Move into a normally-typed file.
The file `zerver/lib/request.py` doesn't have type annotations
of its own; if they did, they would duplicate the annotations that
exist in its stub file `zerver/lib/request.pyi`. The latter exists
so that we can provide types for the highly dynamic `REQ` and
`has_request_variables`, which are beyond the type-checker's ken
to type-check, but we should minimize the scope of code that gets
that kind of treatment and `JsonableError` is not at all the sort of
code that needs it.
So move the definition of `JsonableError` into a file that does
get type-checked.
In doing so, the type-checker points out one issue already:
`__str__` should return a `str`, but we had it returning a `Text`,
which on Python 2 is not the same thing. Indeed, because the
message we pass to the `JsonableError` constructor is generally
translated, it may well be a Unicode string stuffed full of
non-ASCII characters. This is potentially a bit of a landmine.
But (a) it can only possibly matter in Python 2 which we intend to
be off before long, and (b) AFAIK it hasn't been biting us in
practice, so we've probably reasonably well worked around it where
it could matter. Leave it as is.
2017-07-20 00:51:54 +02:00
|
|
|
|
2018-03-22 21:43:28 +01:00
|
|
|
class StreamDoesNotExistError(JsonableError):
|
|
|
|
code = ErrorCode.STREAM_DOES_NOT_EXIST
|
|
|
|
data_fields = ['stream']
|
|
|
|
|
|
|
|
def __init__(self, stream: str) -> None:
|
|
|
|
self.stream = stream
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Stream '{stream}' does not exist")
|
|
|
|
|
2013-05-29 23:58:07 +02:00
|
|
|
class RateLimited(PermissionDenied):
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, msg: str="") -> None:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().__init__(msg)
|