1use axum::http::StatusCode;
6
7pub type Result<T, E = RestError> = std::result::Result<T, E>;
8
9pub struct RestError {
10 status: StatusCode,
11 message: Option<String>,
12}
13
14impl RestError {
15 pub fn new<T: Into<String>>(status: StatusCode, message: T) -> Self {
16 Self {
17 status,
18 message: Some(message.into()),
19 }
20 }
21}
22
23impl axum::response::IntoResponse for RestError {
25 fn into_response(self) -> axum::response::Response {
26 match self.message {
27 Some(message) => (self.status, message).into_response(),
28 None => self.status.into_response(),
29 }
30 }
31}
32
33impl From<iota_types::storage::error::Error> for RestError {
34 fn from(value: iota_types::storage::error::Error) -> Self {
35 Self {
36 status: StatusCode::INTERNAL_SERVER_ERROR,
37 message: Some(value.to_string()),
38 }
39 }
40}
41
42impl From<anyhow::Error> for RestError {
43 fn from(value: anyhow::Error) -> Self {
44 Self {
45 status: StatusCode::INTERNAL_SERVER_ERROR,
46 message: Some(value.to_string()),
47 }
48 }
49}
50
51impl From<iota_types::iota_sdk2_conversions::SdkTypeConversionError> for RestError {
52 fn from(value: iota_types::iota_sdk2_conversions::SdkTypeConversionError) -> Self {
53 Self {
54 status: StatusCode::INTERNAL_SERVER_ERROR,
55 message: Some(value.to_string()),
56 }
57 }
58}
59
60impl From<bcs::Error> for RestError {
61 fn from(value: bcs::Error) -> Self {
62 Self {
63 status: StatusCode::INTERNAL_SERVER_ERROR,
64 message: Some(value.to_string()),
65 }
66 }
67}
68
69impl From<iota_types::quorum_driver_types::QuorumDriverError> for RestError {
70 fn from(error: iota_types::quorum_driver_types::QuorumDriverError) -> Self {
71 use iota_types::{error::IotaError, quorum_driver_types::QuorumDriverError::*};
72 use itertools::Itertools;
73
74 match error {
75 InvalidUserSignature(err) => {
76 let message = {
77 let err = match err {
78 IotaError::UserInput { error } => error.to_string(),
79 _ => err.to_string(),
80 };
81 format!("Invalid user signature: {err}")
82 };
83
84 RestError::new(StatusCode::BAD_REQUEST, message)
85 }
86 QuorumDriverInternal(err) => {
87 RestError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
88 }
89 ObjectsDoubleUsed { conflicting_txes } => {
90 let new_map = conflicting_txes
91 .into_iter()
92 .map(|(digest, (pairs, _))| {
93 (
94 digest,
95 pairs.into_iter().map(|(_, obj_ref)| obj_ref).collect(),
96 )
97 })
98 .collect::<std::collections::BTreeMap<_, Vec<_>>>();
99
100 let message = format!(
101 "Failed to sign transaction by a quorum of validators because of locked objects. Conflicting Transactions:\n{new_map:#?}",
102 );
103
104 RestError::new(StatusCode::CONFLICT, message)
105 }
106 TimeoutBeforeFinality | FailedWithTransientErrorAfterMaximumAttempts { .. } => {
107 RestError::new(
109 StatusCode::SERVICE_UNAVAILABLE,
110 "timed-out before finality could be reached",
111 )
112 }
113 NonRecoverableTransactionError { errors } => {
114 let new_errors: Vec<String> = errors
115 .into_iter()
116 .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
118 .filter_map(|(err, _, _)| {
119 match &err {
120 IotaError::UserInput { error } => Some(error.to_string()),
131 _ => {
132 if err.is_retryable().0 {
133 None
134 } else {
135 Some(err.to_string())
136 }
137 }
138 }
139 })
140 .collect();
141
142 assert!(
143 !new_errors.is_empty(),
144 "NonRecoverableTransactionError should have at least one non-retryable error"
145 );
146
147 let error_list = new_errors.join(", ");
148 let error_msg = format!(
149 "Transaction execution failed due to issues with transaction inputs, please review the errors and try again: {}.",
150 error_list
151 );
152
153 RestError::new(StatusCode::BAD_REQUEST, error_msg)
154 }
155 TxAlreadyFinalizedWithDifferentUserSignatures => RestError::new(
156 StatusCode::CONFLICT,
157 "The transaction is already finalized but with different user signatures",
158 ),
159 SystemOverload { .. } | SystemOverloadRetryAfter { .. } => {
160 RestError::new(StatusCode::SERVICE_UNAVAILABLE, "system is overloaded")
162 }
163 }
164 }
165}