rspc_legacy/
error.rs

1use std::{error, fmt, sync::Arc};
2
3use serde::Serialize;
4use specta::Type;
5
6use crate::internal::jsonrpc::JsonRPCError;
7
8#[derive(thiserror::Error, Debug)]
9pub enum ExecError {
10    #[error("the requested operation '{0}' is not supported by this server")]
11    OperationNotFound(String),
12    #[error("error deserializing procedure arguments: {0}")]
13    DeserializingArgErr(serde_json::Error),
14    #[error("error serializing procedure result: {0}")]
15    SerializingResultErr(serde_json::Error),
16    #[error("error in axum extractor")]
17    AxumExtractorError,
18    #[error("invalid JSON-RPC version")]
19    InvalidJsonRpcVersion,
20    #[error("method '{0}' is not supported by this endpoint.")] // TODO: Better error message
21    UnsupportedMethod(String),
22    #[error("resolver threw error")]
23    ErrResolverError(#[from] Error),
24    #[error("error creating subscription with null id")]
25    ErrSubscriptionWithNullId,
26    #[error("error creating subscription with duplicate id")]
27    ErrSubscriptionDuplicateId,
28}
29
30impl From<ExecError> for Error {
31    fn from(v: ExecError) -> Error {
32        match v {
33            ExecError::OperationNotFound(_) => Error {
34                code: ErrorCode::NotFound,
35                message: "the requested operation is not supported by this server".to_string(),
36                cause: None,
37            },
38            ExecError::DeserializingArgErr(err) => Error {
39                code: ErrorCode::BadRequest,
40                message: "error deserializing procedure arguments".to_string(),
41                cause: Some(Arc::new(err)),
42            },
43            ExecError::SerializingResultErr(err) => Error {
44                code: ErrorCode::InternalServerError,
45                message: "error serializing procedure result".to_string(),
46                cause: Some(Arc::new(err)),
47            },
48            ExecError::AxumExtractorError => Error {
49                code: ErrorCode::BadRequest,
50                message: "Error running Axum extractors on the HTTP request".into(),
51                cause: None,
52            },
53            ExecError::InvalidJsonRpcVersion => Error {
54                code: ErrorCode::BadRequest,
55                message: "invalid JSON-RPC version".into(),
56                cause: None,
57            },
58            ExecError::ErrResolverError(err) => err,
59            ExecError::UnsupportedMethod(_) => Error {
60                code: ErrorCode::BadRequest,
61                message: "unsupported metho".into(),
62                cause: None,
63            },
64            ExecError::ErrSubscriptionWithNullId => Error {
65                code: ErrorCode::BadRequest,
66                message: "error creating subscription with null request id".into(),
67                cause: None,
68            },
69            ExecError::ErrSubscriptionDuplicateId => Error {
70                code: ErrorCode::BadRequest,
71                message: "error creating subscription with duplicate id".into(),
72                cause: None,
73            },
74        }
75    }
76}
77
78impl From<ExecError> for JsonRPCError {
79    fn from(err: ExecError) -> Self {
80        let x: Error = err.into();
81        x.into()
82    }
83}
84
85#[derive(thiserror::Error, Debug)]
86pub enum ExportError {
87    #[error("IO error exporting bindings: {0}")]
88    IOErr(#[from] std::io::Error),
89}
90
91#[derive(Debug, Clone, Serialize, Type)]
92#[allow(dead_code)]
93pub struct Error {
94    pub(crate) code: ErrorCode,
95    pub(crate) message: String,
96    #[serde(skip)]
97    pub(crate) cause: Option<Arc<dyn std::error::Error + Send + Sync>>, // We are using `Arc` instead of `Box` so we can clone the error cause `Clone` isn't dyn safe.
98}
99
100impl From<Error> for JsonRPCError {
101    fn from(err: Error) -> Self {
102        JsonRPCError {
103            code: err.code.to_status_code() as i32,
104            message: err.message,
105            data: None,
106        }
107    }
108}
109
110impl fmt::Display for Error {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(
113            f,
114            "rspc::Error {{ code: {:?}, message: {} }}",
115            self.code, self.message
116        )
117    }
118}
119
120impl error::Error for Error {
121    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
122        None
123    }
124}
125
126impl Error {
127    pub const fn new(code: ErrorCode, message: String) -> Self {
128        Error {
129            code,
130            message,
131            cause: None,
132        }
133    }
134
135    pub fn with_cause<TErr>(code: ErrorCode, message: String, cause: TErr) -> Self
136    where
137        TErr: std::error::Error + Send + Sync + 'static,
138    {
139        Self {
140            code,
141            message,
142            cause: Some(Arc::new(cause)),
143        }
144    }
145
146    #[doc(hidden)]
147    pub fn message(&self) -> &str {
148        &self.message
149    }
150
151    #[doc(hidden)]
152    pub fn cause(self) -> Option<Arc<dyn error::Error + Send + Sync>> {
153        self.cause
154    }
155}
156
157/// TODO
158#[derive(Debug, Clone, Serialize, Type, PartialEq, Eq)]
159pub enum ErrorCode {
160    BadRequest,
161    Unauthorized,
162    Forbidden,
163    NotFound,
164    Timeout,
165    Conflict,
166    PreconditionFailed,
167    PayloadTooLarge,
168    MethodNotSupported,
169    ClientClosedRequest,
170    InternalServerError,
171}
172
173impl ErrorCode {
174    pub fn to_status_code(&self) -> u16 {
175        match self {
176            ErrorCode::BadRequest => 400,
177            ErrorCode::Unauthorized => 401,
178            ErrorCode::Forbidden => 403,
179            ErrorCode::NotFound => 404,
180            ErrorCode::Timeout => 408,
181            ErrorCode::Conflict => 409,
182            ErrorCode::PreconditionFailed => 412,
183            ErrorCode::PayloadTooLarge => 413,
184            ErrorCode::MethodNotSupported => 405,
185            ErrorCode::ClientClosedRequest => 499,
186            ErrorCode::InternalServerError => 500,
187        }
188    }
189
190    pub const fn from_status_code(status_code: u16) -> Option<Self> {
191        match status_code {
192            400 => Some(ErrorCode::BadRequest),
193            401 => Some(ErrorCode::Unauthorized),
194            403 => Some(ErrorCode::Forbidden),
195            404 => Some(ErrorCode::NotFound),
196            408 => Some(ErrorCode::Timeout),
197            409 => Some(ErrorCode::Conflict),
198            412 => Some(ErrorCode::PreconditionFailed),
199            413 => Some(ErrorCode::PayloadTooLarge),
200            405 => Some(ErrorCode::MethodNotSupported),
201            499 => Some(ErrorCode::ClientClosedRequest),
202            500 => Some(ErrorCode::InternalServerError),
203            _ => None,
204        }
205    }
206}