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::FmtCompactAnyhow;
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("Unexpected Error: {}", OptStacktrace(.0))]
61 Unexpected(#[from] anyhow::Error),
62}
63
64impl IntoResponse for PublicGatewayError {
65 fn into_response(self) -> Response {
66 if matches!(self, PublicGatewayError::RateLimited) {
74 debug!(target: LOG_GATEWAY, "{self}");
75 } else {
76 crit!(target: LOG_GATEWAY, "{self}");
77 }
78 let (error_message, status_code) = match &self {
79 PublicGatewayError::Internal(_) => (
80 "Gateway internal error".to_owned(),
81 StatusCode::INTERNAL_SERVER_ERROR,
82 ),
83 PublicGatewayError::FederationNotConnected(e) => {
84 (e.to_string(), StatusCode::BAD_REQUEST)
85 }
86 PublicGatewayError::ReceiveEcashError { .. } => (
87 "Failed to receive ecash".to_string(),
88 StatusCode::INTERNAL_SERVER_ERROR,
89 ),
90 PublicGatewayError::Lightning(_) => (
91 "Lightning Network operation failed".to_string(),
92 StatusCode::INTERNAL_SERVER_ERROR,
93 ),
94 PublicGatewayError::LNv1(_) => (
95 "LNv1 operation failed, please contact gateway operator".to_string(),
96 StatusCode::INTERNAL_SERVER_ERROR,
97 ),
98 PublicGatewayError::LNv2(_) => (
99 "LNv2 operation failed, please contact gateway operator".to_string(),
100 StatusCode::INTERNAL_SERVER_ERROR,
101 ),
102 PublicGatewayError::RateLimited => (
103 "Too many requests, please try again later".to_string(),
104 StatusCode::TOO_MANY_REQUESTS,
105 ),
106 PublicGatewayError::Unexpected(e) => (e.to_string(), StatusCode::BAD_REQUEST),
107 };
108
109 let error_message =
110 self.response_message(error_message, is_env_var_set(FM_DEBUG_GATEWAY_ENV));
111
112 Response::builder()
113 .status(status_code)
114 .body(error_message.into())
115 .expect("Failed to create Response")
116 }
117}
118
119impl PublicGatewayError {
120 fn response_message(&self, sanitized: String, expose_debug_details: bool) -> String {
121 if expose_debug_details && !matches!(self, PublicGatewayError::Internal(_)) {
122 self.to_string()
123 } else {
124 sanitized
125 }
126 }
127}
128
129#[cfg(test)]
130#[path = "error/tests.rs"]
131mod tests;
132
133#[derive(Debug, Error)]
136pub enum AdminGatewayError {
137 #[error("Failed to create a federation client")]
138 ClientCreationError(anyhow::Error),
139 #[error("Failed to remove a federation client: {0}")]
140 ClientRemovalError(String),
141 #[error("There was an error with the Gateway's mnemonic: {}", .0.fmt_compact_anyhow())]
142 MnemonicError(anyhow::Error),
143 #[error("Unexpected Error: {}", .0.fmt_compact_anyhow())]
144 Unexpected(#[from] anyhow::Error),
145 #[error("{}", .0)]
146 FederationNotConnected(#[from] FederationNotConnected),
147 #[error("Error configuring the gateway: {0}")]
148 GatewayConfigurationError(String),
149 #[error("Lightning error: {}", .0)]
150 Lightning(#[from] LightningRpcError),
151 #[error("Error registering federation {federation_id}")]
152 RegistrationError { federation_id: FederationId },
153 #[error("Error withdrawing funds onchain: {failure_reason}")]
154 WithdrawError { failure_reason: String },
155}
156
157impl IntoResponse for AdminGatewayError {
158 fn into_response(self) -> Response {
161 crit!(target: LOG_GATEWAY, "{self}");
162 Response::builder()
163 .status(StatusCode::INTERNAL_SERVER_ERROR)
164 .body(self.to_string().into())
165 .expect("Failed to create Response")
166 }
167}
168
169#[derive(Debug, Error)]
172pub enum LNv1Error {
173 #[error("Incoming payment error: {}", OptStacktrace(.0))]
174 IncomingPayment(String),
175 #[error(
176 "Outgoing Contract Error Reason: {message} Stack: {}",
177 OptStacktrace(error)
178 )]
179 OutgoingContract {
180 error: Box<OutgoingPaymentError>,
181 message: String,
182 },
183 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
184 OutgoingPayment(#[from] anyhow::Error),
185}
186
187#[derive(Debug, Error)]
190pub enum LNv2Error {
191 #[error("Incoming Payment Error: {}", .0)]
192 IncomingPayment(String),
193 #[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
194 OutgoingPayment(#[from] anyhow::Error),
195}
196
197#[derive(Debug, Error)]
200pub struct FederationNotConnected {
201 pub federation_id_prefix: FederationIdPrefix,
202}
203
204impl Display for FederationNotConnected {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 write!(
207 f,
208 "No federation available for prefix {}",
209 self.federation_id_prefix
210 )
211 }
212}
213
214#[derive(Debug, Error)]
216pub(crate) struct LnurlError {
217 code: StatusCode,
218 reason: anyhow::Error,
219}
220
221impl Display for LnurlError {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 write!(f, "LNURL Error: {}", self.reason,)
224 }
225}
226
227impl LnurlError {
228 pub(crate) fn internal(reason: anyhow::Error) -> Self {
229 Self {
230 code: StatusCode::INTERNAL_SERVER_ERROR,
231 reason,
232 }
233 }
234}
235
236impl IntoResponse for LnurlError {
237 fn into_response(self) -> Response<Body> {
238 let json = Json(serde_json::json!({
239 "status": "ERROR",
240 "reason": self.reason.to_string(),
241 }));
242
243 (self.code, json).into_response()
244 }
245}