2021-08-14 02:42:43 +02:00
|
|
|
from enum import Enum, auto
|
2023-02-03 23:52:46 +01:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
2013-10-10 21:37:26 +02:00
|
|
|
|
2021-07-04 10:00:10 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2021-04-16 00:57:30 +02:00
|
|
|
from django.utils.translation import gettext as _
|
2023-02-03 23:52:46 +01:00
|
|
|
from django_stubs_ext import StrPromise
|
2023-10-12 19:43:45 +02:00
|
|
|
from typing_extensions import override
|
2013-05-29 23:58:07 +02:00
|
|
|
|
2019-10-09 02:02:06 +02:00
|
|
|
|
2021-08-14 02:42:43 +02:00
|
|
|
class ErrorCode(Enum):
|
|
|
|
BAD_REQUEST = auto() # Generic name, from the name of HTTP 400.
|
|
|
|
REQUEST_VARIABLE_MISSING = auto()
|
|
|
|
REQUEST_VARIABLE_INVALID = auto()
|
|
|
|
INVALID_JSON = auto()
|
|
|
|
BAD_IMAGE = auto()
|
|
|
|
REALM_UPLOAD_QUOTA = auto()
|
|
|
|
BAD_NARROW = auto()
|
|
|
|
CANNOT_DEACTIVATE_LAST_USER = auto()
|
|
|
|
MISSING_HTTP_EVENT_HEADER = auto()
|
|
|
|
STREAM_DOES_NOT_EXIST = auto()
|
|
|
|
UNAUTHORIZED_PRINCIPAL = auto()
|
|
|
|
UNSUPPORTED_WEBHOOK_EVENT_TYPE = auto()
|
2021-09-13 20:23:54 +02:00
|
|
|
ANOMALOUS_WEBHOOK_PAYLOAD = auto()
|
2021-08-14 02:42:43 +02:00
|
|
|
BAD_EVENT_QUEUE_ID = auto()
|
|
|
|
CSRF_FAILED = auto()
|
|
|
|
INVITATION_FAILED = auto()
|
|
|
|
INVALID_ZULIP_SERVER = auto()
|
|
|
|
INVALID_MARKDOWN_INCLUDE_STATEMENT = auto()
|
|
|
|
REQUEST_CONFUSING_VAR = auto()
|
|
|
|
INVALID_API_KEY = auto()
|
|
|
|
INVALID_ZOOM_TOKEN = auto()
|
|
|
|
UNAUTHENTICATED_USER = auto()
|
|
|
|
NONEXISTENT_SUBDOMAIN = auto()
|
|
|
|
RATE_LIMIT_HIT = auto()
|
|
|
|
USER_DEACTIVATED = auto()
|
|
|
|
REALM_DEACTIVATED = auto()
|
2022-01-14 04:20:39 +01:00
|
|
|
REMOTE_SERVER_DEACTIVATED = auto()
|
2021-08-14 02:42:43 +02:00
|
|
|
PASSWORD_AUTH_DISABLED = auto()
|
|
|
|
PASSWORD_RESET_REQUIRED = auto()
|
|
|
|
AUTHENTICATION_FAILED = auto()
|
2022-07-13 03:29:39 +02:00
|
|
|
UNAUTHORIZED = auto()
|
2022-10-02 21:32:36 +02:00
|
|
|
REQUEST_TIMEOUT = auto()
|
2022-10-14 12:18:37 +02:00
|
|
|
MOVE_MESSAGES_TIME_LIMIT_EXCEEDED = auto()
|
2023-07-18 19:33:27 +02:00
|
|
|
REACTION_ALREADY_EXISTS = auto()
|
|
|
|
REACTION_DOES_NOT_EXIST = auto()
|
2023-09-12 22:34:54 +02:00
|
|
|
SERVER_NOT_READY = auto()
|
2017-07-21 02:17:28 +02:00
|
|
|
|
2021-02-12 08:19:30 +01: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):
|
2021-02-12 08:19:30 +01:00
|
|
|
"""A standardized error format we can turn into a nice JSON HTTP response.
|
2017-07-21 02:17:28 +02:00
|
|
|
|
2018-08-22 17:14:27 +02:00
|
|
|
This class can be invoked in a couple ways.
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
|
|
* 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:
|
2020-05-09 00:10:17 +02:00
|
|
|
self.widget_name: str = widget_name
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
@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)
|
|
|
|
|
2018-08-22 17:14:27 +02:00
|
|
|
Now both server and client code see a `widget_name` attribute
|
|
|
|
and an error code.
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
Subclasses may also override `http_status_code`.
|
2021-02-12 08:19:30 +01:00
|
|
|
"""
|
2017-07-21 02:17:28 +02:00
|
|
|
|
2018-08-22 17:14:27 +02:00
|
|
|
# Override this in subclasses, as needed.
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
code: ErrorCode = ErrorCode.BAD_REQUEST
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
# Override this in subclasses if providing structured data.
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
data_fields: List[str] = []
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
# Optionally override this in subclasses to return a different HTTP status,
|
|
|
|
# like 403 or 404.
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
http_status_code: int = 400
|
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
|
|
|
|
2023-02-03 23:52:46 +01:00
|
|
|
def __init__(self, msg: Union[str, StrPromise]) -> None:
|
2017-07-21 02:17:28 +02:00
|
|
|
# `_msg` is an implementation detail of `JsonableError` itself.
|
2023-02-03 23:52:46 +01:00
|
|
|
self._msg = 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
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
@staticmethod
|
2018-05-11 01:40:23 +02:00
|
|
|
def msg_format() -> str:
|
2021-02-12 08:19:30 +01:00
|
|
|
"""Override in subclasses. Gets the items in `data_fields` as format args.
|
2017-07-21 02:17:28 +02:00
|
|
|
|
|
|
|
This should return (a translation of) a string literal.
|
|
|
|
The reason it's not simply a class attribute is to allow
|
|
|
|
translation to work.
|
2021-02-12 08:19:30 +01:00
|
|
|
"""
|
2017-07-21 02:17:28 +02:00
|
|
|
# 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.
|
2021-02-12 08:20:45 +01:00
|
|
|
return "{_msg}"
|
2017-07-21 02:17:28 +02:00
|
|
|
|
2020-11-27 16:33:01 +01:00
|
|
|
@property
|
|
|
|
def extra_headers(self) -> Dict[str, Any]:
|
|
|
|
return {}
|
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
#
|
|
|
|
# Infrastructure -- not intended to be overridden in subclasses.
|
|
|
|
#
|
|
|
|
|
|
|
|
@property
|
2018-05-11 01:40:23 +02:00
|
|
|
def msg(self) -> str:
|
2021-02-12 08:19:30 +01:00
|
|
|
format_data = dict(
|
2021-02-12 08:20:45 +01:00
|
|
|
((f, getattr(self, f)) for f in self.data_fields), _msg=getattr(self, "_msg", None)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-07-21 02:17:28 +02:00
|
|
|
return self.msg_format().format(**format_data)
|
|
|
|
|
|
|
|
@property
|
2017-11-05 11:15:10 +01:00
|
|
|
def data(self) -> Dict[str, Any]:
|
2021-02-12 08:19:30 +01:00
|
|
|
return dict(((f, getattr(self, f)) for f in self.data_fields), code=self.code.name)
|
2017-07-21 02:17:28 +02:00
|
|
|
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-07-13 03:29:39 +02:00
|
|
|
class UnauthorizedError(JsonableError):
|
|
|
|
code: ErrorCode = ErrorCode.UNAUTHORIZED
|
|
|
|
http_status_code: int = 401
|
|
|
|
|
|
|
|
def __init__(self, msg: Optional[str] = None, www_authenticate: Optional[str] = None) -> None:
|
|
|
|
if msg is None:
|
|
|
|
msg = _("Not logged in: API authentication or user session required")
|
|
|
|
super().__init__(msg)
|
|
|
|
if www_authenticate is None:
|
|
|
|
self.www_authenticate = 'Basic realm="zulip"'
|
|
|
|
elif www_authenticate == "session":
|
|
|
|
self.www_authenticate = 'Session realm="zulip"'
|
|
|
|
else:
|
|
|
|
raise AssertionError("Invalid www_authenticate value!")
|
|
|
|
|
|
|
|
@property
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2022-07-13 03:29:39 +02:00
|
|
|
def extra_headers(self) -> Dict[str, Any]:
|
|
|
|
extra_headers_dict = super().extra_headers
|
|
|
|
extra_headers_dict["WWW-Authenticate"] = self.www_authenticate
|
|
|
|
return extra_headers_dict
|
|
|
|
|
|
|
|
|
2018-03-22 21:43:28 +01:00
|
|
|
class StreamDoesNotExistError(JsonableError):
|
|
|
|
code = ErrorCode.STREAM_DOES_NOT_EXIST
|
2021-02-12 08:20:45 +01:00
|
|
|
data_fields = ["stream"]
|
2018-03-22 21:43:28 +01:00
|
|
|
|
|
|
|
def __init__(self, stream: str) -> None:
|
|
|
|
self.stream = stream
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-03-22 21:43:28 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Stream '{stream}' does not exist")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-01-28 05:28:29 +01:00
|
|
|
class StreamWithIDDoesNotExistError(JsonableError):
|
|
|
|
code = ErrorCode.STREAM_DOES_NOT_EXIST
|
2021-02-12 08:20:45 +01:00
|
|
|
data_fields = ["stream_id"]
|
2019-01-28 05:28:29 +01:00
|
|
|
|
|
|
|
def __init__(self, stream_id: int) -> None:
|
|
|
|
self.stream_id = stream_id
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2019-01-28 05:28:29 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Stream with ID '{stream_id}' does not exist")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-08-21 08:14:46 +02:00
|
|
|
class CannotDeactivateLastUserError(JsonableError):
|
|
|
|
code = ErrorCode.CANNOT_DEACTIVATE_LAST_USER
|
2021-02-12 08:20:45 +01:00
|
|
|
data_fields = ["is_last_owner", "entity"]
|
2018-08-21 08:14:46 +02:00
|
|
|
|
2020-05-16 21:06:43 +02:00
|
|
|
def __init__(self, is_last_owner: bool) -> None:
|
|
|
|
self.is_last_owner = is_last_owner
|
|
|
|
self.entity = _("organization owner") if is_last_owner else _("user")
|
2018-08-21 08:14:46 +02:00
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-08-21 08:14:46 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Cannot deactivate the only {entity}.")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class InvalidMarkdownIncludeStatementError(JsonableError):
|
2018-12-23 00:13:57 +01:00
|
|
|
code = ErrorCode.INVALID_MARKDOWN_INCLUDE_STATEMENT
|
2021-02-12 08:20:45 +01:00
|
|
|
data_fields = ["include_statement"]
|
2018-12-23 00:13:57 +01:00
|
|
|
|
|
|
|
def __init__(self, include_statement: str) -> None:
|
|
|
|
self.include_statement = include_statement
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-12-23 00:13:57 +01:00
|
|
|
def msg_format() -> str:
|
2020-08-11 01:47:49 +02:00
|
|
|
return _("Invalid Markdown include statement: {include_statement}")
|
2018-12-23 00:13:57 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class RateLimitedError(JsonableError):
|
2020-11-27 16:33:01 +01:00
|
|
|
code = ErrorCode.RATE_LIMIT_HIT
|
|
|
|
http_status_code = 429
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def __init__(self, secs_to_freedom: Optional[float] = None) -> None:
|
2020-11-27 16:33:01 +01:00
|
|
|
self.secs_to_freedom = secs_to_freedom
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-11-27 16:33:01 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("API usage exceeded rate limit")
|
|
|
|
|
|
|
|
@property
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-11-27 16:33:01 +01:00
|
|
|
def extra_headers(self) -> Dict[str, Any]:
|
|
|
|
extra_headers_dict = super().extra_headers
|
|
|
|
if self.secs_to_freedom is not None:
|
|
|
|
extra_headers_dict["Retry-After"] = self.secs_to_freedom
|
|
|
|
|
|
|
|
return extra_headers_dict
|
|
|
|
|
|
|
|
@property
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-11-27 16:33:01 +01:00
|
|
|
def data(self) -> Dict[str, Any]:
|
|
|
|
data_dict = super().data
|
2021-02-12 08:20:45 +01:00
|
|
|
data_dict["retry-after"] = self.secs_to_freedom
|
2020-11-27 16:33:01 +01:00
|
|
|
|
|
|
|
return data_dict
|
2018-06-21 15:09:14 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-11-15 05:31:34 +01:00
|
|
|
class InvalidJSONError(JsonableError):
|
|
|
|
code = ErrorCode.INVALID_JSON
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-11-15 05:31:34 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Malformed JSON")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class OrganizationMemberRequiredError(JsonableError):
|
2020-07-15 22:18:32 +02:00
|
|
|
code: ErrorCode = ErrorCode.UNAUTHORIZED_PRINCIPAL
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2020-10-17 03:42:50 +02:00
|
|
|
pass
|
2020-07-15 22:18:32 +02:00
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-07-15 22:18:32 +02:00
|
|
|
def msg_format() -> str:
|
2020-10-17 03:42:50 +02:00
|
|
|
return _("Must be an organization member")
|
2020-07-15 22:18:32 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class OrganizationAdministratorRequiredError(JsonableError):
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
code: ErrorCode = ErrorCode.UNAUTHORIZED_PRINCIPAL
|
2019-11-16 15:53:56 +01:00
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2020-10-17 03:42:50 +02:00
|
|
|
pass
|
2019-11-16 15:53:56 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2019-11-16 15:53:56 +01:00
|
|
|
def msg_format() -> str:
|
2020-10-17 03:42:50 +02:00
|
|
|
return _("Must be an organization administrator")
|
2019-11-16 15:53:56 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class OrganizationOwnerRequiredError(JsonableError):
|
2020-06-11 00:26:49 +02:00
|
|
|
code: ErrorCode = ErrorCode.UNAUTHORIZED_PRINCIPAL
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2020-10-17 03:42:50 +02:00
|
|
|
pass
|
2020-06-11 00:26:49 +02:00
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-06-11 00:26:49 +02:00
|
|
|
def msg_format() -> str:
|
2020-10-17 03:42:50 +02:00
|
|
|
return _("Must be an organization owner")
|
2020-06-11 00:26:49 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-07-05 20:24:44 +02:00
|
|
|
class AuthenticationFailedError(JsonableError):
|
|
|
|
# Generic class for authentication failures
|
|
|
|
code: ErrorCode = ErrorCode.AUTHENTICATION_FAILED
|
2021-07-05 20:28:24 +02:00
|
|
|
http_status_code = 401
|
2021-07-05 20:24:44 +02:00
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-07-05 20:24:44 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Your username or password is incorrect")
|
|
|
|
|
|
|
|
|
2021-07-05 20:27:43 +02:00
|
|
|
class UserDeactivatedError(AuthenticationFailedError):
|
2021-03-31 12:00:56 +02:00
|
|
|
code: ErrorCode = ErrorCode.USER_DEACTIVATED
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-03-31 12:00:56 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Account is deactivated")
|
|
|
|
|
|
|
|
|
2021-07-05 20:27:43 +02:00
|
|
|
class RealmDeactivatedError(AuthenticationFailedError):
|
2021-03-31 13:14:08 +02:00
|
|
|
code: ErrorCode = ErrorCode.REALM_DEACTIVATED
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-03-31 13:14:08 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("This organization has been deactivated")
|
|
|
|
|
|
|
|
|
2022-01-14 04:20:39 +01:00
|
|
|
class RemoteServerDeactivatedError(AuthenticationFailedError):
|
|
|
|
code: ErrorCode = ErrorCode.REALM_DEACTIVATED
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2022-01-14 04:20:39 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _(
|
|
|
|
"The mobile push notification service registration for your server has been deactivated"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-07-05 20:27:43 +02:00
|
|
|
class PasswordAuthDisabledError(AuthenticationFailedError):
|
2021-07-05 20:24:44 +02:00
|
|
|
code: ErrorCode = ErrorCode.PASSWORD_AUTH_DISABLED
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-07-05 20:24:44 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Password authentication is disabled in this organization")
|
|
|
|
|
|
|
|
|
2021-07-05 20:27:43 +02:00
|
|
|
class PasswordResetRequiredError(AuthenticationFailedError):
|
2021-07-05 20:24:44 +02:00
|
|
|
code: ErrorCode = ErrorCode.PASSWORD_RESET_REQUIRED
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-07-05 20:24:44 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Your password has been disabled and needs to be reset")
|
|
|
|
|
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class MarkdownRenderingError(Exception):
|
2018-06-21 15:09:14 +02:00
|
|
|
pass
|
2019-01-05 20:18:18 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-01-05 20:18:18 +01:00
|
|
|
class InvalidAPIKeyError(JsonableError):
|
|
|
|
code = ErrorCode.INVALID_API_KEY
|
|
|
|
http_status_code = 401
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2019-01-05 20:18:18 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Invalid API key")
|
2019-06-06 05:55:09 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-12-16 08:12:39 +01:00
|
|
|
class InvalidAPIKeyFormatError(InvalidAPIKeyError):
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2019-12-16 08:12:39 +01:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Malformed API key")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-09-13 20:23:54 +02:00
|
|
|
class WebhookError(JsonableError):
|
|
|
|
"""
|
|
|
|
Intended as a generic exception raised by specific webhook
|
|
|
|
integrations. This class is subclassed by more specific exceptions
|
2022-11-17 09:30:48 +01:00
|
|
|
such as UnsupportedWebhookEventTypeError and AnomalousWebhookPayloadError.
|
2021-09-13 20:23:54 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
data_fields = ["webhook_name"]
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
# webhook_name is often set by decorators such as webhook_view
|
|
|
|
# in zerver/decorator.py
|
|
|
|
self.webhook_name = "(unknown)"
|
|
|
|
|
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class UnsupportedWebhookEventTypeError(WebhookError):
|
2021-09-13 20:23:54 +02:00
|
|
|
"""Intended as an exception for event formats that we know the
|
|
|
|
third-party service generates but which Zulip doesn't support /
|
|
|
|
generate a message for.
|
|
|
|
|
|
|
|
Exceptions where we cannot parse the event type, possibly because
|
|
|
|
the event isn't actually from the service in question, should
|
2022-11-17 09:30:48 +01:00
|
|
|
raise AnomalousWebhookPayloadError.
|
2021-09-13 20:23:54 +02:00
|
|
|
"""
|
|
|
|
|
2020-08-19 22:26:38 +02:00
|
|
|
code = ErrorCode.UNSUPPORTED_WEBHOOK_EVENT_TYPE
|
2023-04-04 07:09:30 +02:00
|
|
|
http_status_code = 200
|
2021-02-12 08:20:45 +01:00
|
|
|
data_fields = ["webhook_name", "event_type"]
|
2019-06-06 05:55:09 +02:00
|
|
|
|
2020-08-20 00:50:06 +02:00
|
|
|
def __init__(self, event_type: Optional[str]) -> None:
|
2021-09-13 20:23:54 +02:00
|
|
|
super().__init__()
|
2019-06-06 05:55:09 +02:00
|
|
|
self.event_type = event_type
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2019-06-06 05:55:09 +02:00
|
|
|
def msg_format() -> str:
|
2023-04-04 07:09:30 +02:00
|
|
|
return _(
|
|
|
|
"The '{event_type}' event isn't currently supported by the {webhook_name} webhook; ignoring"
|
|
|
|
)
|
2020-08-22 20:20:42 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class AnomalousWebhookPayloadError(WebhookError):
|
2021-09-13 20:23:54 +02:00
|
|
|
"""Intended as an exception for incoming webhook requests that we
|
|
|
|
cannot recognize as having been generated by the service in
|
|
|
|
question. (E.g. because someone pointed a Jira server at the
|
|
|
|
GitHub integration URL).
|
|
|
|
|
|
|
|
If we can parse the event but don't support it, use
|
2022-11-17 09:30:48 +01:00
|
|
|
UnsupportedWebhookEventTypeError.
|
2021-09-13 20:23:54 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
code = ErrorCode.ANOMALOUS_WEBHOOK_PAYLOAD
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-09-13 20:23:54 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Unable to parse request: Did {webhook_name} generate this event?")
|
|
|
|
|
|
|
|
|
2020-08-22 20:20:42 +02:00
|
|
|
class MissingAuthenticationError(JsonableError):
|
|
|
|
code = ErrorCode.UNAUTHENTICATED_USER
|
|
|
|
http_status_code = 401
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# No msg_format is defined since this exception is caught and
|
|
|
|
# converted into json_unauthorized in Zulip's middleware.
|
2020-09-01 13:56:15 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-09-01 13:56:15 +02:00
|
|
|
class InvalidSubdomainError(JsonableError):
|
|
|
|
code = ErrorCode.NONEXISTENT_SUBDOMAIN
|
|
|
|
http_status_code = 404
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2020-09-01 13:56:15 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Invalid subdomain")
|
2020-11-23 15:38:48 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-11-17 09:30:48 +01:00
|
|
|
class ZephyrMessageAlreadySentError(Exception):
|
2020-11-23 15:38:48 +01:00
|
|
|
def __init__(self, message_id: int) -> None:
|
|
|
|
self.message_id = message_id
|
2021-06-07 18:11:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
class InvitationError(JsonableError):
|
|
|
|
code = ErrorCode.INVITATION_FAILED
|
2021-09-21 18:46:48 +02:00
|
|
|
data_fields = [
|
|
|
|
"errors",
|
|
|
|
"sent_invitations",
|
|
|
|
"license_limit_reached",
|
|
|
|
"daily_limit_reached",
|
|
|
|
]
|
2021-06-07 18:11:26 +02:00
|
|
|
|
|
|
|
def __init__(
|
2021-05-28 15:57:08 +02:00
|
|
|
self,
|
|
|
|
msg: str,
|
|
|
|
errors: List[Tuple[str, str, bool]],
|
|
|
|
sent_invitations: bool,
|
|
|
|
license_limit_reached: bool = False,
|
2021-09-21 18:46:48 +02:00
|
|
|
daily_limit_reached: bool = False,
|
2021-06-07 18:11:26 +02:00
|
|
|
) -> None:
|
|
|
|
self._msg: str = msg
|
|
|
|
self.errors: List[Tuple[str, str, bool]] = errors
|
|
|
|
self.sent_invitations: bool = sent_invitations
|
2021-05-28 15:57:08 +02:00
|
|
|
self.license_limit_reached: bool = license_limit_reached
|
2021-09-21 18:46:48 +02:00
|
|
|
self.daily_limit_reached: bool = daily_limit_reached
|
2021-07-04 08:45:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
class AccessDeniedError(JsonableError):
|
|
|
|
http_status_code = 403
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2021-07-04 08:45:34 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Access denied")
|
2021-07-04 08:52:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ResourceNotFoundError(JsonableError):
|
|
|
|
http_status_code = 404
|
2021-07-04 10:00:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ValidationFailureError(JsonableError):
|
|
|
|
# This class translations a Django ValidationError into a
|
|
|
|
# Zulip-style JsonableError, sending back just the first error for
|
|
|
|
# consistency of API.
|
|
|
|
data_fields = ["errors"]
|
|
|
|
|
|
|
|
def __init__(self, error: ValidationError) -> None:
|
|
|
|
super().__init__(error.messages[0])
|
2022-07-22 23:52:07 +02:00
|
|
|
self.errors = error.message_dict
|
2022-10-14 12:18:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
class MessageMoveError(JsonableError):
|
|
|
|
code = ErrorCode.MOVE_MESSAGES_TIME_LIMIT_EXCEEDED
|
|
|
|
data_fields = [
|
|
|
|
"first_message_id_allowed_to_move",
|
|
|
|
"total_messages_in_topic",
|
|
|
|
"total_messages_allowed_to_move",
|
|
|
|
]
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
first_message_id_allowed_to_move: int,
|
|
|
|
total_messages_in_topic: int,
|
|
|
|
total_messages_allowed_to_move: int,
|
|
|
|
) -> None:
|
|
|
|
self.first_message_id_allowed_to_move = first_message_id_allowed_to_move
|
|
|
|
self.total_messages_in_topic = total_messages_in_topic
|
|
|
|
self.total_messages_allowed_to_move = total_messages_allowed_to_move
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2022-10-14 12:18:37 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _(
|
|
|
|
"You only have permission to move the {total_messages_allowed_to_move}/{total_messages_in_topic} most recent messages in this topic."
|
|
|
|
)
|
2023-07-18 19:33:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ReactionExistsError(JsonableError):
|
|
|
|
code = ErrorCode.REACTION_ALREADY_EXISTS
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2023-07-18 19:33:27 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Reaction already exists.")
|
|
|
|
|
|
|
|
|
|
|
|
class ReactionDoesNotExistError(JsonableError):
|
|
|
|
code = ErrorCode.REACTION_DOES_NOT_EXIST
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2023-07-18 19:33:27 +02:00
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Reaction doesn't exist.")
|
api: Add new typed_endpoint decorators.
The goal of typed_endpoint is to replicate most features supported by
has_request_variables, and to improve on top of it. There are some
unresolved issues that we don't plan to work on currently. For example,
typed_endpoint does not support ignored_parameters_supported for 400
responses, and it does not run validators on path-only arguments.
Unlike has_request_variables, typed_endpoint supports error handling by
processing validation errors from Pydantic.
Most features supported by has_request_variables are supported by
typed_endpoint in various ways.
To define a function, use a syntax like this with Annotated if there is
any metadata you want to associate with a parameter, do note that
parameters that are not keyword-only are ignored from the request:
```
@typed_endpoint
def view(
request: HttpRequest,
user_profile: UserProfile,
*,
foo: Annotated[int, ApiParamConfig(path_only=True)],
bar: Json[int],
other: Annotated[
Json[int],
ApiParamConfig(
whence="lorem",
documentation_status=NTENTIONALLY_UNDOCUMENTED
)
] = 10,
) -> HttpResponse:
....
```
There are also some shorthands for the commonly used annotated types,
which are encouraged when applicable for better readability and less
typing:
```
WebhookPayload = Annotated[Json[T], ApiParamConfig(argument_type_is_body=True)]
PathOnly = Annotated[T, ApiParamConfig(path_only=True)]
```
Then the view function above can be rewritten as:
```
@typed_endpoint
def view(
request: HttpRequest,
user_profile: UserProfile,
*,
foo: PathOnly[int],
bar: Json[int],
other: Annotated[
Json[int],
ApiParamConfig(
whence="lorem",
documentation_status=INTENTIONALLY_UNDOCUMENTED
)
] = 10,
) -> HttpResponse:
....
```
There are some intentional restrictions:
- A single parameter cannot have more than one ApiParamConfig
- Path-only parameters cannot have default values
- argument_type_is_body is incompatible with whence
- Arguments of name "request", "user_profile", "args", and "kwargs" and
etc. are ignored by typed_endpoint.
- positional-only arguments are not supported by typed_endpoint. Only
keyword-only parameters are expected to be parsed from the request.
- Pydantic's strict mode is always enabled, because we don't want to
coerce input parsed from JSON into other types unnecessarily.
- Using strict mode all the time also means that we should always use
Json[int] instead of int, because it is only possible for the request
to have data of type str, and a type annotation of int will always
reject such data.
typed_endpoint's handling of ignored_parameters_unsupported is mostly
identical to that of has_request_variables.
2023-07-28 08:34:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ApiParamValidationError(JsonableError):
|
|
|
|
def __init__(self, msg: str, error_type: str) -> None:
|
|
|
|
super().__init__(msg)
|
|
|
|
self.error_type = error_type
|
2023-09-12 22:34:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ServerNotReadyError(JsonableError):
|
|
|
|
code = ErrorCode.SERVER_NOT_READY
|
|
|
|
http_status_code = 500
|