1use std::array::TryFromSliceError;
4use std::fmt;
5
6use cashu::{CurrencyUnit, PaymentMethod};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_json::Value;
9use thiserror::Error;
10
11use crate::nuts::Id;
12use crate::util::hex;
13#[cfg(feature = "wallet")]
14use crate::wallet::WalletKey;
15use crate::Amount;
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("No Key for Amount")]
22 AmountKey,
23 #[error("Keyset id not known: `{0}`")]
25 KeysetUnknown(Id),
26 #[error("Unit unsupported")]
28 UnsupportedUnit,
29 #[error("Payment failed")]
31 PaymentFailed,
32 #[error("Payment pending")]
34 PaymentPending,
35 #[error("Request already paid")]
37 RequestAlreadyPaid,
38 #[error("Invalid payment request")]
40 InvalidPaymentRequest,
41 #[error("Invoice Amount undefined")]
43 InvoiceAmountUndefined,
44 #[error("Split Values must be less then or equal to amount")]
46 SplitValuesGreater,
47 #[error("Amount Overflow")]
49 AmountOverflow,
50 #[error("Signature missing or invalid")]
52 SignatureMissingOrInvalid,
53 #[error("Amount Less Invoice is not allowed")]
55 AmountLessNotAllowed,
56 #[error("Multi-Part Internal Melt Quotes are not supported")]
58 InternalMultiPartMeltQuote,
59 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
61 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
62 #[error("Clear Auth Required")]
64 ClearAuthRequired,
65 #[error("Blind Auth Required")]
67 BlindAuthRequired,
68 #[error("Clear Auth Failed")]
70 ClearAuthFailed,
71 #[error("Blind Auth Failed")]
73 BlindAuthFailed,
74 #[error("Auth settings undefined")]
76 AuthSettingsUndefined,
77 #[error("Mint time outside of tolerance")]
79 MintTimeExceedsTolerance,
80 #[error("Insufficient blind auth tokens, must reauth")]
82 InsufficientBlindAuthTokens,
83 #[error("Auth localstore undefined")]
85 AuthLocalstoreUndefined,
86 #[error("Wallet cat not set")]
88 CatNotSet,
89 #[error("Could not get mint info")]
91 CouldNotGetMintInfo,
92 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
94 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
95 #[error("Payment id seen for mint")]
97 DuplicatePaymentId,
98 #[error("Pubkey required")]
100 PubkeyRequired,
101 #[error("Invalid payment method")]
103 InvalidPaymentMethod,
104 #[error("Amount undefined")]
106 AmountUndefined,
107 #[error("Payment method unsupported")]
109 UnsupportedPaymentMethod,
110 #[error("Could not parse bolt12")]
112 Bolt12parse,
113 #[error("Could not parse invoice")]
115 InvalidInvoice,
116
117 #[error("Failed to parse BIP353 address: {0}")]
119 Bip353Parse(String),
120
121 #[error("Operation timeout")]
123 Timeout,
124
125 #[error("Failed to resolve BIP353 address: {0}")]
127 Bip353Resolve(String),
128 #[error("No Lightning offer found in BIP353 payment instructions")]
130 Bip353NoLightningOffer,
131
132 #[error("Failed to parse Lightning address: {0}")]
134 LightningAddressParse(String),
135 #[error("Failed to request invoice from Lightning address service: {0}")]
137 LightningAddressRequest(String),
138
139 #[error("Internal send error: {0}")]
141 SendError(String),
142
143 #[error("Internal receive error: {0}")]
145 RecvError(String),
146
147 #[error("Minting is disabled")]
150 MintingDisabled,
151 #[error("Unknown quote")]
153 UnknownQuote,
154 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
156 ExpiredQuote(u64, u64),
157 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
159 AmountOutofLimitRange(Amount, Amount, Amount),
160 #[error("Quote not paid")]
162 UnpaidQuote,
163 #[error("Quote pending")]
165 PendingQuote,
166 #[error("Quote already issued")]
168 IssuedQuote,
169 #[error("Quote is already paid")]
171 PaidQuote,
172 #[error("Payment state is unknown")]
174 UnknownPaymentState,
175 #[error("Minting is disabled")]
177 MeltingDisabled,
178 #[error("Unknown Keyset")]
180 UnknownKeySet,
181 #[error("Blinded Message is already signed")]
183 BlindedMessageAlreadySigned,
184 #[error("Inactive Keyset")]
186 InactiveKeyset,
187 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
189 TransactionUnbalanced(u64, u64, u64),
190 #[error("Duplicate Inputs")]
192 DuplicateInputs,
193 #[error("Duplicate outputs")]
195 DuplicateOutputs,
196 #[error("Cannot have multiple units")]
198 MultipleUnits,
199 #[error("Input unit must match output")]
201 UnitMismatch,
202 #[error("Sig all cannot be used in melt")]
204 SigAllUsedInMelt,
205 #[error("Token Already Spent")]
207 TokenAlreadySpent,
208 #[error("Token Pending")]
210 TokenPending,
211 #[error("Internal Error")]
213 Internal,
214 #[error("Oidc client not set")]
216 OidcNotSet,
217
218 #[error("P2PK condition not met `{0}`")]
221 P2PKConditionsNotMet(String),
222 #[error("Duplicate signature from same pubkey in P2PK")]
224 DuplicateSignatureError,
225 #[error("Spending condition locktime not provided")]
227 LocktimeNotProvided,
228 #[error("Invalid spending conditions: `{0}`")]
230 InvalidSpendConditions(String),
231 #[error("Incorrect wallet: `{0}`")]
233 IncorrectWallet(String),
234 #[error("Unknown wallet: `{0}`")]
236 #[cfg(feature = "wallet")]
237 UnknownWallet(WalletKey),
238 #[error("Max fee exceeded")]
240 MaxFeeExceeded,
241 #[error("Url path segments could not be joined")]
243 UrlPathSegments,
244 #[error("Unknown error response: `{0}`")]
246 UnknownErrorResponse(String),
247 #[error("Could not verify DLEQ proof")]
249 CouldNotVerifyDleq,
250 #[error("Dleq proof not provided for signature")]
252 DleqProofNotProvided,
253 #[error("Token does not match wallet mint")]
256 IncorrectMint,
257 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
259 MultiMintTokenNotSupported,
260 #[error("Preimage not provided")]
262 PreimageNotProvided,
263
264 #[error("Currency unit mismatch: wallet uses {expected}, but {found} provided")]
267 MultiMintCurrencyUnitMismatch {
268 expected: CurrencyUnit,
270 found: CurrencyUnit,
272 },
273 #[error("Unknown mint: {mint_url}")]
275 UnknownMint {
276 mint_url: String,
278 },
279 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
281 TransferTimeout {
282 source_mint: String,
284 target_mint: String,
286 amount: Amount,
288 },
289 #[error("Insufficient funds")]
291 InsufficientFunds,
292 #[error("Unexpected proof state")]
294 UnexpectedProofState,
295 #[error("No active keyset")]
297 NoActiveKeyset,
298 #[error("Incorrect quote amount")]
300 IncorrectQuoteAmount,
301 #[error("Invoice Description not supported")]
303 InvoiceDescriptionUnsupported,
304 #[error("Invalid transaction direction")]
306 InvalidTransactionDirection,
307 #[error("Invalid transaction id")]
309 InvalidTransactionId,
310 #[error("Transaction not found")]
312 TransactionNotFound,
313 #[error("Invalid KV store key or namespace: {0}")]
315 KVStoreInvalidKey(String),
316 #[error("`{0}`")]
318 Custom(String),
319
320 #[error(transparent)]
323 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
324 #[error(transparent)]
326 Bip32(#[from] bitcoin::bip32::Error),
327 #[error(transparent)]
329 ParseInt(#[from] std::num::ParseIntError),
330 #[error(transparent)]
332 UrlParseError(#[from] url::ParseError),
333 #[error(transparent)]
335 Utf8ParseError(#[from] std::string::FromUtf8Error),
336 #[error(transparent)]
338 SerdeJsonError(#[from] serde_json::Error),
339 #[error(transparent)]
341 Base64Error(#[from] bitcoin::base64::DecodeError),
342 #[error(transparent)]
344 HexError(#[from] hex::Error),
345 #[error("Http transport error {0:?}: {1}")]
347 HttpError(Option<u16>, String),
348 #[cfg(feature = "mint")]
350 #[error(transparent)]
351 Uuid(#[from] uuid::Error),
352 #[error(transparent)]
355 CashuUrl(#[from] crate::mint_url::Error),
356 #[error(transparent)]
358 Secret(#[from] crate::secret::Error),
359 #[error(transparent)]
361 AmountError(#[from] crate::amount::Error),
362 #[error(transparent)]
364 DHKE(#[from] crate::dhke::Error),
365 #[error(transparent)]
367 NUT00(#[from] crate::nuts::nut00::Error),
368 #[error(transparent)]
370 NUT01(#[from] crate::nuts::nut01::Error),
371 #[error(transparent)]
373 NUT02(#[from] crate::nuts::nut02::Error),
374 #[error(transparent)]
376 NUT03(#[from] crate::nuts::nut03::Error),
377 #[error(transparent)]
379 NUT04(#[from] crate::nuts::nut04::Error),
380 #[error(transparent)]
382 NUT05(#[from] crate::nuts::nut05::Error),
383 #[error(transparent)]
385 NUT11(#[from] crate::nuts::nut11::Error),
386 #[error(transparent)]
388 NUT12(#[from] crate::nuts::nut12::Error),
389 #[error(transparent)]
391 #[cfg(feature = "wallet")]
392 NUT13(#[from] crate::nuts::nut13::Error),
393 #[error(transparent)]
395 NUT14(#[from] crate::nuts::nut14::Error),
396 #[error(transparent)]
398 NUT18(#[from] crate::nuts::nut18::Error),
399 #[error(transparent)]
401 NUT20(#[from] crate::nuts::nut20::Error),
402 #[error(transparent)]
404 #[cfg(feature = "auth")]
405 NUT21(#[from] crate::nuts::nut21::Error),
406 #[error(transparent)]
408 #[cfg(feature = "auth")]
409 NUT22(#[from] crate::nuts::nut22::Error),
410 #[error(transparent)]
412 NUT23(#[from] crate::nuts::nut23::Error),
413 #[error(transparent)]
415 #[cfg(feature = "mint")]
416 QuoteId(#[from] crate::quote_id::QuoteIdError),
417 #[error(transparent)]
419 TryFromSliceError(#[from] TryFromSliceError),
420 #[error(transparent)]
422 Database(crate::database::Error),
423 #[error(transparent)]
425 #[cfg(feature = "mint")]
426 Payment(#[from] crate::payment::Error),
427}
428
429#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
434pub struct ErrorResponse {
435 pub code: ErrorCode,
437 #[serde(default)]
439 pub detail: String,
440}
441
442impl fmt::Display for ErrorResponse {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 write!(f, "code: {}, detail: {}", self.code, self.detail)
445 }
446}
447
448impl ErrorResponse {
449 pub fn new(code: ErrorCode, detail: String) -> Self {
451 Self { code, detail }
452 }
453
454 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
456 let value: Value = serde_json::from_str(json)?;
457
458 Self::from_value(value)
459 }
460
461 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
463 match serde_json::from_value::<ErrorResponse>(value.clone()) {
464 Ok(res) => Ok(res),
465 Err(_) => Ok(Self {
466 code: ErrorCode::Unknown(999),
467 detail: value.to_string(),
468 }),
469 }
470 }
471}
472
473fn map_nut11_error(nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
475 match nut11_error {
476 crate::nuts::nut11::Error::SignaturesNotProvided => ErrorCode::WitnessMissingOrInvalid,
477 crate::nuts::nut11::Error::InvalidSignature => ErrorCode::WitnessMissingOrInvalid,
478 crate::nuts::nut11::Error::DuplicateSignature => ErrorCode::DuplicateSignature,
479 _ => ErrorCode::Unknown(9999), }
481}
482
483impl From<Error> for ErrorResponse {
484 fn from(err: Error) -> ErrorResponse {
485 match err {
486 Error::TokenAlreadySpent => ErrorResponse {
487 code: ErrorCode::TokenAlreadySpent,
488 detail: err.to_string(),
489 },
490 Error::UnsupportedUnit => ErrorResponse {
491 code: ErrorCode::UnsupportedUnit,
492 detail: err.to_string(),
493 },
494 Error::PaymentFailed => ErrorResponse {
495 code: ErrorCode::LightningError,
496 detail: err.to_string(),
497 },
498 Error::RequestAlreadyPaid => ErrorResponse {
499 code: ErrorCode::InvoiceAlreadyPaid,
500 detail: "Invoice already paid.".to_string(),
501 },
502 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
503 ErrorResponse {
504 code: ErrorCode::TransactionUnbalanced,
505 detail: format!(
506 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
507 ),
508 }
509 }
510 Error::MintingDisabled => ErrorResponse {
511 code: ErrorCode::MintingDisabled,
512 detail: err.to_string(),
513 },
514 Error::BlindedMessageAlreadySigned => ErrorResponse {
515 code: ErrorCode::BlindedMessageAlreadySigned,
516 detail: err.to_string(),
517 },
518 Error::InsufficientFunds => ErrorResponse {
519 code: ErrorCode::TransactionUnbalanced,
520 detail: err.to_string(),
521 },
522 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
523 code: ErrorCode::AmountOutofLimitRange,
524 detail: err.to_string(),
525 },
526 Error::ExpiredQuote(_, _) => ErrorResponse {
527 code: ErrorCode::QuoteExpired,
528 detail: err.to_string(),
529 },
530 Error::PendingQuote => ErrorResponse {
531 code: ErrorCode::QuotePending,
532 detail: err.to_string(),
533 },
534 Error::TokenPending => ErrorResponse {
535 code: ErrorCode::TokenPending,
536 detail: err.to_string(),
537 },
538 Error::ClearAuthRequired => ErrorResponse {
539 code: ErrorCode::ClearAuthRequired,
540 detail: Error::ClearAuthRequired.to_string(),
541 },
542 Error::ClearAuthFailed => ErrorResponse {
543 code: ErrorCode::ClearAuthFailed,
544 detail: Error::ClearAuthFailed.to_string(),
545 },
546 Error::BlindAuthRequired => ErrorResponse {
547 code: ErrorCode::BlindAuthRequired,
548 detail: Error::BlindAuthRequired.to_string(),
549 },
550 Error::BlindAuthFailed => ErrorResponse {
551 code: ErrorCode::BlindAuthFailed,
552 detail: Error::BlindAuthFailed.to_string(),
553 },
554 Error::NUT20(err) => ErrorResponse {
555 code: ErrorCode::WitnessMissingOrInvalid,
556 detail: err.to_string(),
557 },
558 Error::DuplicateInputs => ErrorResponse {
559 code: ErrorCode::DuplicateInputs,
560 detail: err.to_string(),
561 },
562 Error::DuplicateOutputs => ErrorResponse {
563 code: ErrorCode::DuplicateOutputs,
564 detail: err.to_string(),
565 },
566 Error::MultipleUnits => ErrorResponse {
567 code: ErrorCode::MultipleUnits,
568 detail: err.to_string(),
569 },
570 Error::UnitMismatch => ErrorResponse {
571 code: ErrorCode::UnitMismatch,
572 detail: err.to_string(),
573 },
574 Error::UnpaidQuote => ErrorResponse {
575 code: ErrorCode::QuoteNotPaid,
576 detail: Error::UnpaidQuote.to_string(),
577 },
578 Error::NUT11(err) => {
579 let code = map_nut11_error(&err);
580 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
581 Some("P2PK signatures are required but not provided".to_string())
582 } else {
583 None
584 };
585 ErrorResponse {
586 code,
587 detail: match extra {
588 Some(extra) => format!("{err}. {extra}"),
589 None => err.to_string(),
590 },
591 }
592 },
593 Error::DuplicateSignatureError => ErrorResponse {
594 code: ErrorCode::DuplicateSignature,
595 detail: err.to_string(),
596 },
597 _ => ErrorResponse {
598 code: ErrorCode::Unknown(9999),
599 detail: err.to_string(),
600 },
601 }
602 }
603}
604
605#[cfg(feature = "mint")]
606impl From<crate::database::Error> for Error {
607 fn from(db_error: crate::database::Error) -> Self {
608 match db_error {
609 crate::database::Error::InvalidStateTransition(state) => match state {
610 crate::state::Error::Pending => Self::TokenPending,
611 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
612 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
613 },
614 db_error => Self::Database(db_error),
615 }
616 }
617}
618
619#[cfg(not(feature = "mint"))]
620impl From<crate::database::Error> for Error {
621 fn from(db_error: crate::database::Error) -> Self {
622 Self::Database(db_error)
623 }
624}
625
626impl From<ErrorResponse> for Error {
627 fn from(err: ErrorResponse) -> Error {
628 match err.code {
629 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
630 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
631 ErrorCode::QuotePending => Self::PendingQuote,
632 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
633 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
634 ErrorCode::KeysetInactive => Self::InactiveKeyset,
635 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
636 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
637 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
638 ErrorCode::MintingDisabled => Self::MintingDisabled,
639 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
640 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
641 ErrorCode::LightningError => Self::PaymentFailed,
642 ErrorCode::AmountOutofLimitRange => {
643 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
644 }
645 ErrorCode::TokenPending => Self::TokenPending,
646 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
647 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
648 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
649 ErrorCode::MultipleUnits => Self::MultipleUnits,
650 ErrorCode::UnitMismatch => Self::UnitMismatch,
651 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
652 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
653 ErrorCode::DuplicateSignature => Self::DuplicateSignatureError,
654 _ => Self::UnknownErrorResponse(err.to_string()),
655 }
656 }
657}
658
659#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
661#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
662pub enum ErrorCode {
663 TokenAlreadySpent,
665 TokenPending,
667 QuoteNotPaid,
669 QuoteExpired,
671 QuotePending,
673 KeysetNotFound,
675 KeysetInactive,
677 BlindedMessageAlreadySigned,
679 UnsupportedUnit,
681 TokensAlreadyIssued,
683 MintingDisabled,
685 InvoiceAlreadyPaid,
687 TokenNotVerified,
689 LightningError,
691 TransactionUnbalanced,
693 AmountOutofLimitRange,
695 WitnessMissingOrInvalid,
697 DuplicateInputs,
699 DuplicateOutputs,
701 MultipleUnits,
703 UnitMismatch,
705 ClearAuthRequired,
707 ClearAuthFailed,
709 BlindAuthRequired,
711 BlindAuthFailed,
713 DuplicateSignature,
715 Unknown(u16),
717}
718
719impl ErrorCode {
720 pub fn from_code(code: u16) -> Self {
722 match code {
723 10002 => Self::BlindedMessageAlreadySigned,
724 10003 => Self::TokenNotVerified,
725 11001 => Self::TokenAlreadySpent,
726 11002 => Self::TransactionUnbalanced,
727 11005 => Self::UnsupportedUnit,
728 11006 => Self::AmountOutofLimitRange,
729 11007 => Self::DuplicateInputs,
730 11008 => Self::DuplicateOutputs,
731 11009 => Self::MultipleUnits,
732 11010 => Self::UnitMismatch,
733 11012 => Self::TokenPending,
734 12001 => Self::KeysetNotFound,
735 12002 => Self::KeysetInactive,
736 20000 => Self::LightningError,
737 20001 => Self::QuoteNotPaid,
738 20002 => Self::TokensAlreadyIssued,
739 20003 => Self::MintingDisabled,
740 20005 => Self::QuotePending,
741 20006 => Self::InvoiceAlreadyPaid,
742 20007 => Self::QuoteExpired,
743 20008 => Self::WitnessMissingOrInvalid,
744 20009 => Self::DuplicateSignature,
745 30001 => Self::ClearAuthRequired,
746 30002 => Self::ClearAuthFailed,
747 31001 => Self::BlindAuthRequired,
748 31002 => Self::BlindAuthFailed,
749 _ => Self::Unknown(code),
750 }
751 }
752
753 pub fn to_code(&self) -> u16 {
755 match self {
756 Self::BlindedMessageAlreadySigned => 10002,
757 Self::TokenNotVerified => 10003,
758 Self::TokenAlreadySpent => 11001,
759 Self::TransactionUnbalanced => 11002,
760 Self::UnsupportedUnit => 11005,
761 Self::AmountOutofLimitRange => 11006,
762 Self::DuplicateInputs => 11007,
763 Self::DuplicateOutputs => 11008,
764 Self::MultipleUnits => 11009,
765 Self::UnitMismatch => 11010,
766 Self::TokenPending => 11012,
767 Self::KeysetNotFound => 12001,
768 Self::KeysetInactive => 12002,
769 Self::LightningError => 20000,
770 Self::QuoteNotPaid => 20001,
771 Self::TokensAlreadyIssued => 20002,
772 Self::MintingDisabled => 20003,
773 Self::QuotePending => 20005,
774 Self::InvoiceAlreadyPaid => 20006,
775 Self::QuoteExpired => 20007,
776 Self::WitnessMissingOrInvalid => 20008,
777 Self::DuplicateSignature => 20009,
778 Self::ClearAuthRequired => 30001,
779 Self::ClearAuthFailed => 30002,
780 Self::BlindAuthRequired => 31001,
781 Self::BlindAuthFailed => 31002,
782 Self::Unknown(code) => *code,
783 }
784 }
785}
786
787impl Serialize for ErrorCode {
788 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
789 where
790 S: Serializer,
791 {
792 serializer.serialize_u16(self.to_code())
793 }
794}
795
796impl<'de> Deserialize<'de> for ErrorCode {
797 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
798 where
799 D: Deserializer<'de>,
800 {
801 let code = u16::deserialize(deserializer)?;
802
803 Ok(ErrorCode::from_code(code))
804 }
805}
806
807impl fmt::Display for ErrorCode {
808 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
809 write!(f, "{}", self.to_code())
810 }
811}