miden_client/rpc/
errors.rs

1use alloc::boxed::Box;
2use alloc::string::{String, ToString};
3use core::error::Error;
4use core::num::TryFromIntError;
5
6use miden_objects::NoteError;
7use miden_objects::account::AccountId;
8use miden_objects::crypto::merkle::MerkleError;
9use miden_objects::note::NoteId;
10use miden_objects::utils::DeserializationError;
11use thiserror::Error;
12
13use super::NodeRpcClientEndpoint;
14
15// RPC ERROR
16// ================================================================================================
17
18#[derive(Debug, Error)]
19pub enum RpcError {
20    #[error("accept header validation failed: {0}")]
21    AcceptHeaderError(#[from] AcceptHeaderError),
22    #[error("rpc api response contained an update for a private account: {0}")]
23    AccountUpdateForPrivateAccountReceived(AccountId),
24    #[error("failed to connect to the api server: {0}")]
25    ConnectionError(#[source] Box<dyn Error + Send + Sync + 'static>),
26    #[error("failed to deserialize rpc data: {0}")]
27    DeserializationError(String),
28    #[error("rpc api response missing an expected field: {0}")]
29    ExpectedDataMissing(String),
30    #[error("rpc api response is invalid: {0}")]
31    InvalidResponse(String),
32    #[error("grpc request failed for {endpoint}: {error_kind}")]
33    GrpcError {
34        endpoint: NodeRpcClientEndpoint,
35        error_kind: GrpcError,
36        #[source]
37        source: Option<Box<dyn Error + Send + Sync + 'static>>,
38    },
39    #[error("note with id {0} was not found")]
40    NoteNotFound(NoteId),
41    #[error("invalid node endpoint: {0}")]
42    InvalidNodeEndpoint(String),
43}
44
45impl From<DeserializationError> for RpcError {
46    fn from(err: DeserializationError) -> Self {
47        Self::DeserializationError(err.to_string())
48    }
49}
50
51impl From<NoteError> for RpcError {
52    fn from(err: NoteError) -> Self {
53        Self::DeserializationError(err.to_string())
54    }
55}
56
57impl From<RpcConversionError> for RpcError {
58    fn from(err: RpcConversionError) -> Self {
59        Self::DeserializationError(err.to_string())
60    }
61}
62
63// RPC CONVERSION ERROR
64// ================================================================================================
65
66#[derive(Debug, Error)]
67pub enum RpcConversionError {
68    #[error("failed to deserialize: {0}")]
69    DeserializationError(#[from] DeserializationError),
70    #[error("value is not in the range 0..modulus")]
71    NotAValidFelt,
72    #[error("note error")]
73    NoteTypeError(#[from] NoteError),
74    #[error("merkle error")]
75    MerkleError(#[from] MerkleError),
76    #[error("failed to convert rpc data: {0}")]
77    InvalidField(String),
78    #[error("failed to convert int")]
79    InvalidInt(#[from] TryFromIntError),
80    #[error("field `{field_name}` expected to be present in protobuf representation of {entity}")]
81    MissingFieldInProtobufRepresentation {
82        entity: &'static str,
83        field_name: &'static str,
84    },
85}
86
87// GRPC ERROR KIND
88// ================================================================================================
89
90/// Categorizes gRPC errors based on their status codes and common patterns
91#[derive(Debug, Error)]
92pub enum GrpcError {
93    #[error("resource not found")]
94    NotFound,
95    #[error("invalid request parameters")]
96    InvalidArgument,
97    #[error("permission denied")]
98    PermissionDenied,
99    #[error("resource already exists")]
100    AlreadyExists,
101    #[error("resource exhausted or rate limited")]
102    ResourceExhausted,
103    #[error("precondition failed")]
104    FailedPrecondition,
105    #[error("operation was cancelled")]
106    Cancelled,
107    #[error("deadline exceeded")]
108    DeadlineExceeded,
109    #[error("service unavailable")]
110    Unavailable,
111    #[error("internal server error")]
112    Internal,
113    #[error("unimplemented method")]
114    Unimplemented,
115    #[error("unauthenticated request")]
116    Unauthenticated,
117    #[error("operation was aborted")]
118    Aborted,
119    #[error("operation was attempted past the valid range")]
120    OutOfRange,
121    #[error("unrecoverable data loss or corruption")]
122    DataLoss,
123    #[error("unknown error: {0}")]
124    Unknown(String),
125}
126
127impl GrpcError {
128    /// Creates a `GrpcError` from a gRPC status code following the official specification
129    /// <https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc>
130    pub fn from_code(code: i32, message: Option<String>) -> Self {
131        match code {
132            1 => Self::Cancelled,
133            2 => Self::Unknown(message.unwrap_or_default()),
134            3 => Self::InvalidArgument,
135            4 => Self::DeadlineExceeded,
136            5 => Self::NotFound,
137            6 => Self::AlreadyExists,
138            7 => Self::PermissionDenied,
139            8 => Self::ResourceExhausted,
140            9 => Self::FailedPrecondition,
141            10 => Self::Aborted,
142            11 => Self::OutOfRange,
143            12 => Self::Unimplemented,
144            13 => Self::Internal,
145            14 => Self::Unavailable,
146            15 => Self::DataLoss,
147            16 => Self::Unauthenticated,
148            _ => Self::Unknown(
149                message.unwrap_or_else(|| format!("Unknown gRPC status code: {code}")),
150            ),
151        }
152    }
153}
154
155// ACCEPT HEADER ERROR
156// ================================================================================================
157
158// TODO: Once the node returns structure error information, replace this with a more structure
159// approach. See miden-client/#1129 for more information.
160
161/// Errors that can occur during accept header validation.
162#[derive(Debug, Error)]
163pub enum AcceptHeaderError {
164    #[error("server rejected request - please check your version and network settings")]
165    NoSupportedMediaRange,
166    #[error("server rejected request - parsing error: {0}")]
167    ParsingError(String),
168}
169
170impl AcceptHeaderError {
171    /// Try to parse an accept header error from a message string
172    pub fn try_from_message(message: &str) -> Option<Self> {
173        // Check for the main compatibility error message
174        if message.contains(
175            "server does not support any of the specified application/vnd.miden content types",
176        ) {
177            return Some(Self::NoSupportedMediaRange);
178        }
179        if message.contains("genesis value failed to parse")
180            || message.contains("version value failed to parse")
181        {
182            return Some(Self::ParsingError(message.to_string()));
183        }
184        None
185    }
186}