1use 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#[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 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 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}