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}