jsoncall/
error.rs

1use std::{
2    backtrace::{Backtrace, BacktraceStatus},
3    fmt::{self, Display},
4    sync::Arc,
5};
6
7use parse_display::Display;
8use serde_json::{Map, Value};
9
10use crate::{ErrorCode, utils::downcast};
11
12use super::ErrorObject;
13
14/// Represents a JSON-RPC error that the server may send as a response.
15///
16/// This type contains both the standard JSON-RPC error fields (code, message, data)
17/// and an optional [`std::error::Error`] source for additional context. Depending on
18/// the build configuration (`cfg!(debug_assertions)`) or certain [`Session`] settings,
19/// debug builds typically include the source error to facilitate troubleshooting,
20/// while release builds omit it to prevent leaking internal information.
21///
22/// # Notes
23///
24/// - This error type can be automatically created from any [`std::error::Error`] using
25///   the `?` operator, similarly to [`anyhow`].
26/// - Serialization is intentionally **not** implemented. This prevents unintended usage
27///   of `Result<T>` where `T` might be handled by [`RequestContext::handle`] or
28///   [`RequestContext::handle_async`], avoiding subtle bugs.
29/// - For JSON-RPC errors received **from** a server, see [`SessionError`].
30///
31/// [`RequestContext::handle`]: crate::RequestContext::handle
32/// [`RequestContext::handle_async`]: crate::RequestContext::handle_async
33/// [`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html
34/// [`anyhow`]: https://docs.rs/anyhow
35/// [`Session`]: crate::Session
36/// [`SessionError`]: crate::SessionError
37#[derive(Clone, Debug)]
38pub struct Error {
39    code: ErrorCode,
40    message: Option<String>,
41    message_is_public: bool,
42    data: Option<Value>,
43    source: Option<Arc<dyn std::error::Error + Send + Sync>>,
44    backtrace: Arc<Backtrace>,
45}
46
47impl Error {
48    pub(crate) fn unsupported_version() -> Self {
49        Self::new(ErrorCode::INVALID_REQUEST).with_message("Unsupported JSON-RPC version", true)
50    }
51    pub(crate) fn missing_params() -> Self {
52        Self::new(ErrorCode::INVALID_PARAMS).with_message("`params` is required but missing", true)
53    }
54    pub(crate) fn invalid_params(source: impl std::error::Error + Send + Sync + 'static) -> Self {
55        Self::new(ErrorCode::INVALID_PARAMS).with_source(source)
56    }
57    pub(crate) fn invalid_json(source: impl std::error::Error + Send + Sync + 'static) -> Self {
58        Self::new(ErrorCode::PARSE_ERROR)
59            .with_message("Invalid JSON", true)
60            .with_source(source)
61    }
62    pub(crate) fn invalid_message() -> Self {
63        Self::new(ErrorCode::INVALID_REQUEST).with_message("Invalid JSON-RPC message", true)
64    }
65    pub(crate) fn request_id_reused() -> Self {
66        Self::new(ErrorCode::INVALID_REQUEST).with_message("Request ID reused", true)
67    }
68
69    pub fn new(code: ErrorCode) -> Self {
70        Self {
71            code,
72            message: None,
73            message_is_public: false,
74            data: None,
75            source: None,
76            backtrace: Arc::new(Backtrace::capture()),
77        }
78    }
79    pub fn with_message(self, message: impl Display, is_public: bool) -> Self {
80        Self {
81            message: Some(message.to_string()),
82            message_is_public: is_public,
83            ..self
84        }
85    }
86    pub fn with_source(self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
87        Self {
88            source: Some(Arc::new(source)),
89            ..self
90        }
91    }
92
93    pub fn backtrace(&self) -> &Backtrace {
94        &self.backtrace
95    }
96    pub fn source(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
97        Some(&**self.source.as_ref()?)
98    }
99
100    pub fn to_error_object(&self, expose_internals: bool) -> ErrorObject {
101        let mut data = self.data.clone();
102        if expose_internals && data.is_none() {
103            let mut m = Map::<String, Value>::new();
104            if let Some(source) = &self.source {
105                m.insert(
106                    "source".to_string(),
107                    Value::String(source.to_string().trim().to_string()),
108                );
109            }
110            if self.backtrace.status() == BacktraceStatus::Captured {
111                m.insert(
112                    "backtrace".to_string(),
113                    Value::String(format!("{:#?}", self.backtrace)),
114                );
115            }
116            data = Some(Value::Object(m));
117        }
118        let code = self.code;
119        let message = self.message(expose_internals);
120        ErrorObject {
121            code,
122            message,
123            data,
124        }
125    }
126    fn message(&self, expose_internals: bool) -> String {
127        let mut message = None;
128        if expose_internals || self.message_is_public {
129            message = self.message.clone();
130        }
131        if expose_internals {
132            if let Some(source) = &self.source {
133                if message.is_none() {
134                    message = source
135                        .to_string()
136                        .lines()
137                        .map(|s| s.trim().to_string())
138                        .find(|s| !s.is_empty());
139                }
140            }
141        }
142        message.unwrap_or_else(|| self.code.message().to_string())
143    }
144
145    pub(crate) fn to_session_error(&self) -> SessionError {
146        RawSessionError::Error(self.clone()).into_error()
147    }
148}
149
150impl From<ErrorCode> for Error {
151    fn from(code: ErrorCode) -> Self {
152        Self::new(code)
153    }
154}
155
156impl From<ErrorObject> for Error {
157    fn from(e: ErrorObject) -> Self {
158        Self {
159            code: e.code,
160            message: Some(e.message),
161            data: e.data,
162            message_is_public: true,
163            source: None,
164            backtrace: Arc::new(Backtrace::capture()),
165        }
166    }
167}
168
169impl<E> From<E> for Error
170where
171    E: std::error::Error + Send + Sync + 'static,
172{
173    fn from(e: E) -> Self {
174        Self::from(ErrorCode::INTERNAL_ERROR).with_source(e)
175    }
176}
177
178#[derive(Debug, Clone)]
179pub struct SessionError {
180    raw: RawSessionError,
181}
182impl SessionError {
183    pub(crate) fn shutdown() -> Self {
184        RawSessionError::Shutdown.into_error()
185    }
186    pub(crate) fn serialize_failed(source: impl std::error::Error + Send + Sync + 'static) -> Self {
187        Self::from_error(source)
188    }
189    pub(crate) fn deserialize_failed(
190        source: impl std::error::Error + Send + Sync + 'static,
191    ) -> Self {
192        Self::from_error(source)
193    }
194    pub(crate) fn request_id_overflow() -> Self {
195        Self::from_message("Request ID overflow")
196    }
197    pub(crate) fn request_id_not_found() -> Self {
198        Self::from_message("Request ID not found")
199    }
200    pub fn from_error(e: impl std::error::Error + Send + Sync + 'static) -> Self {
201        match downcast::<Self, _>(e) {
202            Ok(e) => e,
203            Err(e) => RawSessionError::Error(e.into()).into_error(),
204        }
205    }
206    pub fn from_message(message: impl Display) -> Self {
207        RawSessionError::Message(message.to_string()).into_error()
208    }
209
210    pub fn kind(&self) -> SessionErrorKind {
211        self.raw.kind()
212    }
213    pub fn error_object(&self) -> Option<&ErrorObject> {
214        if let RawSessionError::ErrorObject(e) = &self.raw {
215            Some(e)
216        } else {
217            None
218        }
219    }
220}
221impl From<ErrorObject> for SessionError {
222    fn from(e: ErrorObject) -> Self {
223        RawSessionError::ErrorObject(e).into_error()
224    }
225}
226impl Display for SessionError {
227    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228        match &self.raw {
229            RawSessionError::Shutdown => write!(f, "Session shutdown"),
230            RawSessionError::ErrorObject(e) => write!(f, "{e:#}"),
231            RawSessionError::Error(e) => write!(f, "{}", e.message(true)),
232            RawSessionError::Message(message) => Display::fmt(message, f),
233        }
234    }
235}
236impl std::error::Error for SessionError {
237    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238        match &self.raw {
239            RawSessionError::Shutdown => None,
240            RawSessionError::ErrorObject(_) => None,
241            RawSessionError::Error(e) => e.source().map(|s| s as &dyn std::error::Error),
242            RawSessionError::Message(_) => None,
243        }
244    }
245}
246
247#[derive(Debug, Copy, Clone, Display, Eq, PartialEq, Ord, PartialOrd, Hash)]
248#[non_exhaustive]
249pub enum SessionErrorKind {
250    Shutdown,
251    ErrorObject,
252    Other,
253}
254
255#[derive(Debug, Clone)]
256enum RawSessionError {
257    Shutdown,
258    ErrorObject(ErrorObject),
259    Error(Error),
260    Message(String),
261}
262impl RawSessionError {
263    fn into_error(self) -> SessionError {
264        SessionError { raw: self }
265    }
266    fn kind(&self) -> SessionErrorKind {
267        match self {
268            RawSessionError::Shutdown => SessionErrorKind::Shutdown,
269            RawSessionError::ErrorObject(_) => SessionErrorKind::ErrorObject,
270            RawSessionError::Error(_) | RawSessionError::Message(_) => SessionErrorKind::Other,
271        }
272    }
273}
274impl From<std::io::Error> for SessionError {
275    fn from(e: std::io::Error) -> Self {
276        Self::from_error(e)
277    }
278}
279
280pub type Result<T, E = Error> = std::result::Result<T, E>;
281pub type SessionResult<T> = Result<T, SessionError>;
282
283/// Returns early with a message containing implementation details
284///
285/// Specifies a message using the same arguments as [`std::format!`].
286/// This message will not be included in the [`ErrorObject`]'s message when `expose_internals` is set to `false` in [`Error::to_error_object`].
287///
288/// Equivalent to `return Err(Error::new(ErrorCode::INTERNAL_ERROR).with_message(format!(...), false))`
289///
290/// # Examples
291///
292/// ```no_run
293/// # use jsoncall::bail;
294/// # fn main() -> jsoncall::Result<()> {
295/// bail!();
296/// bail!("Invalid request");
297/// bail!("Invalid request: {}", 1);
298/// # Ok(())
299/// # }
300/// ```
301#[macro_export]
302macro_rules! bail {
303    () => {
304        return ::std::result::Result::Err($crate::Error::new($crate::ErrorCode::INTERNAL_ERROR))
305    };
306    ($fmt:literal $(,)?) => {
307        return ::std::result::Result::Err($crate::Error::new($crate::ErrorCode::INTERNAL_ERROR)
308            .with_message(::std::format!($fmt), false))
309    };
310    ($fmt:literal, $($arg:tt)*) => {
311        return ::std::result::Result::Err($crate::Error::new($crate::ErrorCode::INTERNAL_ERROR)
312            .with_message(::std::format!($fmt, $($arg)*), false))
313    };
314}
315
316/// Returns early with a message that will be exposed externally through JSON-RPC
317///
318/// Specifies an [error code](`ErrorCode`) and a message using the same format as [`std::format!`].
319///
320/// Using `_` instead of an error code will use [`ErrorCode::INTERNAL_ERROR`].
321///
322/// Equivalent to `return Err(Error::new(code).with_message(format!(...), true))`
323///
324/// # Examples
325///
326/// ```no_run
327/// # use jsoncall::{bail_public, ErrorCode};
328/// # fn main() -> jsoncall::Result<()> {
329/// bail_public!(_, "Invalid request");
330/// bail_public!(ErrorCode::INVALID_REQUEST, "Invalid request");
331/// bail_public!(ErrorCode::INVALID_REQUEST, "Invalid request: {}", 1);
332/// # Ok(())
333/// # }
334/// ```
335#[macro_export]
336macro_rules! bail_public {
337    (_, $($arg:tt)*) => {
338        bail_public!($crate::ErrorCode::INTERNAL_ERROR, $($arg)*)
339    };
340    ($code:expr) => {
341        return ::std::result::Result::Err($crate::Error::new($code))
342    };
343    ($code:expr, $fmt:literal $(,)?) => {
344        return ::std::result::Result::Err($crate::Error::new($code)
345            .with_message(::std::format!($fmt), true))
346    };
347    ($code:expr, $fmt:literal, $($arg:tt)*) => {
348        return ::std::result::Result::Err($crate::Error::new($code)
349            .with_message(::std::format!($fmt, $($arg)*), true))
350    };
351}