Skip to main content

iota_sdk/
json_rpc_error.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use iota_json_rpc_api::error_object_from_rpc;
6pub use iota_json_rpc_api::{
7    TRANSACTION_EXECUTION_CLIENT_ERROR_CODE, TRANSACTION_NOT_FOUND_ERROR_CODE, TRANSIENT_ERROR_CODE,
8};
9use jsonrpsee::types::error::UNKNOWN_ERROR_CODE;
10use thiserror::Error;
11
12#[derive(Error, Debug, Clone)]
13pub struct Error {
14    pub code: i32,
15    pub message: String,
16    // TODO: as this SDK is specialized for the IOTA JSON RPC implementation, we should define
17    // structured representation for the data field if applicable
18    pub data: Option<serde_json::Value>,
19}
20
21impl std::fmt::Display for Error {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "code: '{}', message: '{}'", self.code, self.message)
24    }
25}
26
27impl Error {
28    pub fn is_call_error(&self) -> bool {
29        self.code != UNKNOWN_ERROR_CODE
30    }
31
32    pub fn is_client_error(&self) -> bool {
33        use jsonrpsee::types::error::{
34            BATCHES_NOT_SUPPORTED_CODE, INVALID_PARAMS_CODE, INVALID_REQUEST_CODE,
35            METHOD_NOT_FOUND_CODE, OVERSIZED_REQUEST_CODE, PARSE_ERROR_CODE,
36        };
37        matches!(
38            self.code,
39            PARSE_ERROR_CODE
40                | OVERSIZED_REQUEST_CODE
41                | INVALID_PARAMS_CODE
42                | INVALID_REQUEST_CODE
43                | METHOD_NOT_FOUND_CODE
44                | BATCHES_NOT_SUPPORTED_CODE
45                | TRANSACTION_EXECUTION_CLIENT_ERROR_CODE
46        )
47    }
48
49    pub fn is_execution_error(&self) -> bool {
50        self.code == TRANSACTION_EXECUTION_CLIENT_ERROR_CODE
51    }
52
53    pub fn is_transient_error(&self) -> bool {
54        self.code == TRANSIENT_ERROR_CODE
55    }
56
57    pub fn is_transaction_not_found(&self) -> bool {
58        self.code == TRANSACTION_NOT_FOUND_ERROR_CODE
59    }
60}
61
62impl From<jsonrpsee::core::ClientError> for Error {
63    fn from(err: jsonrpsee::core::ClientError) -> Self {
64        // The following code converts any variant that is not Error::Call into
65        // an ErrorObject with UNKNOWN_ERROR_CODE
66        let error_object_owned = error_object_from_rpc(err);
67        Error {
68            code: error_object_owned.code(),
69            message: error_object_owned.message().to_string(),
70            data: error_object_owned
71                .data()
72                .map(|v| serde_json::from_str(v.get()).expect("raw json is always valid")),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::{Error, TRANSACTION_NOT_FOUND_ERROR_CODE};
80
81    #[test]
82    fn transaction_not_found_has_dedicated_classification() {
83        let error = Error {
84            code: TRANSACTION_NOT_FOUND_ERROR_CODE,
85            message: "Transaction not found".to_string(),
86            data: None,
87        };
88
89        assert!(error.is_call_error());
90        assert!(!error.is_client_error());
91        assert!(error.is_transaction_not_found());
92        assert!(!error.is_execution_error());
93        assert!(!error.is_transient_error());
94    }
95}