use std::fmt::Display;
use axum::Json;
use axum::body::Body;
use axum::response::{IntoResponse, Response};
use fedimint_core::config::{FederationId, FederationIdPrefix};
use fedimint_core::crit;
use fedimint_core::envs::is_env_var_set;
use fedimint_core::fmt_utils::OptStacktrace;
use fedimint_core::util::FmtCompactAnyhow;
use fedimint_gw_client::pay::OutgoingPaymentError;
use fedimint_lightning::LightningRpcError;
use fedimint_logging::LOG_GATEWAY;
use reqwest::StatusCode;
use thiserror::Error;
use crate::envs::FM_DEBUG_GATEWAY_ENV;
#[derive(Debug, thiserror::Error)]
pub enum GatewayError {
#[error("Admin error: {0}")]
Admin(#[from] AdminGatewayError),
#[error("Public error: {0}")]
Public(#[from] PublicGatewayError),
#[error("{0}")]
Lnurl(#[from] LnurlError),
}
impl IntoResponse for GatewayError {
fn into_response(self) -> Response {
match self {
GatewayError::Admin(admin) => admin.into_response(),
GatewayError::Public(public) => public.into_response(),
GatewayError::Lnurl(lnurl) => lnurl.into_response(),
}
}
}
#[derive(Debug, Error)]
pub enum PublicGatewayError {
#[error("Lightning rpc error: {}", .0)]
Lightning(#[from] LightningRpcError),
#[error("LNv1 error: {:?}", .0)]
LNv1(#[from] LNv1Error),
#[error("LNv2 error: {:?}", .0)]
LNv2(#[from] LNv2Error),
#[error("{}", .0)]
FederationNotConnected(#[from] FederationNotConnected),
#[error("Failed to receive ecash: {failure_reason}")]
ReceiveEcashError { failure_reason: String },
#[error("Unexpected Error: {}", OptStacktrace(.0))]
Unexpected(#[from] anyhow::Error),
}
impl IntoResponse for PublicGatewayError {
fn into_response(self) -> Response {
crit!(target: LOG_GATEWAY, "{self}");
let (error_message, status_code) = match &self {
PublicGatewayError::FederationNotConnected(e) => {
(e.to_string(), StatusCode::BAD_REQUEST)
}
PublicGatewayError::ReceiveEcashError { .. } => (
"Failed to receive ecash".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::Lightning(_) => (
"Lightning Network operation failed".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::LNv1(_) => (
"LNv1 operation failed, please contact gateway operator".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::LNv2(_) => (
"LNv2 operation failed, please contact gateway operator".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::Unexpected(e) => (e.to_string(), StatusCode::BAD_REQUEST),
};
let error_message = if is_env_var_set(FM_DEBUG_GATEWAY_ENV) {
self.to_string()
} else {
error_message
};
Response::builder()
.status(status_code)
.body(error_message.into())
.expect("Failed to create Response")
}
}
#[derive(Debug, Error)]
pub enum AdminGatewayError {
#[error("Failed to create a federation client")]
ClientCreationError(anyhow::Error),
#[error("Failed to remove a federation client: {0}")]
ClientRemovalError(String),
#[error("There was an error with the Gateway's mnemonic: {}", .0.fmt_compact_anyhow())]
MnemonicError(anyhow::Error),
#[error("Unexpected Error: {}", .0.fmt_compact_anyhow())]
Unexpected(#[from] anyhow::Error),
#[error("{}", .0)]
FederationNotConnected(#[from] FederationNotConnected),
#[error("Error configuring the gateway: {0}")]
GatewayConfigurationError(String),
#[error("Lightning error: {}", .0)]
Lightning(#[from] LightningRpcError),
#[error("Error registering federation {federation_id}")]
RegistrationError { federation_id: FederationId },
#[error("Error withdrawing funds onchain: {failure_reason}")]
WithdrawError { failure_reason: String },
}
impl IntoResponse for AdminGatewayError {
fn into_response(self) -> Response {
crit!(target: LOG_GATEWAY, "{self}");
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(self.to_string().into())
.expect("Failed to create Response")
}
}
#[derive(Debug, Error)]
pub enum LNv1Error {
#[error("Incoming payment error: {}", OptStacktrace(.0))]
IncomingPayment(String),
#[error(
"Outgoing Contract Error Reason: {message} Stack: {}",
OptStacktrace(error)
)]
OutgoingContract {
error: Box<OutgoingPaymentError>,
message: String,
},
#[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
OutgoingPayment(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum LNv2Error {
#[error("Incoming Payment Error: {}", .0)]
IncomingPayment(String),
#[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
OutgoingPayment(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub struct FederationNotConnected {
pub federation_id_prefix: FederationIdPrefix,
}
impl Display for FederationNotConnected {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"No federation available for prefix {}",
self.federation_id_prefix
)
}
}
#[derive(Debug, Error)]
pub(crate) struct LnurlError {
code: StatusCode,
reason: anyhow::Error,
}
impl Display for LnurlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "LNURL Error: {}", self.reason,)
}
}
impl LnurlError {
pub(crate) fn internal(reason: anyhow::Error) -> Self {
Self {
code: StatusCode::INTERNAL_SERVER_ERROR,
reason,
}
}
}
impl IntoResponse for LnurlError {
fn into_response(self) -> Response<Body> {
let json = Json(serde_json::json!({
"status": "ERROR",
"reason": self.reason.to_string(),
}));
(self.code, json).into_response()
}
}