reown_relay_rpc/rpc/
error.rs

1use {
2    super::ErrorData,
3    std::fmt::{Debug, Display},
4};
5
6/// Provides serialization to and from string tags. This has a blanket
7/// implementation for all error types that derive [`strum::EnumString`] and
8/// [`strum::IntoStaticStr`].
9pub trait ServiceError: Sized + Debug + Display + PartialEq + Send + 'static {
10    fn from_tag(tag: &str) -> Result<Self, InvalidErrorData>;
11
12    fn tag(&self) -> &'static str;
13}
14
15impl<T> ServiceError for T
16where
17    T: for<'a> TryFrom<&'a str> + Debug + Display + PartialEq + Send + 'static,
18    for<'a> &'static str: From<&'a T>,
19{
20    fn from_tag(tag: &str) -> Result<Self, InvalidErrorData> {
21        tag.try_into().map_err(|_| InvalidErrorData)
22    }
23
24    fn tag(&self) -> &'static str {
25        self.into()
26    }
27}
28
29#[derive(Debug, thiserror::Error, strum::EnumString, strum::IntoStaticStr, PartialEq, Eq)]
30pub enum AuthError {
31    #[error("Project not found")]
32    ProjectNotFound,
33
34    #[error("Project ID not specified")]
35    ProjectIdNotSpecified,
36
37    #[error("Project inactive")]
38    ProjectInactive,
39
40    #[error("Origin not allowed")]
41    OriginNotAllowed,
42
43    #[error("Invalid JWT")]
44    InvalidJwt,
45
46    #[error("Missing JWT")]
47    MissingJwt,
48
49    #[error("Country blocked")]
50    CountryBlocked,
51}
52
53/// Request payload validation problems.
54#[derive(
55    Debug, Clone, thiserror::Error, strum::EnumString, strum::IntoStaticStr, PartialEq, Eq,
56)]
57pub enum PayloadError {
58    #[error("Invalid request method")]
59    InvalidMethod,
60
61    #[error("Invalid request parameters")]
62    InvalidParams,
63
64    #[error("Payload size exceeded")]
65    PayloadSizeExceeded,
66
67    #[error("Topic decoding failed")]
68    InvalidTopic,
69
70    #[error("Subscription ID decoding failed")]
71    InvalidSubscriptionId,
72
73    #[error("Invalid request ID")]
74    InvalidRequestId,
75
76    #[error("Invalid JSON RPC version")]
77    InvalidJsonRpcVersion,
78
79    #[error("The batch contains too many items")]
80    BatchLimitExceeded,
81
82    #[error("The batch contains no items")]
83    BatchEmpty,
84
85    #[error("Failed to deserialize request")]
86    Serialization,
87}
88
89#[derive(Debug, thiserror::Error, strum::EnumString, strum::IntoStaticStr, PartialEq, Eq)]
90pub enum InternalError {
91    #[error("Storage operation failed")]
92    StorageError,
93
94    #[error("Failed to serialize response")]
95    Serialization,
96
97    #[error("Internal error")]
98    Unknown,
99}
100
101/// Errors caught while processing the request. These are meant to be serialized
102/// into [`super::ErrorResponse`], and should be specific enough for the clients
103/// to make sense of the problem.
104#[derive(Debug, thiserror::Error, strum::IntoStaticStr, PartialEq, Eq)]
105pub enum Error<T> {
106    #[error("Auth error: {0}")]
107    Auth(#[from] AuthError),
108
109    #[error("Invalid payload: {0}")]
110    Payload(#[from] PayloadError),
111
112    #[error("Request handler error: {0}")]
113    Handler(T),
114
115    #[error("Internal error: {0}")]
116    Internal(#[from] InternalError),
117
118    #[error("Too many requests")]
119    TooManyRequests,
120}
121
122impl<T: ServiceError> Error<T> {
123    pub fn code(&self) -> i32 {
124        match self {
125            Self::Auth(_) => CODE_AUTH,
126            Self::TooManyRequests => CODE_TOO_MANY_REQUESTS,
127            Self::Payload(_) => CODE_PAYLOAD,
128            Self::Handler(_) => CODE_HANDLER,
129            Self::Internal(_) => CODE_INTERNAL,
130        }
131    }
132
133    pub fn tag(&self) -> &'static str {
134        match &self {
135            Self::Auth(err) => err.tag(),
136            Self::Payload(err) => err.tag(),
137            Self::Handler(err) => err.tag(),
138            Self::Internal(err) => err.tag(),
139            Self::TooManyRequests => self.into(),
140        }
141    }
142}
143
144pub const CODE_AUTH: i32 = 3000;
145pub const CODE_TOO_MANY_REQUESTS: i32 = 3001;
146pub const CODE_PAYLOAD: i32 = -32600;
147pub const CODE_HANDLER: i32 = -32000;
148pub const CODE_INTERNAL: i32 = -32603;
149
150#[derive(Debug, thiserror::Error)]
151#[error("Invalid error data")]
152pub struct InvalidErrorData;
153
154impl<T: ServiceError> TryFrom<ErrorData> for Error<T> {
155    type Error = InvalidErrorData;
156
157    fn try_from(err: ErrorData) -> Result<Self, Self::Error> {
158        let tag = &err.data;
159
160        let err = match err.code {
161            CODE_AUTH => Error::Auth(try_parse_error(tag)?),
162            CODE_TOO_MANY_REQUESTS => Error::TooManyRequests,
163            CODE_PAYLOAD => Error::Payload(try_parse_error(tag)?),
164            CODE_HANDLER => Error::Handler(try_parse_error(tag)?),
165            CODE_INTERNAL => Error::Internal(try_parse_error(tag)?),
166            _ => return Err(InvalidErrorData),
167        };
168
169        Ok(err)
170    }
171}
172
173#[inline]
174fn try_parse_error<T: ServiceError>(tag: &Option<String>) -> Result<T, InvalidErrorData> {
175    tag.as_deref().ok_or(InvalidErrorData).map(T::from_tag)?
176}
177
178impl<T: ServiceError> From<Error<T>> for ErrorData {
179    fn from(err: Error<T>) -> Self {
180        Self {
181            code: err.code(),
182            message: err.to_string(),
183            data: Some(err.tag().to_owned()),
184        }
185    }
186}