cdk_common/
error.rs

1//! Errors
2
3use std::fmt;
4
5use cashu::{CurrencyUnit, PaymentMethod};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::Value;
8use thiserror::Error;
9
10use crate::nuts::Id;
11use crate::util::hex;
12#[cfg(feature = "wallet")]
13use crate::wallet::WalletKey;
14use crate::Amount;
15
16/// CDK Error
17#[derive(Debug, Error)]
18pub enum Error {
19    /// Mint does not have a key for amount
20    #[error("No Key for Amount")]
21    AmountKey,
22    /// Keyset is not known
23    #[error("Keyset id not known: `{0}`")]
24    KeysetUnknown(Id),
25    /// Unsupported unit
26    #[error("Unit unsupported")]
27    UnsupportedUnit,
28    /// Payment failed
29    #[error("Payment failed")]
30    PaymentFailed,
31    /// Payment pending
32    #[error("Payment pending")]
33    PaymentPending,
34    /// Invoice already paid
35    #[error("Request already paid")]
36    RequestAlreadyPaid,
37    /// Invalid payment request
38    #[error("Invalid payment request")]
39    InvalidPaymentRequest,
40    /// Bolt11 invoice does not have amount
41    #[error("Invoice Amount undefined")]
42    InvoiceAmountUndefined,
43    /// Split Values must be less then or equal to amount
44    #[error("Split Values must be less then or equal to amount")]
45    SplitValuesGreater,
46    /// Amount overflow
47    #[error("Amount Overflow")]
48    AmountOverflow,
49    /// Witness missing or invalid
50    #[error("Signature missing or invalid")]
51    SignatureMissingOrInvalid,
52    /// Amountless Invoice Not supported
53    #[error("Amount Less Invoice is not allowed")]
54    AmountLessNotAllowed,
55    /// Multi-Part Internal Melt Quotes are not supported
56    #[error("Multi-Part Internal Melt Quotes are not supported")]
57    InternalMultiPartMeltQuote,
58    /// Multi-Part Payment not supported for unit and method
59    #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
60    MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
61    /// Clear Auth Required
62    #[error("Clear Auth Required")]
63    ClearAuthRequired,
64    /// Blind Auth Required
65    #[error("Blind Auth Required")]
66    BlindAuthRequired,
67    /// Clear Auth Failed
68    #[error("Clear Auth Failed")]
69    ClearAuthFailed,
70    /// Blind Auth Failed
71    #[error("Blind Auth Failed")]
72    BlindAuthFailed,
73    /// Auth settings undefined
74    #[error("Auth settings undefined")]
75    AuthSettingsUndefined,
76    /// Mint time outside of tolerance
77    #[error("Mint time outside of tolerance")]
78    MintTimeExceedsTolerance,
79    /// Insufficient blind auth tokens
80    #[error("Insufficient blind auth tokens, must reauth")]
81    InsufficientBlindAuthTokens,
82    /// Auth localstore undefined
83    #[error("Auth localstore undefined")]
84    AuthLocalstoreUndefined,
85    /// Wallet cat not set
86    #[error("Wallet cat not set")]
87    CatNotSet,
88    /// Could not get mint info
89    #[error("Could not get mint info")]
90    CouldNotGetMintInfo,
91
92    // Mint Errors
93    /// Minting is disabled
94    #[error("Minting is disabled")]
95    MintingDisabled,
96    /// Quote is not known
97    #[error("Unknown quote")]
98    UnknownQuote,
99    /// Quote is expired
100    #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
101    ExpiredQuote(u64, u64),
102    /// Amount is outside of allowed range
103    #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
104    AmountOutofLimitRange(Amount, Amount, Amount),
105    /// Quote is not paiud
106    #[error("Quote not paid")]
107    UnpaidQuote,
108    /// Quote is pending
109    #[error("Quote pending")]
110    PendingQuote,
111    /// ecash already issued for quote
112    #[error("Quote already issued")]
113    IssuedQuote,
114    /// Quote has already been paid
115    #[error("Quote is already paid")]
116    PaidQuote,
117    /// Payment state is unknown
118    #[error("Payment state is unknown")]
119    UnknownPaymentState,
120    /// Melting is disabled
121    #[error("Minting is disabled")]
122    MeltingDisabled,
123    /// Unknown Keyset
124    #[error("Unknown Keyset")]
125    UnknownKeySet,
126    /// BlindedMessage is already signed
127    #[error("Blinded Message is already signed")]
128    BlindedMessageAlreadySigned,
129    /// Inactive Keyset
130    #[error("Inactive Keyset")]
131    InactiveKeyset,
132    /// Transaction unbalanced
133    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
134    TransactionUnbalanced(u64, u64, u64),
135    /// Duplicate proofs provided
136    #[error("Duplicate Inputs")]
137    DuplicateInputs,
138    /// Duplicate output
139    #[error("Duplicate outputs")]
140    DuplicateOutputs,
141    /// Multiple units provided
142    #[error("Cannot have multiple units")]
143    MultipleUnits,
144    /// Unit mismatch
145    #[error("Input unit must match output")]
146    UnitMismatch,
147    /// Sig all cannot be used in melt
148    #[error("Sig all cannot be used in melt")]
149    SigAllUsedInMelt,
150    /// Token is already spent
151    #[error("Token Already Spent")]
152    TokenAlreadySpent,
153    /// Token is already pending
154    #[error("Token Pending")]
155    TokenPending,
156    /// Internal Error
157    #[error("Internal Error")]
158    Internal,
159    /// Oidc config not set
160    #[error("Oidc client not set")]
161    OidcNotSet,
162
163    // Wallet Errors
164    /// P2PK spending conditions not met
165    #[error("P2PK condition not met `{0}`")]
166    P2PKConditionsNotMet(String),
167    /// Spending Locktime not provided
168    #[error("Spending condition locktime not provided")]
169    LocktimeNotProvided,
170    /// Invalid Spending Conditions
171    #[error("Invalid spending conditions: `{0}`")]
172    InvalidSpendConditions(String),
173    /// Incorrect Wallet
174    #[error("Incorrect wallet: `{0}`")]
175    IncorrectWallet(String),
176    /// Unknown Wallet
177    #[error("Unknown wallet: `{0}`")]
178    #[cfg(feature = "wallet")]
179    UnknownWallet(WalletKey),
180    /// Max Fee Ecxeded
181    #[error("Max fee exceeded")]
182    MaxFeeExceeded,
183    /// Url path segments could not be joined
184    #[error("Url path segments could not be joined")]
185    UrlPathSegments,
186    ///  Unknown error response
187    #[error("Unknown error response: `{0}`")]
188    UnknownErrorResponse(String),
189    /// Invalid DLEQ proof
190    #[error("Could not verify DLEQ proof")]
191    CouldNotVerifyDleq,
192    /// Dleq Proof not provided for signature
193    #[error("Dleq proof not provided for signature")]
194    DleqProofNotProvided,
195    /// Incorrect Mint
196    /// Token does not match wallet mint
197    #[error("Token does not match wallet mint")]
198    IncorrectMint,
199    /// Receive can only be used with tokens from single mint
200    #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
201    MultiMintTokenNotSupported,
202    /// Preimage not provided
203    #[error("Preimage not provided")]
204    PreimageNotProvided,
205    /// Insufficient Funds
206    #[error("Insufficient funds")]
207    InsufficientFunds,
208    /// Unexpected proof state
209    #[error("Unexpected proof state")]
210    UnexpectedProofState,
211    /// No active keyset
212    #[error("No active keyset")]
213    NoActiveKeyset,
214    /// Incorrect quote amount
215    #[error("Incorrect quote amount")]
216    IncorrectQuoteAmount,
217    /// Invoice Description not supported
218    #[error("Invoice Description not supported")]
219    InvoiceDescriptionUnsupported,
220    /// Custom Error
221    #[error("`{0}`")]
222    Custom(String),
223
224    // External Error conversions
225    /// Parse invoice error
226    #[error(transparent)]
227    Invoice(#[from] lightning_invoice::ParseOrSemanticError),
228    /// Bip32 error
229    #[error(transparent)]
230    Bip32(#[from] bitcoin::bip32::Error),
231    /// Parse int error
232    #[error(transparent)]
233    ParseInt(#[from] std::num::ParseIntError),
234    /// Parse 9rl Error
235    #[error(transparent)]
236    UrlParseError(#[from] url::ParseError),
237    /// Utf8 parse error
238    #[error(transparent)]
239    Utf8ParseError(#[from] std::string::FromUtf8Error),
240    /// Serde Json error
241    #[error(transparent)]
242    SerdeJsonError(#[from] serde_json::Error),
243    /// Base64 error
244    #[error(transparent)]
245    Base64Error(#[from] bitcoin::base64::DecodeError),
246    /// From hex error
247    #[error(transparent)]
248    HexError(#[from] hex::Error),
249    /// Http transport error
250    #[error("Http transport error: {0}")]
251    HttpError(String),
252    #[cfg(feature = "wallet")]
253    // Crate error conversions
254    /// Cashu Url Error
255    #[error(transparent)]
256    CashuUrl(#[from] crate::mint_url::Error),
257    /// Secret error
258    #[error(transparent)]
259    Secret(#[from] crate::secret::Error),
260    /// Amount Error
261    #[error(transparent)]
262    AmountError(#[from] crate::amount::Error),
263    /// DHKE Error
264    #[error(transparent)]
265    DHKE(#[from] crate::dhke::Error),
266    /// NUT00 Error
267    #[error(transparent)]
268    NUT00(#[from] crate::nuts::nut00::Error),
269    /// Nut01 error
270    #[error(transparent)]
271    NUT01(#[from] crate::nuts::nut01::Error),
272    /// NUT02 error
273    #[error(transparent)]
274    NUT02(#[from] crate::nuts::nut02::Error),
275    /// NUT03 error
276    #[error(transparent)]
277    NUT03(#[from] crate::nuts::nut03::Error),
278    /// NUT04 error
279    #[error(transparent)]
280    NUT04(#[from] crate::nuts::nut04::Error),
281    /// NUT05 error
282    #[error(transparent)]
283    NUT05(#[from] crate::nuts::nut05::Error),
284    /// NUT11 Error
285    #[error(transparent)]
286    NUT11(#[from] crate::nuts::nut11::Error),
287    /// NUT12 Error
288    #[error(transparent)]
289    NUT12(#[from] crate::nuts::nut12::Error),
290    /// NUT13 Error
291    #[error(transparent)]
292    #[cfg(feature = "wallet")]
293    NUT13(#[from] crate::nuts::nut13::Error),
294    /// NUT14 Error
295    #[error(transparent)]
296    NUT14(#[from] crate::nuts::nut14::Error),
297    /// NUT18 Error
298    #[error(transparent)]
299    NUT18(#[from] crate::nuts::nut18::Error),
300    /// NUT20 Error
301    #[error(transparent)]
302    NUT20(#[from] crate::nuts::nut20::Error),
303    /// NUTXX Error
304    #[error(transparent)]
305    NUT21(#[from] crate::nuts::nut21::Error),
306    /// NUTXX1 Error
307    #[error(transparent)]
308    NUT22(#[from] crate::nuts::nut22::Error),
309    /// Database Error
310    #[error(transparent)]
311    Database(#[from] crate::database::Error),
312    /// Payment Error
313    #[error(transparent)]
314    #[cfg(feature = "mint")]
315    Payment(#[from] crate::payment::Error),
316}
317
318/// CDK Error Response
319///
320/// See NUT definition in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
323pub struct ErrorResponse {
324    /// Error Code
325    pub code: ErrorCode,
326    /// Human readable Text
327    pub error: Option<String>,
328    /// Longer human readable description
329    pub detail: Option<String>,
330}
331
332impl fmt::Display for ErrorResponse {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        write!(
335            f,
336            "code: {}, error: {}, detail: {}",
337            self.code,
338            self.error.clone().unwrap_or_default(),
339            self.detail.clone().unwrap_or_default()
340        )
341    }
342}
343
344impl ErrorResponse {
345    /// Create new [`ErrorResponse`]
346    pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
347        Self {
348            code,
349            error,
350            detail,
351        }
352    }
353
354    /// Error response from json
355    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
356        let value: Value = serde_json::from_str(json)?;
357
358        Self::from_value(value)
359    }
360
361    /// Error response from json Value
362    pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
363        match serde_json::from_value::<ErrorResponse>(value.clone()) {
364            Ok(res) => Ok(res),
365            Err(_) => Ok(Self {
366                code: ErrorCode::Unknown(999),
367                error: Some(value.to_string()),
368                detail: None,
369            }),
370        }
371    }
372}
373
374impl From<Error> for ErrorResponse {
375    fn from(err: Error) -> ErrorResponse {
376        match err {
377            Error::TokenAlreadySpent => ErrorResponse {
378                code: ErrorCode::TokenAlreadySpent,
379                error: Some(err.to_string()),
380                detail: None,
381            },
382            Error::UnsupportedUnit => ErrorResponse {
383                code: ErrorCode::UnsupportedUnit,
384                error: Some(err.to_string()),
385                detail: None,
386            },
387            Error::PaymentFailed => ErrorResponse {
388                code: ErrorCode::LightningError,
389                error: Some(err.to_string()),
390                detail: None,
391            },
392            Error::RequestAlreadyPaid => ErrorResponse {
393                code: ErrorCode::InvoiceAlreadyPaid,
394                error: Some("Invoice already paid.".to_string()),
395                detail: None,
396            },
397            Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
398                ErrorResponse {
399                    code: ErrorCode::TransactionUnbalanced,
400                    error: Some(format!(
401                        "Inputs: {}, Outputs: {}, expected_fee: {}",
402                        inputs_total, outputs_total, fee_expected,
403                    )),
404                    detail: Some("Transaction inputs should equal outputs less fee".to_string()),
405                }
406            }
407            Error::MintingDisabled => ErrorResponse {
408                code: ErrorCode::MintingDisabled,
409                error: Some(err.to_string()),
410                detail: None,
411            },
412            Error::BlindedMessageAlreadySigned => ErrorResponse {
413                code: ErrorCode::BlindedMessageAlreadySigned,
414                error: Some(err.to_string()),
415                detail: None,
416            },
417            Error::InsufficientFunds => ErrorResponse {
418                code: ErrorCode::TransactionUnbalanced,
419                error: Some(err.to_string()),
420                detail: None,
421            },
422            Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
423                code: ErrorCode::AmountOutofLimitRange,
424                error: Some(err.to_string()),
425                detail: None,
426            },
427            Error::ExpiredQuote(_, _) => ErrorResponse {
428                code: ErrorCode::QuoteExpired,
429                error: Some(err.to_string()),
430                detail: None,
431            },
432            Error::PendingQuote => ErrorResponse {
433                code: ErrorCode::QuotePending,
434                error: Some(err.to_string()),
435                detail: None,
436            },
437            Error::TokenPending => ErrorResponse {
438                code: ErrorCode::TokenPending,
439                error: Some(err.to_string()),
440                detail: None,
441            },
442            Error::ClearAuthRequired => ErrorResponse {
443                code: ErrorCode::ClearAuthRequired,
444                error: None,
445                detail: None,
446            },
447            Error::ClearAuthFailed => ErrorResponse {
448                code: ErrorCode::ClearAuthFailed,
449                error: None,
450                detail: None,
451            },
452            Error::BlindAuthRequired => ErrorResponse {
453                code: ErrorCode::BlindAuthRequired,
454                error: None,
455                detail: None,
456            },
457            Error::BlindAuthFailed => ErrorResponse {
458                code: ErrorCode::BlindAuthFailed,
459                error: None,
460                detail: None,
461            },
462            Error::NUT20(err) => ErrorResponse {
463                code: ErrorCode::WitnessMissingOrInvalid,
464                error: Some(err.to_string()),
465                detail: None,
466            },
467            Error::DuplicateInputs => ErrorResponse {
468                code: ErrorCode::DuplicateInputs,
469                error: Some(err.to_string()),
470                detail: None,
471            },
472            Error::DuplicateOutputs => ErrorResponse {
473                code: ErrorCode::DuplicateOutputs,
474                error: Some(err.to_string()),
475                detail: None,
476            },
477            Error::MultipleUnits => ErrorResponse {
478                code: ErrorCode::MultipleUnits,
479                error: Some(err.to_string()),
480                detail: None,
481            },
482            Error::UnitMismatch => ErrorResponse {
483                code: ErrorCode::UnitMismatch,
484                error: Some(err.to_string()),
485                detail: None,
486            },
487            _ => ErrorResponse {
488                code: ErrorCode::Unknown(9999),
489                error: Some(err.to_string()),
490                detail: None,
491            },
492        }
493    }
494}
495
496impl From<ErrorResponse> for Error {
497    fn from(err: ErrorResponse) -> Error {
498        match err.code {
499            ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
500            ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
501            ErrorCode::QuotePending => Self::PendingQuote,
502            ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
503            ErrorCode::KeysetNotFound => Self::UnknownKeySet,
504            ErrorCode::KeysetInactive => Self::InactiveKeyset,
505            ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
506            ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
507            ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
508            ErrorCode::MintingDisabled => Self::MintingDisabled,
509            ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
510            ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
511            ErrorCode::LightningError => Self::PaymentFailed,
512            ErrorCode::AmountOutofLimitRange => {
513                Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
514            }
515            ErrorCode::TokenPending => Self::TokenPending,
516            ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
517            ErrorCode::DuplicateInputs => Self::DuplicateInputs,
518            ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
519            ErrorCode::MultipleUnits => Self::MultipleUnits,
520            ErrorCode::UnitMismatch => Self::UnitMismatch,
521            ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
522            ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
523            _ => Self::UnknownErrorResponse(err.to_string()),
524        }
525    }
526}
527
528/// Possible Error Codes
529#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
530#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
531pub enum ErrorCode {
532    /// Token is already spent
533    TokenAlreadySpent,
534    /// Token Pending
535    TokenPending,
536    /// Quote is not paid
537    QuoteNotPaid,
538    /// Quote is not expired
539    QuoteExpired,
540    /// Quote Pending
541    QuotePending,
542    /// Keyset is not found
543    KeysetNotFound,
544    /// Keyset inactive
545    KeysetInactive,
546    /// Blinded Message Already signed
547    BlindedMessageAlreadySigned,
548    /// Unsupported unit
549    UnsupportedUnit,
550    /// Token already issed for quote
551    TokensAlreadyIssued,
552    /// Minting Disabled
553    MintingDisabled,
554    /// Invoice Already Paid
555    InvoiceAlreadyPaid,
556    /// Token Not Verified
557    TokenNotVerified,
558    /// Lightning Error
559    LightningError,
560    /// Unbalanced Error
561    TransactionUnbalanced,
562    /// Amount outside of allowed range
563    AmountOutofLimitRange,
564    /// Witness missing or invalid
565    WitnessMissingOrInvalid,
566    /// Duplicate Inputs
567    DuplicateInputs,
568    /// Duplicate Outputs
569    DuplicateOutputs,
570    /// Multiple Units
571    MultipleUnits,
572    /// Input unit does not match output
573    UnitMismatch,
574    /// Clear Auth Required
575    ClearAuthRequired,
576    /// Clear Auth Failed
577    ClearAuthFailed,
578    /// Blind Auth Required
579    BlindAuthRequired,
580    /// Blind Auth Failed
581    BlindAuthFailed,
582    /// Unknown error code
583    Unknown(u16),
584}
585
586impl ErrorCode {
587    /// Error code from u16
588    pub fn from_code(code: u16) -> Self {
589        match code {
590            10002 => Self::BlindedMessageAlreadySigned,
591            10003 => Self::TokenNotVerified,
592            11001 => Self::TokenAlreadySpent,
593            11002 => Self::TransactionUnbalanced,
594            11005 => Self::UnsupportedUnit,
595            11006 => Self::AmountOutofLimitRange,
596            11007 => Self::DuplicateInputs,
597            11008 => Self::DuplicateOutputs,
598            11009 => Self::MultipleUnits,
599            11010 => Self::UnitMismatch,
600            11012 => Self::TokenPending,
601            12001 => Self::KeysetNotFound,
602            12002 => Self::KeysetInactive,
603            20000 => Self::LightningError,
604            20001 => Self::QuoteNotPaid,
605            20002 => Self::TokensAlreadyIssued,
606            20003 => Self::MintingDisabled,
607            20005 => Self::QuotePending,
608            20006 => Self::InvoiceAlreadyPaid,
609            20007 => Self::QuoteExpired,
610            20008 => Self::WitnessMissingOrInvalid,
611            30001 => Self::ClearAuthRequired,
612            30002 => Self::ClearAuthFailed,
613            31001 => Self::BlindAuthRequired,
614            31002 => Self::BlindAuthFailed,
615            _ => Self::Unknown(code),
616        }
617    }
618
619    /// Error code to u16
620    pub fn to_code(&self) -> u16 {
621        match self {
622            Self::BlindedMessageAlreadySigned => 10002,
623            Self::TokenNotVerified => 10003,
624            Self::TokenAlreadySpent => 11001,
625            Self::TransactionUnbalanced => 11002,
626            Self::UnsupportedUnit => 11005,
627            Self::AmountOutofLimitRange => 11006,
628            Self::DuplicateInputs => 11007,
629            Self::DuplicateOutputs => 11008,
630            Self::MultipleUnits => 11009,
631            Self::UnitMismatch => 11010,
632            Self::TokenPending => 11012,
633            Self::KeysetNotFound => 12001,
634            Self::KeysetInactive => 12002,
635            Self::LightningError => 20000,
636            Self::QuoteNotPaid => 20001,
637            Self::TokensAlreadyIssued => 20002,
638            Self::MintingDisabled => 20003,
639            Self::QuotePending => 20005,
640            Self::InvoiceAlreadyPaid => 20006,
641            Self::QuoteExpired => 20007,
642            Self::WitnessMissingOrInvalid => 20008,
643            Self::ClearAuthRequired => 30001,
644            Self::ClearAuthFailed => 30002,
645            Self::BlindAuthRequired => 31001,
646            Self::BlindAuthFailed => 31002,
647            Self::Unknown(code) => *code,
648        }
649    }
650}
651
652impl Serialize for ErrorCode {
653    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
654    where
655        S: Serializer,
656    {
657        serializer.serialize_u16(self.to_code())
658    }
659}
660
661impl<'de> Deserialize<'de> for ErrorCode {
662    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
663    where
664        D: Deserializer<'de>,
665    {
666        let code = u16::deserialize(deserializer)?;
667
668        Ok(ErrorCode::from_code(code))
669    }
670}
671
672impl fmt::Display for ErrorCode {
673    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674        write!(f, "{}", self.to_code())
675    }
676}