sui_indexer_alt_jsonrpc/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::any::Any;
5use std::convert::Infallible;
6use std::fmt::Display;
7use std::sync::Arc;
8
9use axum::Json;
10use axum::response::IntoResponse;
11use jsonrpsee::types::ErrorObject;
12use jsonrpsee::types::error::INTERNAL_ERROR_CODE;
13use jsonrpsee::types::error::INVALID_PARAMS_CODE;
14use serde_json::json;
15use tower_http::catch_panic::ResponseForPanic;
16
17use crate::metrics::RpcMetrics;
18
19/// Request timed out.
20pub const TIMEOUT_ERROR_CODE: i32 = -32604;
21
22/// Like anyhow's `bail!`, but for returning an internal error.
23macro_rules! rpc_bail {
24    ($($arg:tt)*) => {
25        return Err(crate::error::internal_error!($($arg)*))
26    };
27}
28
29/// Like anyhow's `anyhow!`, but for returning an internal error.
30macro_rules! internal_error {
31    ($($arg:tt)*) => {
32        crate::error::RpcError::InternalError(anyhow::anyhow!($($arg)*))
33    };
34}
35
36pub(crate) use internal_error;
37pub(crate) use rpc_bail;
38
39/// Behaves exactly like `anyhow::Context`, but only adds context to `RpcError::InternalError`.
40pub(crate) trait InternalContext<T, E: std::error::Error> {
41    fn internal_context<C>(self, ctx: C) -> Result<T, RpcError<E>>
42    where
43        C: Display + Send + Sync + 'static;
44
45    fn with_internal_context<C, F>(self, f: F) -> Result<T, RpcError<E>>
46    where
47        C: Display + Send + Sync + 'static,
48        F: FnOnce() -> C;
49}
50
51/// This type represents three kinds of errors: Invalid Params (the user's fault), Timeouts, and
52/// Internal Errors (the service's fault). Each RpcModule is responsible for defining its own
53/// structured user errors, while timeouts and internal errors are represented with anyhow
54/// everywhere.
55///
56/// The internal error type defaults to `Infallible`, meaning there are no reasons the response
57/// might fail because of user input.
58///
59/// This representation was chosen to encourage a pattern where errors that are presented to users
60/// have a single source of truth for how they should be displayed, while internal errors encourage
61/// the addition of context (extra information to build a trace of why something went wrong).
62///
63/// User errors must be explicitly wrapped with `invalid_params` while internal errors are
64/// implicitly converted using the `?` operator. This asymmetry comes from the fact that we could
65/// populate `E` with `anyhow::Error`, which would then cause `From` impls to overlap if we
66/// supported conversion from both `E` and `anyhow::Error`.
67#[derive(thiserror::Error, Debug)]
68pub(crate) enum RpcError<E: std::error::Error = Infallible> {
69    #[error("Invalid Params: {0}")]
70    InvalidParams(E),
71
72    #[error("Timed out: {0}")]
73    Timeout(anyhow::Error),
74
75    #[error("Internal Error: {0:#}")]
76    InternalError(#[from] anyhow::Error),
77}
78
79/// Handler for panics that occur during request processing. Converts panics into JSON-RPC error
80/// responses with a 500 status code.
81#[derive(Clone)]
82pub(crate) struct PanicHandler {
83    metrics: Arc<RpcMetrics>,
84}
85
86impl PanicHandler {
87    pub fn new(metrics: Arc<RpcMetrics>) -> Self {
88        Self { metrics }
89    }
90}
91
92impl<T, E: std::error::Error> InternalContext<T, E> for Result<T, RpcError<E>> {
93    /// Wrap an internal error with additional context.
94    fn internal_context<C>(self, ctx: C) -> Result<T, RpcError<E>>
95    where
96        C: Display + Send + Sync + 'static,
97    {
98        use RpcError as E;
99        match self {
100            Err(E::InternalError(e)) => Err(E::InternalError(e.context(ctx))),
101            Err(E::Timeout(e)) => Err(E::Timeout(e.context(ctx))),
102            _ => self,
103        }
104    }
105
106    /// Wrap an internal error with additional context that is lazily evaluated only once an
107    /// internal error has occured.
108    fn with_internal_context<C, F>(self, f: F) -> Result<T, RpcError<E>>
109    where
110        C: Display + Send + Sync + 'static,
111        F: FnOnce() -> C,
112    {
113        use RpcError as E;
114        match self {
115            Err(E::InternalError(e)) => Err(E::InternalError(e.context(f()))),
116            Err(E::Timeout(e)) => Err(E::Timeout(e.context(f()))),
117            _ => self,
118        }
119    }
120}
121
122impl<E: std::error::Error> From<RpcError<E>> for ErrorObject<'static> {
123    fn from(err: RpcError<E>) -> Self {
124        use RpcError as E;
125        match &err {
126            E::InvalidParams(_) => {
127                ErrorObject::owned(INVALID_PARAMS_CODE, err.to_string(), None::<()>)
128            }
129
130            E::Timeout(_) => ErrorObject::owned(TIMEOUT_ERROR_CODE, err.to_string(), None::<()>),
131
132            E::InternalError(_) => {
133                ErrorObject::owned(INTERNAL_ERROR_CODE, err.to_string(), None::<()>)
134            }
135        }
136    }
137}
138
139/// Helper function to convert a user error into the `RpcError` type.
140pub(crate) fn invalid_params<E: std::error::Error>(err: E) -> RpcError<E> {
141    RpcError::InvalidParams(err)
142}
143
144/// Helper function to convert a jsonrpc client error into an `ErrorObject`.
145pub(crate) fn client_error_to_error_object(
146    error: jsonrpsee::core::ClientError,
147) -> ErrorObject<'static> {
148    match error {
149        // `Call` is the only error type that actually conveys meaningful error
150        // from a user calling the method. Other error variants are all more or less
151        // internal errors.
152        jsonrpsee::core::ClientError::Call(e) => e,
153        _ => ErrorObject::owned(INTERNAL_ERROR_CODE, error.to_string(), None::<()>),
154    }
155}
156
157impl ResponseForPanic for PanicHandler {
158    type ResponseBody = axum::body::Body;
159
160    fn response_for_panic(
161        &mut self,
162        err: Box<dyn Any + Send + 'static>,
163    ) -> axum::http::Response<Self::ResponseBody> {
164        self.metrics.requests_panicked.inc();
165
166        let err = if let Some(s) = err.downcast_ref::<String>() {
167            anyhow::anyhow!(s.clone()).context("Request panicked")
168        } else if let Some(s) = err.downcast_ref::<&str>() {
169            anyhow::anyhow!(s.to_string()).context("Request panicked")
170        } else {
171            anyhow::anyhow!("Request panicked")
172        };
173
174        let err: RpcError = err.into();
175        let err: ErrorObject<'static> = err.into();
176
177        let resp = json!({
178            "jsonrpc": "2.0",
179            "error": err,
180            "id": null,
181        });
182
183        Json(resp).into_response()
184    }
185}