1use std::fmt::Display;
2
3use axum::Json;
4use axum::body::Body;
5use axum::response::{IntoResponse, Response};
6use fedimint_core::config::{FederationId, FederationIdPrefix};
7use fedimint_core::crit;
8use fedimint_core::envs::is_env_var_set;
9use fedimint_core::fmt_utils::OptStacktrace;
10use fedimint_core::util::FmtCompact;
11use fedimint_gw_client::pay::OutgoingPaymentError;
12use fedimint_lightning::LightningRpcError;
13use fedimint_logging::LOG_GATEWAY;
14use reqwest::StatusCode;
15use thiserror::Error;
16use tracing::debug;
17
18use crate::envs::FM_DEBUG_GATEWAY_ENV;
19
20#[derive(Debug, thiserror::Error)]
22pub enum GatewayError {
23 #[error("Admin error: {0}")]
24 Admin(#[from] AdminGatewayError),
25 #[error("Public error: {0}")]
26 Public(#[from] PublicGatewayError),
27 #[error("{0}")]
28 Lnurl(#[from] LnurlError),
29}
30
31impl IntoResponse for GatewayError {
32 fn into_response(self) -> Response {
33 match self {
34 GatewayError::Admin(admin) => admin.into_response(),
35 GatewayError::Public(public) => public.into_response(),
36 GatewayError::Lnurl(lnurl) => lnurl.into_response(),
37 }
38 }
39}
40
41#[derive(Debug, Error)]
45pub enum PublicGatewayError {
46 #[error("Gateway invariant violation: {}", OptStacktrace(.0))]
47 Internal(anyhow::Error),
48 #[error("Lightning rpc error: {}", .0)]
49 Lightning(#[from] LightningRpcError),
50 #[error("LNv1 error: {:?}", .0)]
51 LNv1(#[from] LNv1Error),
52 #[error("LNv2 error: {:?}", .0)]
53 LNv2(#[from] LNv2Error),
54 #[error("{}", .0)]
55 FederationNotConnected(#[from] FederationNotConnected),
56 #[error("Failed to receive ecash: {failure_reason}")]
57 ReceiveEcashError { failure_reason: String },
58 #[error("Too many requests")]
59 RateLimited,
60 #[error("Receiving payments is disabled for federation {federation_id_prefix}")]
61 ReceiveDisabled {
62 federation_id_prefix: FederationIdPrefix,
63 },
64 #[error("Unexpected Error: {}", OptStacktrace(.0))]
65 Unexpected(#[from] anyhow::Error),
66}
67
68impl IntoResponse for PublicGatewayError {
69 fn into_response(self) -> Response {
70 if matches!(self, PublicGatewayError::RateLimited) {
78 debug!(target: LOG_GATEWAY, "{self}");
79 } else {
80 crit!(target: LOG_GATEWAY, "{self}");
81 }
82 let (error_message, status_code) = match &self {
83 PublicGatewayError::Internal(_) => (
84 "Gateway internal error".to_owned(),
85 StatusCode::INTERNAL_SERVER_ERROR,
86 ),
87 PublicGatewayError::FederationNotConnected(e) => {
88 (e.to_string(), StatusCode::BAD_REQUEST)
89 }
90 PublicGatewayError::ReceiveEcashError { .. } => (
91 "Failed to receive ecash".to_string(),
92 StatusCode::INTERNAL_SERVER_ERROR,
93 ),
94 PublicGatewayError::Lightning(_) => (
95 "Lightning Network operation failed".to_string(),
96 StatusCode::INTERNAL_SERVER_ERROR,
97 ),
98 PublicGatewayError::LNv1(_) => (
99 "LNv1 operation failed, please contact gateway operator".to_string(),
100 StatusCode::INTERNAL_SERVER_ERROR,
101 ),
102 PublicGatewayError::LNv2(_) => (
103 "LNv2 operation failed, please contact gateway operator".to_string(),
104 StatusCode::INTERNAL_SERVER_ERROR,
105 ),
106 PublicGatewayError::RateLimited => (
107 "Too many requests, please try again later".to_string(),
108 StatusCode::TOO_MANY_REQUESTS,
109 ),
110 PublicGatewayError::ReceiveDisabled { .. } => {
113 (self.to_string(), StatusCode::BAD_REQUEST)
114 }
115 PublicGatewayError::Unexpected(e) => (e.to_string(), StatusCode::BAD_REQUEST),
116 };
117
118 let error_message =
119 self.response_message(error_message, is_env_var_set(FM_DEBUG_GATEWAY_ENV));
120
121 Response::builder()
122 .status(status_code)
123 .body(error_message.into())
124 .expect("Failed to create Response")
125 }
126}
127
128impl PublicGatewayError {
129 fn response_message(&self, sanitized: String, expose_debug_details: bool) -> String {
130 if expose_debug_details && !matches!(self, PublicGatewayError::Internal(_)) {
131 self.to_string()
132 } else {
133 sanitized
134 }
135 }
136}
137
138#[cfg(test)]
139#[path = "error/tests.rs"]
140mod tests;
141
142#[derive(Debug, Error)]
145pub enum AdminGatewayError {
146 #[error("Failed to create a federation client")]
147 ClientCreationError(anyhow::Error),
148 #[error("Failed to remove a federation client: {0}")]
149 ClientRemovalError(String),
150 #[error("There was an error with the Gateway's mnemonic: {}", .0.fmt_compact())]
151 MnemonicError(anyhow::Error),
152 #[error("Unexpected Error: {}", .0.fmt_compact())]
153 Unexpected(#[from] anyhow::Error),
154 #[error("{}", .0)]
155 FederationNotConnected(#[from] FederationNotConnected),
156 #[error("Error configuring the gateway: {0}")]
157 GatewayConfigurationError(String),
158 #[error("Lightning error: {}", .0)]
159 Lightning(#[from] LightningRpcError),
160 #[error("Error registering federation {federation_id}")]
161 RegistrationError { federation_id: FederationId },
162 #[error("Error withdrawing funds onchain: {failure_reason}")]
163 WithdrawError { failure_reason: String },
164}
165
166impl IntoResponse for AdminGatewayError {
167 fn into_response(self) -> Response {
170 crit!(target: LOG_GATEWAY, "{self}");
171 Response::builder()
172 .status(StatusCode::INTERNAL_SERVER_ERROR)
173 .body(self.to_string().into())
174 .expect("Failed to create Response")
175 }
176}
177
178#[derive(Debug, Error)]
181pub enum LNv1Error {
182 #[error("Incoming payment error: {}", OptStacktrace(.0))]
183 IncomingPayment(String),
184 #[error(
185 "Outgoing Contract Error Reason: {message} Stack: {}",
186 OptStacktrace(error)
187 )]
188 OutgoingContract {
189 error: Box<OutgoingPaymentError>,
190 message: String,
191 },
192 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
193 OutgoingPayment(#[from] anyhow::Error),
194}
195
196#[derive(Debug, Error)]
199pub enum LNv2Error {
200 #[error("Incoming Payment Error: {}", .0)]
201 IncomingPayment(String),
202 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
203 OutgoingPayment(#[from] anyhow::Error),
204}
205
206#[derive(Debug, Error)]
209pub struct FederationNotConnected {
210 pub federation_id_prefix: FederationIdPrefix,
211}
212
213impl Display for FederationNotConnected {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 write!(
216 f,
217 "No federation available for prefix {}",
218 self.federation_id_prefix
219 )
220 }
221}
222
223#[derive(Debug, Error)]
225pub(crate) struct LnurlError {
226 code: StatusCode,
227 reason: anyhow::Error,
228}
229
230impl Display for LnurlError {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 write!(f, "LNURL Error: {}", self.reason,)
233 }
234}
235
236impl LnurlError {
237 pub(crate) fn internal(reason: anyhow::Error) -> Self {
238 Self {
239 code: StatusCode::INTERNAL_SERVER_ERROR,
240 reason,
241 }
242 }
243}
244
245impl IntoResponse for LnurlError {
246 fn into_response(self) -> Response<Body> {
247 let json = Json(serde_json::json!({
248 "status": "ERROR",
249 "reason": self.reason.to_string(),
250 }));
251
252 (self.code, json).into_response()
253 }
254}