Skip to main content

mostro_core/
error.rs

1//! Error taxonomy used across the crate.
2//!
3//! Errors surfaced to clients are modelled as [`MostroError`], which is split
4//! into two branches:
5//!
6//! * [`MostroError::MostroCantDo`] — a "soft" error: the request was
7//!   well-formed but the server refuses to perform the action (e.g. the order
8//!   is not in the right state). Clients should surface the inner
9//!   [`CantDoReason`] to the user.
10//! * [`MostroError::MostroInternalErr`] — a "hard" error: something went
11//!   wrong while processing the request (database failure, Nostr relay
12//!   issue, malformed invoice, etc.). The inner [`ServiceError`] carries the
13//!   diagnostic detail.
14//!
15//! Both inner enums implement [`Display`](std::fmt::Display) with
16//! human-readable messages suited for logging.
17
18use crate::prelude::*;
19
20/// Machine-readable reasons carried by a `CantDo` response.
21///
22/// Serialized in `snake_case` so clients can pattern-match on the value
23/// transported in [`crate::message::Payload::CantDo`].
24#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
25#[serde(rename_all = "snake_case")]
26pub enum CantDoReason {
27    /// The provided signature is invalid or missing.
28    InvalidSignature,
29    /// The specified trade index does not exist or is invalid.
30    InvalidTradeIndex,
31    /// The provided amount is invalid or out of acceptable range.
32    InvalidAmount,
33    /// The provided invoice is malformed or expired.
34    InvalidInvoice,
35    /// The payment request is invalid or cannot be processed.
36    InvalidPaymentRequest,
37    /// The specified peer is invalid or not found.
38    InvalidPeer,
39    /// The rating value is invalid or out of range.
40    InvalidRating,
41    /// The text message is invalid or contains prohibited content.
42    InvalidTextMessage,
43    /// The order kind is invalid.
44    InvalidOrderKind,
45    /// The order status is invalid.
46    InvalidOrderStatus,
47    /// The provided public key is invalid.
48    InvalidPubkey,
49    /// One or more request parameters are invalid.
50    InvalidParameters,
51    /// The targeted order has already been canceled.
52    OrderAlreadyCanceled,
53    /// User creation failed on the server side.
54    CantCreateUser,
55    /// The caller tried to operate on an order that does not belong to them.
56    IsNotYourOrder,
57    /// The requested action is not allowed in the order's current status.
58    NotAllowedByStatus,
59    /// The fiat amount is outside the allowed range for this order.
60    OutOfRangeFiatAmount,
61    /// The sats amount is outside the allowed range for this order.
62    OutOfRangeSatsAmount,
63    /// The caller tried to operate on a dispute that does not belong to them.
64    IsNotYourDispute,
65    /// A solver is being notified that an admin has taken over their dispute.
66    DisputeTakenByAdmin,
67    /// The caller is authenticated but lacks the permission for this action.
68    NotAuthorized,
69    /// A dispute could not be created (e.g. order not in a disputable state).
70    DisputeCreationError,
71    /// Generic "resource not found" error.
72    NotFound,
73    /// The dispute is in an invalid state for the requested action.
74    InvalidDisputeStatus,
75    /// The requested action is invalid.
76    InvalidAction,
77    /// The caller already has a pending order and cannot create another.
78    PendingOrderExists,
79    /// The fiat currency code is not accepted by this Mostro node.
80    InvalidFiatCurrency,
81    /// The caller is being rate-limited.
82    TooManyRequests,
83}
84
85/// Internal errors raised by services behind the Mostro API.
86///
87/// Unlike [`CantDoReason`], values of this enum are not expected to be
88/// forwarded verbatim to end users; they are meant for logs, telemetry and
89/// other server-to-server diagnostics.
90#[derive(Debug, PartialEq, Eq)]
91pub enum ServiceError {
92    /// Wraps an error returned by `nostr_sdk`.
93    NostrError(String),
94    /// The invoice string could not be parsed as a valid BOLT-11 invoice.
95    ParsingInvoiceError,
96    /// A numeric value could not be parsed.
97    ParsingNumberError,
98    /// The invoice has expired.
99    InvoiceExpiredError,
100    /// The invoice is otherwise invalid.
101    InvoiceInvalidError,
102    /// The invoice expiration time is below the minimum required.
103    MinExpirationTimeError,
104    /// The invoice amount is below the minimum allowed.
105    MinAmountError,
106    /// The invoice amount does not match the expected value.
107    WrongAmountError,
108    /// The price API did not answer in time.
109    NoAPIResponse,
110    /// The requested currency is not listed by the exchange API.
111    NoCurrency,
112    /// The exchange API returned a response that could not be parsed.
113    MalformedAPIRes,
114    /// Amount value is negative where only positives are allowed.
115    NegativeAmount,
116    /// A Lightning Address could not be parsed.
117    LnAddressParseError,
118    /// A Lightning Address payment was attempted with a wrong amount.
119    LnAddressWrongAmount,
120    /// A Lightning payment failed; the inner string carries the reason.
121    LnPaymentError(String),
122    /// Communication with the Lightning node failed.
123    LnNodeError(String),
124    /// Order id was not found in the database.
125    InvalidOrderId,
126    /// Database access failed; the inner string carries the detail.
127    DbAccessError(String),
128    /// The provided public key is invalid.
129    InvalidPubkey,
130    /// Hold-invoice operation failed; the inner string carries the detail.
131    HoldInvoiceError(String),
132    /// Could not update the order status in the database.
133    UpdateOrderStatusError,
134    /// The order status is invalid.
135    InvalidOrderStatus,
136    /// The order kind is invalid.
137    InvalidOrderKind,
138    /// A dispute already exists for this order.
139    DisputeAlreadyExists,
140    /// Could not publish the dispute Nostr event.
141    DisputeEventError,
142    /// The rating message itself is invalid.
143    InvalidRating,
144    /// The rating value is outside the accepted range.
145    InvalidRatingValue,
146    /// Failed to serialize or deserialize a [`crate::message::Message`].
147    MessageSerializationError,
148    /// The dispute id is invalid or unknown.
149    InvalidDisputeId,
150    /// The dispute status is invalid.
151    InvalidDisputeStatus,
152    /// The payload does not match the action.
153    InvalidPayload,
154    /// Any other unexpected error; inner string carries the detail.
155    UnexpectedError(String),
156    /// An environment variable could not be read or parsed.
157    EnvVarError(String),
158    /// Underlying I/O error.
159    IOError(String),
160    /// NIP-44/NIP-59 encryption failed.
161    EncryptionError(String),
162    /// NIP-44/NIP-59 decryption failed.
163    DecryptionError(String),
164}
165
166/// Top-level error type returned by the Mostro API surface.
167///
168/// Most public functions in this crate return `Result<T, MostroError>`.
169/// Match on the variants to distinguish between user-actionable "can't do"
170/// responses and internal service errors.
171#[derive(Debug, PartialEq, Eq)]
172pub enum MostroError {
173    /// An internal service-level error; diagnostic only.
174    MostroInternalErr(ServiceError),
175    /// A structured "can't do" response to surface to the user.
176    MostroCantDo(CantDoReason),
177}
178
179impl std::error::Error for MostroError {}
180
181impl std::fmt::Display for MostroError {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        match self {
184            MostroError::MostroInternalErr(m) => write!(f, "Error caused by {}", m),
185            MostroError::MostroCantDo(m) => write!(f, "Sending cantDo message to user for {:?}", m),
186        }
187    }
188}
189
190impl std::fmt::Display for ServiceError {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        match self {
193            ServiceError::ParsingInvoiceError => write!(f, "Incorrect invoice"),
194            ServiceError::ParsingNumberError => write!(f, "Error parsing the number"),
195            ServiceError::InvoiceExpiredError => write!(f, "Invoice has expired"),
196            ServiceError::MinExpirationTimeError => write!(f, "Minimal expiration time on invoice"),
197            ServiceError::InvoiceInvalidError => write!(f, "Invoice is invalid"),
198            ServiceError::MinAmountError => write!(f, "Minimal payment amount"),
199            ServiceError::WrongAmountError => write!(f, "The amount on this invoice is wrong"),
200            ServiceError::NoAPIResponse => write!(f, "Price API not answered - retry"),
201            ServiceError::NoCurrency => write!(f, "Currency requested is not present in the exchange list, please specify a fixed rate"),
202            ServiceError::MalformedAPIRes => write!(f, "Malformed answer from exchange quoting request"),
203            ServiceError::NegativeAmount => write!(f, "Negative amount is not valid"),
204            ServiceError::LnAddressWrongAmount => write!(f, "Ln address need amount of 0 sats - please check your order"),
205            ServiceError::LnAddressParseError  => write!(f, "Ln address parsing error - please check your address"),
206            ServiceError::LnPaymentError(e) => write!(f, "Lightning payment failure cause: {}",e),
207            ServiceError::LnNodeError(e) => write!(f, "Lightning node connection failure caused by: {}",e),
208            ServiceError::InvalidOrderId => write!(f, "Order id not present in database"),
209            ServiceError::InvalidPubkey => write!(f, "Invalid pubkey"),
210            ServiceError::DbAccessError(e) => write!(f, "Error in database access: {}",e),
211            ServiceError::HoldInvoiceError(e) => write!(f, "Error holding invoice: {}",e),
212            ServiceError::UpdateOrderStatusError => write!(f, "Error updating order status"),
213            ServiceError::InvalidOrderStatus => write!(f, "Invalid order status"),
214            ServiceError::InvalidOrderKind => write!(f, "Invalid order kind"),
215            ServiceError::DisputeAlreadyExists => write!(f, "Dispute already exists"),
216            ServiceError::DisputeEventError => write!(f, "Error publishing dispute event"),
217            ServiceError::NostrError(e) => write!(f, "Error in nostr: {}",e),
218            ServiceError::InvalidRating => write!(f, "Invalid rating message"),
219            ServiceError::InvalidRatingValue => write!(f, "Invalid rating value"),
220            ServiceError::MessageSerializationError => write!(f, "Error serializing message"),
221            ServiceError::InvalidDisputeId => write!(f, "Invalid dispute id"),
222            ServiceError::InvalidDisputeStatus => write!(f, "Invalid dispute status"),
223            ServiceError::InvalidPayload => write!(f, "Invalid payload"),
224            ServiceError::UnexpectedError(e) => write!(f, "Unexpected error: {}", e),
225            ServiceError::EnvVarError(e) => write!(f, "Environment variable error: {}", e),
226            ServiceError::IOError(e) => write!(f, "IO error: {}", e),
227            ServiceError::EncryptionError(e) => write!(f, "Encryption error: {}", e),
228            ServiceError::DecryptionError(e) => write!(f, "Decryption error: {}", e),
229        }
230    }
231}