iota_rosetta/
errors.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::Debug;
6
7use axum::{
8    Json,
9    extract::rejection::JsonRejection,
10    http::StatusCode,
11    response::{IntoResponse, Response},
12};
13use fastcrypto::error::FastCryptoError;
14use iota_types::error::IotaError;
15use serde::{Serialize, Serializer};
16use serde_json::{Value, json};
17use strum::{EnumProperty, IntoEnumIterator};
18use strum_macros::{Display, EnumDiscriminants, EnumIter};
19use thiserror::Error;
20use typed_store::TypedStoreError;
21
22use crate::types::{BlockHash, IotaEnv, OperationType, PublicKey};
23
24/// IOTA Rosetta specific error types.
25/// This contains all the errors returns by the iota-rosetta server.
26#[derive(Debug, Error, EnumDiscriminants, EnumProperty)]
27#[strum_discriminants(
28    name(ErrorType),
29    derive(Display, EnumIter),
30    strum(serialize_all = "kebab-case")
31)]
32pub enum Error {
33    #[error("Unsupported blockchain: {0}")]
34    UnsupportedBlockchain(String),
35    #[error("Unsupported network: {0:?}")]
36    UnsupportedNetwork(IotaEnv),
37    #[error("Invalid input: {0}")]
38    InvalidInput(String),
39    #[error("Missing input: {0}")]
40    MissingInput(String),
41    #[error("Missing metadata")]
42    MissingMetadata,
43    #[error("{0}")]
44    MalformedOperation(String),
45    #[error("Unsupported operation: {0:?}")]
46    UnsupportedOperation(OperationType),
47    #[error("Data error: {0}")]
48    Data(String),
49    #[error("Block not found, index: {index:?}, hash: {hash:?}")]
50    BlockNotFound {
51        index: Option<u64>,
52        hash: Option<BlockHash>,
53    },
54    #[error("Public key deserialization error: {0:?}")]
55    PublicKeyDeserialization(PublicKey),
56
57    #[error("Error executing transaction: {0}")]
58    TransactionExecution(String),
59
60    #[error("{0}")]
61    TransactionDryRun(String),
62
63    #[error(transparent)]
64    Internal(#[from] anyhow::Error),
65    #[error(transparent)]
66    BCSSerialization(#[from] bcs::Error),
67    #[error(transparent)]
68    Crypto(#[from] FastCryptoError),
69    #[error(transparent)]
70    Iota(#[from] IotaError),
71    #[error(transparent)]
72    IotaRpc(#[from] iota_sdk::error::Error),
73    #[error(transparent)]
74    Encoding(#[from] eyre::Report),
75    #[error(transparent)]
76    DB(#[from] TypedStoreError),
77    #[error(transparent)]
78    JsonExtractorRejection(#[from] JsonRejection),
79
80    #[error("Retries exhausted while getting balance. try again.")]
81    #[strum(props(retriable = "true"))]
82    RetryExhausted(String),
83}
84
85impl Serialize for ErrorType {
86    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: Serializer,
89    {
90        self.json().serialize(serializer)
91    }
92}
93
94trait CustomProperties {
95    fn retriable(&self) -> bool;
96}
97
98impl CustomProperties for Error {
99    fn retriable(&self) -> bool {
100        matches!(self.get_str("retriable"), Some("true"))
101    }
102}
103
104impl ErrorType {
105    fn json(&self) -> Value {
106        let retriable = false;
107        // Safe to unwrap
108        let error_code = ErrorType::iter().position(|e| &e == self).unwrap();
109        let message = format!("{self}").replace('-', " ");
110        let message = message[0..1].to_uppercase() + &message[1..];
111
112        json![{
113            "code": error_code,
114            "message": message,
115            "retriable": retriable,
116        }]
117    }
118}
119
120impl Serialize for Error {
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: Serializer,
124    {
125        let type_: ErrorType = self.into();
126        let mut json = type_.json();
127        // Safe to unwrap, we know ErrorType must be an object.
128        let error = json.as_object_mut().unwrap();
129        error.insert("details".into(), json!({ "error": format!("{self}") }));
130        error.insert("retriable".into(), json!(self.retriable()));
131        error.serialize(serializer)
132    }
133}
134
135impl IntoResponse for Error {
136    fn into_response(self) -> Response {
137        (StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
138    }
139}