sui_indexer_alt_jsonrpc/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{convert::Infallible, fmt::Display};
5
6use jsonrpsee::types::{
7    ErrorObject,
8    error::{INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE},
9};
10
11/// Request timed out.
12pub const TIMEOUT_ERROR_CODE: i32 = -32604;
13
14/// Like anyhow's `bail!`, but for returning an internal error.
15macro_rules! rpc_bail {
16    ($($arg:tt)*) => {
17        return Err(crate::error::internal_error!($($arg)*))
18    };
19}
20
21/// Like anyhow's `anyhow!`, but for returning an internal error.
22macro_rules! internal_error {
23    ($($arg:tt)*) => {
24        crate::error::RpcError::InternalError(anyhow::anyhow!($($arg)*))
25    };
26}
27
28pub(crate) use internal_error;
29pub(crate) use rpc_bail;
30
31/// Behaves exactly like `anyhow::Context`, but only adds context to `RpcError::InternalError`.
32pub(crate) trait InternalContext<T, E: std::error::Error> {
33    fn internal_context<C>(self, ctx: C) -> Result<T, RpcError<E>>
34    where
35        C: Display + Send + Sync + 'static;
36
37    fn with_internal_context<C, F>(self, f: F) -> Result<T, RpcError<E>>
38    where
39        C: Display + Send + Sync + 'static,
40        F: FnOnce() -> C;
41}
42
43/// This type represents three kinds of errors: Invalid Params (the user's fault), Timeouts, and
44/// Internal Errors (the service's fault). Each RpcModule is responsible for defining its own
45/// structured user errors, while timeouts and internal errors are represented with anyhow
46/// everywhere.
47///
48/// The internal error type defaults to `Infallible`, meaning there are no reasons the response
49/// might fail because of user input.
50///
51/// This representation was chosen to encourage a pattern where errors that are presented to users
52/// have a single source of truth for how they should be displayed, while internal errors encourage
53/// the addition of context (extra information to build a trace of why something went wrong).
54///
55/// User errors must be explicitly wrapped with `invalid_params` while internal errors are
56/// implicitly converted using the `?` operator. This asymmetry comes from the fact that we could
57/// populate `E` with `anyhow::Error`, which would then cause `From` impls to overlap if we
58/// supported conversion from both `E` and `anyhow::Error`.
59#[derive(thiserror::Error, Debug)]
60pub(crate) enum RpcError<E: std::error::Error = Infallible> {
61    #[error("Invalid Params: {0}")]
62    InvalidParams(E),
63
64    #[error("Timed out: {0}")]
65    Timeout(anyhow::Error),
66
67    #[error("Internal Error: {0:#}")]
68    InternalError(#[from] anyhow::Error),
69}
70
71impl<T, E: std::error::Error> InternalContext<T, E> for Result<T, RpcError<E>> {
72    /// Wrap an internal error with additional context.
73    fn internal_context<C>(self, ctx: C) -> Result<T, RpcError<E>>
74    where
75        C: Display + Send + Sync + 'static,
76    {
77        use RpcError as E;
78        match self {
79            Err(E::InternalError(e)) => Err(E::InternalError(e.context(ctx))),
80            Err(E::Timeout(e)) => Err(E::Timeout(e.context(ctx))),
81            _ => self,
82        }
83    }
84
85    /// Wrap an internal error with additional context that is lazily evaluated only once an
86    /// internal error has occured.
87    fn with_internal_context<C, F>(self, f: F) -> Result<T, RpcError<E>>
88    where
89        C: Display + Send + Sync + 'static,
90        F: FnOnce() -> C,
91    {
92        use RpcError as E;
93        match self {
94            Err(E::InternalError(e)) => Err(E::InternalError(e.context(f()))),
95            Err(E::Timeout(e)) => Err(E::Timeout(e.context(f()))),
96            _ => self,
97        }
98    }
99}
100
101impl<E: std::error::Error> From<RpcError<E>> for ErrorObject<'static> {
102    fn from(err: RpcError<E>) -> Self {
103        use RpcError as E;
104        match &err {
105            E::InvalidParams(_) => {
106                ErrorObject::owned(INVALID_PARAMS_CODE, err.to_string(), None::<()>)
107            }
108
109            E::Timeout(_) => ErrorObject::owned(TIMEOUT_ERROR_CODE, err.to_string(), None::<()>),
110
111            E::InternalError(_) => {
112                ErrorObject::owned(INTERNAL_ERROR_CODE, err.to_string(), None::<()>)
113            }
114        }
115    }
116}
117
118/// Helper function to convert a user error into the `RpcError` type.
119pub(crate) fn invalid_params<E: std::error::Error>(err: E) -> RpcError<E> {
120    RpcError::InvalidParams(err)
121}
122
123/// Helper function to convert a jsonrpc client error into an `ErrorObject`.
124pub(crate) fn client_error_to_error_object(
125    error: jsonrpsee::core::ClientError,
126) -> ErrorObject<'static> {
127    match error {
128        // `Call` is the only error type that actually conveys meaningful error
129        // from a user calling the method. Other error variants are all more or less
130        // internal errors.
131        jsonrpsee::core::ClientError::Call(e) => e,
132        _ => ErrorObject::owned(INTERNAL_ERROR_CODE, error.to_string(), None::<()>),
133    }
134}