errors.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # Copyright (c) 2022 Tulir Asokan
  2. #
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. from __future__ import annotations
  7. from typing import Any
  8. class RPCError(Exception):
  9. pass
  10. class UnexpectedError(RPCError):
  11. pass
  12. class UnexpectedResponse(RPCError):
  13. def __init__(self, resp_type: str, data: Any) -> None:
  14. super().__init__(f"Got unexpected response type {resp_type}")
  15. self.resp_type = resp_type
  16. self.data = data
  17. class NotConnected(RPCError):
  18. pass
  19. class ResponseError(RPCError):
  20. def __init__(
  21. self,
  22. data: dict[str, Any],
  23. error_type: str | None = None,
  24. message_override: str | None = None,
  25. ) -> None:
  26. self.data = data
  27. msg = message_override or data["message"]
  28. if error_type:
  29. msg = f"{error_type}: {msg}"
  30. super().__init__(msg)
  31. class TimeoutException(ResponseError):
  32. pass
  33. class UnknownIdentityKey(ResponseError):
  34. pass
  35. class CaptchaRequiredError(ResponseError):
  36. pass
  37. class AuthorizationFailedError(ResponseError):
  38. pass
  39. class ScanTimeoutError(ResponseError):
  40. pass
  41. class UserAlreadyExistsError(ResponseError):
  42. def __init__(self, data: dict[str, Any]) -> None:
  43. super().__init__(data, message_override="You're already logged in")
  44. class OwnProfileKeyDoesNotExistError(ResponseError):
  45. def __init__(self, data: dict[str, Any]) -> None:
  46. super().__init__(
  47. data,
  48. message_override=(
  49. "Cannot find own profile key. Please make sure you have a Signal profile name set."
  50. ),
  51. )
  52. class RequestValidationFailure(ResponseError):
  53. def __init__(self, data: dict[str, Any]) -> None:
  54. results = data["validationResults"]
  55. result_str = ", ".join(results) if isinstance(results, list) else str(results)
  56. super().__init__(data, message_override=result_str)
  57. class InternalError(ResponseError):
  58. """
  59. If you find yourself using this, please file an issue against signald. We want to make
  60. explicit error types at the protocol for anything a client might normally expect.
  61. """
  62. def __init__(self, data: dict[str, Any]) -> None:
  63. exceptions = data.get("exceptions", [])
  64. self.exceptions = exceptions
  65. message = data.get("message")
  66. super().__init__(data, error_type=", ".join(exceptions), message_override=message)
  67. class AttachmentTooLargeError(ResponseError):
  68. def __init__(self, data: dict[str, Any]) -> None:
  69. self.filename = data.get("filename", "")
  70. super().__init__(data, message_override="File is over the 100MB limit.")
  71. class UnregisteredUserError(ResponseError):
  72. pass
  73. class ProfileUnavailableError(ResponseError):
  74. pass
  75. class NoSuchAccountError(ResponseError):
  76. pass
  77. response_error_types = {
  78. "invalid_request": RequestValidationFailure,
  79. "TimeoutException": TimeoutException,
  80. "UserAlreadyExists": UserAlreadyExistsError,
  81. "RequestValidationFailure": RequestValidationFailure,
  82. "UnknownIdentityKey": UnknownIdentityKey,
  83. "CaptchaRequiredError": CaptchaRequiredError,
  84. "InternalError": InternalError,
  85. "AttachmentTooLargeError": AttachmentTooLargeError,
  86. "AuthorizationFailedError": AuthorizationFailedError,
  87. "ScanTimeoutError": ScanTimeoutError,
  88. "OwnProfileKeyDoesNotExistError": OwnProfileKeyDoesNotExistError,
  89. "UnregisteredUserError": UnregisteredUserError,
  90. "ProfileUnavailableError": ProfileUnavailableError,
  91. "NoSuchAccountError": NoSuchAccountError,
  92. # TODO add rest from https://gitlab.com/signald/signald/-/tree/main/src/main/java/io/finn/signald/clientprotocol/v1/exceptions
  93. }
  94. def make_response_error(data: dict[str, Any]) -> ResponseError:
  95. error_data = data["error"]
  96. if isinstance(error_data, str):
  97. error_data = {"message": error_data}
  98. elif not isinstance(error_data, dict):
  99. error_data = {"message": str(error_data)}
  100. if "message" not in error_data:
  101. error_data["message"] = "no message, see signald logs"
  102. error_type = data.get("error_type")
  103. try:
  104. error_class = response_error_types[error_type]
  105. except KeyError:
  106. return ResponseError(error_data, error_type=error_type)
  107. else:
  108. return error_class(error_data)