cosmwasm_std/errors/
std_error.rs

1use core::fmt;
2#[cfg(feature = "backtraces")]
3use std::backtrace::Backtrace;
4use thiserror::Error;
5
6use crate::errors::{RecoverPubkeyError, VerificationError};
7
8/// Structured error type for init, execute and query.
9///
10/// This can be serialized and passed over the Wasm/VM boundary, which allows us to use structured
11/// error types in e.g. integration tests. In that process backtraces are stripped off.
12///
13/// The prefix "Std" means "the standard error within the standard library". This is not the only
14/// result/error type in cosmwasm-std.
15///
16/// When new cases are added, they should describe the problem rather than what was attempted (e.g.
17/// InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of
18/// the duplication in "StdError::FooErr".
19///
20/// Checklist for adding a new error:
21/// - Add enum case
22/// - Add creator function in std_error_helpers.rs
23#[derive(Error, Debug)]
24pub enum StdError {
25    #[error("Verification error: {source}")]
26    VerificationErr {
27        source: VerificationError,
28        #[cfg(feature = "backtraces")]
29        backtrace: Backtrace,
30    },
31    #[error("Recover pubkey error: {source}")]
32    RecoverPubkeyErr {
33        source: RecoverPubkeyError,
34        #[cfg(feature = "backtraces")]
35        backtrace: Backtrace,
36    },
37    /// Whenever there is no specific error type available
38    #[error("Generic error: {msg}")]
39    GenericErr {
40        msg: String,
41        #[cfg(feature = "backtraces")]
42        backtrace: Backtrace,
43    },
44    #[error("Invalid Base64 string: {msg}")]
45    InvalidBase64 {
46        msg: String,
47        #[cfg(feature = "backtraces")]
48        backtrace: Backtrace,
49    },
50    #[error("Invalid data size: expected={expected} actual={actual}")]
51    InvalidDataSize {
52        expected: u64,
53        actual: u64,
54        #[cfg(feature = "backtraces")]
55        backtrace: Backtrace,
56    },
57    #[error("Invalid hex string: {msg}")]
58    InvalidHex {
59        msg: String,
60        #[cfg(feature = "backtraces")]
61        backtrace: Backtrace,
62    },
63    /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.
64    #[error("Cannot decode UTF8 bytes into string: {msg}")]
65    InvalidUtf8 {
66        msg: String,
67        #[cfg(feature = "backtraces")]
68        backtrace: Backtrace,
69    },
70    #[error("{kind} not found")]
71    NotFound {
72        kind: String,
73        #[cfg(feature = "backtraces")]
74        backtrace: Backtrace,
75    },
76    #[error("Error parsing into type {target_type}: {msg}")]
77    ParseErr {
78        /// the target type that was attempted
79        target_type: String,
80        msg: String,
81        #[cfg(feature = "backtraces")]
82        backtrace: Backtrace,
83    },
84    #[error("Error serializing type {source_type}: {msg}")]
85    SerializeErr {
86        /// the source type that was attempted
87        source_type: String,
88        msg: String,
89        #[cfg(feature = "backtraces")]
90        backtrace: Backtrace,
91    },
92    #[error("Overflow: {source}")]
93    Overflow {
94        source: OverflowError,
95        #[cfg(feature = "backtraces")]
96        backtrace: Backtrace,
97    },
98    #[error("Divide by zero: {source}")]
99    DivideByZero {
100        source: DivideByZeroError,
101        #[cfg(feature = "backtraces")]
102        backtrace: Backtrace,
103    },
104    #[error("Conversion error: ")]
105    ConversionOverflow {
106        #[from]
107        source: ConversionOverflowError,
108        #[cfg(feature = "backtraces")]
109        backtrace: Backtrace,
110    },
111}
112
113impl StdError {
114    pub fn verification_err(source: VerificationError) -> Self {
115        StdError::VerificationErr {
116            source,
117            #[cfg(feature = "backtraces")]
118            backtrace: Backtrace::capture(),
119        }
120    }
121
122    pub fn recover_pubkey_err(source: RecoverPubkeyError) -> Self {
123        StdError::RecoverPubkeyErr {
124            source,
125            #[cfg(feature = "backtraces")]
126            backtrace: Backtrace::capture(),
127        }
128    }
129
130    pub fn generic_err(msg: impl Into<String>) -> Self {
131        StdError::GenericErr {
132            msg: msg.into(),
133            #[cfg(feature = "backtraces")]
134            backtrace: Backtrace::capture(),
135        }
136    }
137
138    pub fn invalid_base64(msg: impl ToString) -> Self {
139        StdError::InvalidBase64 {
140            msg: msg.to_string(),
141            #[cfg(feature = "backtraces")]
142            backtrace: Backtrace::capture(),
143        }
144    }
145
146    pub fn invalid_data_size(expected: usize, actual: usize) -> Self {
147        StdError::InvalidDataSize {
148            // Cast is safe because usize is 32 or 64 bit large in all environments we support
149            expected: expected as u64,
150            actual: actual as u64,
151            #[cfg(feature = "backtraces")]
152            backtrace: Backtrace::capture(),
153        }
154    }
155
156    pub fn invalid_hex(msg: impl ToString) -> Self {
157        StdError::InvalidHex {
158            msg: msg.to_string(),
159            #[cfg(feature = "backtraces")]
160            backtrace: Backtrace::capture(),
161        }
162    }
163
164    pub fn invalid_utf8(msg: impl ToString) -> Self {
165        StdError::InvalidUtf8 {
166            msg: msg.to_string(),
167            #[cfg(feature = "backtraces")]
168            backtrace: Backtrace::capture(),
169        }
170    }
171
172    pub fn not_found(kind: impl Into<String>) -> Self {
173        StdError::NotFound {
174            kind: kind.into(),
175            #[cfg(feature = "backtraces")]
176            backtrace: Backtrace::capture(),
177        }
178    }
179
180    pub fn parse_err(target: impl Into<String>, msg: impl ToString) -> Self {
181        StdError::ParseErr {
182            target_type: target.into(),
183            msg: msg.to_string(),
184            #[cfg(feature = "backtraces")]
185            backtrace: Backtrace::capture(),
186        }
187    }
188
189    pub fn serialize_err(source: impl Into<String>, msg: impl ToString) -> Self {
190        StdError::SerializeErr {
191            source_type: source.into(),
192            msg: msg.to_string(),
193            #[cfg(feature = "backtraces")]
194            backtrace: Backtrace::capture(),
195        }
196    }
197
198    pub fn overflow(source: OverflowError) -> Self {
199        StdError::Overflow {
200            source,
201            #[cfg(feature = "backtraces")]
202            backtrace: Backtrace::capture(),
203        }
204    }
205
206    pub fn divide_by_zero(source: DivideByZeroError) -> Self {
207        StdError::DivideByZero {
208            source,
209            #[cfg(feature = "backtraces")]
210            backtrace: Backtrace::capture(),
211        }
212    }
213}
214
215impl PartialEq<StdError> for StdError {
216    fn eq(&self, rhs: &StdError) -> bool {
217        match self {
218            StdError::VerificationErr {
219                source,
220                #[cfg(feature = "backtraces")]
221                    backtrace: _,
222            } => {
223                if let StdError::VerificationErr {
224                    source: rhs_source,
225                    #[cfg(feature = "backtraces")]
226                        backtrace: _,
227                } = rhs
228                {
229                    source == rhs_source
230                } else {
231                    false
232                }
233            }
234            StdError::RecoverPubkeyErr {
235                source,
236                #[cfg(feature = "backtraces")]
237                    backtrace: _,
238            } => {
239                if let StdError::RecoverPubkeyErr {
240                    source: rhs_source,
241                    #[cfg(feature = "backtraces")]
242                        backtrace: _,
243                } = rhs
244                {
245                    source == rhs_source
246                } else {
247                    false
248                }
249            }
250            StdError::GenericErr {
251                msg,
252                #[cfg(feature = "backtraces")]
253                    backtrace: _,
254            } => {
255                if let StdError::GenericErr {
256                    msg: rhs_msg,
257                    #[cfg(feature = "backtraces")]
258                        backtrace: _,
259                } = rhs
260                {
261                    msg == rhs_msg
262                } else {
263                    false
264                }
265            }
266            StdError::InvalidBase64 {
267                msg,
268                #[cfg(feature = "backtraces")]
269                    backtrace: _,
270            } => {
271                if let StdError::InvalidBase64 {
272                    msg: rhs_msg,
273                    #[cfg(feature = "backtraces")]
274                        backtrace: _,
275                } = rhs
276                {
277                    msg == rhs_msg
278                } else {
279                    false
280                }
281            }
282            StdError::InvalidDataSize {
283                expected,
284                actual,
285                #[cfg(feature = "backtraces")]
286                    backtrace: _,
287            } => {
288                if let StdError::InvalidDataSize {
289                    expected: rhs_expected,
290                    actual: rhs_actual,
291                    #[cfg(feature = "backtraces")]
292                        backtrace: _,
293                } = rhs
294                {
295                    expected == rhs_expected && actual == rhs_actual
296                } else {
297                    false
298                }
299            }
300            StdError::InvalidHex {
301                msg,
302                #[cfg(feature = "backtraces")]
303                    backtrace: _,
304            } => {
305                if let StdError::InvalidHex {
306                    msg: rhs_msg,
307                    #[cfg(feature = "backtraces")]
308                        backtrace: _,
309                } = rhs
310                {
311                    msg == rhs_msg
312                } else {
313                    false
314                }
315            }
316            StdError::InvalidUtf8 {
317                msg,
318                #[cfg(feature = "backtraces")]
319                    backtrace: _,
320            } => {
321                if let StdError::InvalidUtf8 {
322                    msg: rhs_msg,
323                    #[cfg(feature = "backtraces")]
324                        backtrace: _,
325                } = rhs
326                {
327                    msg == rhs_msg
328                } else {
329                    false
330                }
331            }
332            StdError::NotFound {
333                kind,
334                #[cfg(feature = "backtraces")]
335                    backtrace: _,
336            } => {
337                if let StdError::NotFound {
338                    kind: rhs_kind,
339                    #[cfg(feature = "backtraces")]
340                        backtrace: _,
341                } = rhs
342                {
343                    kind == rhs_kind
344                } else {
345                    false
346                }
347            }
348            StdError::ParseErr {
349                target_type,
350                msg,
351                #[cfg(feature = "backtraces")]
352                    backtrace: _,
353            } => {
354                if let StdError::ParseErr {
355                    target_type: rhs_target_type,
356                    msg: rhs_msg,
357                    #[cfg(feature = "backtraces")]
358                        backtrace: _,
359                } = rhs
360                {
361                    target_type == rhs_target_type && msg == rhs_msg
362                } else {
363                    false
364                }
365            }
366            StdError::SerializeErr {
367                source_type,
368                msg,
369                #[cfg(feature = "backtraces")]
370                    backtrace: _,
371            } => {
372                if let StdError::SerializeErr {
373                    source_type: rhs_source_type,
374                    msg: rhs_msg,
375                    #[cfg(feature = "backtraces")]
376                        backtrace: _,
377                } = rhs
378                {
379                    source_type == rhs_source_type && msg == rhs_msg
380                } else {
381                    false
382                }
383            }
384            StdError::Overflow {
385                source,
386                #[cfg(feature = "backtraces")]
387                    backtrace: _,
388            } => {
389                if let StdError::Overflow {
390                    source: rhs_source,
391                    #[cfg(feature = "backtraces")]
392                        backtrace: _,
393                } = rhs
394                {
395                    source == rhs_source
396                } else {
397                    false
398                }
399            }
400            StdError::DivideByZero {
401                source,
402                #[cfg(feature = "backtraces")]
403                    backtrace: _,
404            } => {
405                if let StdError::DivideByZero {
406                    source: rhs_source,
407                    #[cfg(feature = "backtraces")]
408                        backtrace: _,
409                } = rhs
410                {
411                    source == rhs_source
412                } else {
413                    false
414                }
415            }
416            StdError::ConversionOverflow {
417                source,
418                #[cfg(feature = "backtraces")]
419                    backtrace: _,
420            } => {
421                if let StdError::ConversionOverflow {
422                    source: rhs_source,
423                    #[cfg(feature = "backtraces")]
424                        backtrace: _,
425                } = rhs
426                {
427                    source == rhs_source
428                } else {
429                    false
430                }
431            }
432        }
433    }
434}
435
436impl From<core::str::Utf8Error> for StdError {
437    fn from(source: core::str::Utf8Error) -> Self {
438        Self::invalid_utf8(source)
439    }
440}
441
442impl From<alloc::string::FromUtf8Error> for StdError {
443    fn from(source: alloc::string::FromUtf8Error) -> Self {
444        Self::invalid_utf8(source)
445    }
446}
447
448impl From<VerificationError> for StdError {
449    fn from(source: VerificationError) -> Self {
450        Self::verification_err(source)
451    }
452}
453
454impl From<RecoverPubkeyError> for StdError {
455    fn from(source: RecoverPubkeyError) -> Self {
456        Self::recover_pubkey_err(source)
457    }
458}
459
460impl From<OverflowError> for StdError {
461    fn from(source: OverflowError) -> Self {
462        Self::overflow(source)
463    }
464}
465
466impl From<DivideByZeroError> for StdError {
467    fn from(source: DivideByZeroError) -> Self {
468        Self::divide_by_zero(source)
469    }
470}
471
472/// The return type for init, execute and query. Since the error type cannot be serialized to JSON,
473/// this is only available within the contract and its unit tests.
474///
475/// The prefix "Std" means "the standard result within the standard library". This is not the only
476/// result/error type in cosmwasm-std.
477pub type StdResult<T> = core::result::Result<T, StdError>;
478
479#[derive(Error, Debug, PartialEq, Eq)]
480pub enum OverflowOperation {
481    Add,
482    Sub,
483    Mul,
484    Pow,
485    Shr,
486    Shl,
487}
488
489impl fmt::Display for OverflowOperation {
490    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
491        write!(f, "{self:?}")
492    }
493}
494
495#[derive(Error, Debug, PartialEq, Eq)]
496#[error("Cannot {operation} with {operand1} and {operand2}")]
497pub struct OverflowError {
498    pub operation: OverflowOperation,
499    pub operand1: String,
500    pub operand2: String,
501}
502
503impl OverflowError {
504    pub fn new(
505        operation: OverflowOperation,
506        operand1: impl ToString,
507        operand2: impl ToString,
508    ) -> Self {
509        Self {
510            operation,
511            operand1: operand1.to_string(),
512            operand2: operand2.to_string(),
513        }
514    }
515}
516
517/// The error returned by [`TryFrom`] conversions that overflow, for example
518/// when converting from [`Uint256`] to [`Uint128`].
519///
520/// [`TryFrom`]: core::convert::TryFrom
521/// [`Uint256`]: crate::Uint256
522/// [`Uint128`]: crate::Uint128
523#[derive(Error, Debug, PartialEq, Eq)]
524#[error("Error converting {source_type} to {target_type} for {value}")]
525pub struct ConversionOverflowError {
526    pub source_type: &'static str,
527    pub target_type: &'static str,
528    pub value: String,
529}
530
531impl ConversionOverflowError {
532    pub fn new(
533        source_type: &'static str,
534        target_type: &'static str,
535        value: impl Into<String>,
536    ) -> Self {
537        Self {
538            source_type,
539            target_type,
540            value: value.into(),
541        }
542    }
543}
544
545#[derive(Error, Debug, PartialEq, Eq)]
546#[error("Cannot divide {operand} by zero")]
547pub struct DivideByZeroError {
548    pub operand: String,
549}
550
551impl DivideByZeroError {
552    pub fn new(operand: impl ToString) -> Self {
553        Self {
554            operand: operand.to_string(),
555        }
556    }
557}
558
559#[derive(Error, Debug, PartialEq, Eq)]
560pub enum DivisionError {
561    #[error("Divide by zero")]
562    DivideByZero,
563
564    #[error("Overflow in division")]
565    Overflow,
566}
567
568#[derive(Error, Debug, PartialEq, Eq)]
569pub enum CheckedMultiplyFractionError {
570    #[error("{0}")]
571    DivideByZero(#[from] DivideByZeroError),
572
573    #[error("{0}")]
574    ConversionOverflow(#[from] ConversionOverflowError),
575
576    #[error("{0}")]
577    Overflow(#[from] OverflowError),
578}
579
580#[derive(Error, Debug, PartialEq, Eq)]
581pub enum CheckedMultiplyRatioError {
582    #[error("Denominator must not be zero")]
583    DivideByZero,
584
585    #[error("Multiplication overflow")]
586    Overflow,
587}
588
589#[derive(Error, Debug, PartialEq, Eq)]
590pub enum CheckedFromRatioError {
591    #[error("Denominator must not be zero")]
592    DivideByZero,
593
594    #[error("Overflow")]
595    Overflow,
596}
597
598#[derive(Error, Debug, PartialEq, Eq)]
599#[error("Round up operation failed because of overflow")]
600pub struct RoundUpOverflowError;
601
602#[derive(Error, Debug, PartialEq, Eq)]
603#[error("Round down operation failed because of overflow")]
604pub struct RoundDownOverflowError;
605
606#[derive(Error, Debug, PartialEq, Eq)]
607pub enum CoinsError {
608    #[error("Duplicate denom")]
609    DuplicateDenom,
610}
611
612impl From<CoinsError> for StdError {
613    fn from(value: CoinsError) -> Self {
614        Self::generic_err(format!("Creating Coins: {value}"))
615    }
616}
617
618#[derive(Error, Debug, PartialEq, Eq)]
619pub enum CoinFromStrError {
620    #[error("Missing denominator")]
621    MissingDenom,
622    #[error("Missing amount or non-digit characters in amount")]
623    MissingAmount,
624    #[error("Invalid amount: {0}")]
625    InvalidAmount(core::num::ParseIntError),
626}
627
628impl From<core::num::ParseIntError> for CoinFromStrError {
629    fn from(value: core::num::ParseIntError) -> Self {
630        Self::InvalidAmount(value)
631    }
632}
633
634impl From<CoinFromStrError> for StdError {
635    fn from(value: CoinFromStrError) -> Self {
636        Self::generic_err(format!("Parsing Coin: {value}"))
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643    use core::str;
644
645    // constructors
646
647    // example of reporting contract errors with format!
648    #[test]
649    fn generic_err_owned() {
650        let guess = 7;
651        let error = StdError::generic_err(format!("{guess} is too low"));
652        match error {
653            StdError::GenericErr { msg, .. } => {
654                assert_eq!(msg, String::from("7 is too low"));
655            }
656            e => panic!("unexpected error, {e:?}"),
657        }
658    }
659
660    // example of reporting static contract errors
661    #[test]
662    fn generic_err_ref() {
663        let error = StdError::generic_err("not implemented");
664        match error {
665            StdError::GenericErr { msg, .. } => assert_eq!(msg, "not implemented"),
666            e => panic!("unexpected error, {e:?}"),
667        }
668    }
669
670    #[test]
671    fn invalid_base64_works_for_strings() {
672        let error = StdError::invalid_base64("my text");
673        match error {
674            StdError::InvalidBase64 { msg, .. } => {
675                assert_eq!(msg, "my text");
676            }
677            _ => panic!("expect different error"),
678        }
679    }
680
681    #[test]
682    fn invalid_base64_works_for_errors() {
683        let original = base64::DecodeError::InvalidLength;
684        let error = StdError::invalid_base64(original);
685        match error {
686            StdError::InvalidBase64 { msg, .. } => {
687                assert_eq!(msg, "Encoded text cannot have a 6-bit remainder.");
688            }
689            _ => panic!("expect different error"),
690        }
691    }
692
693    #[test]
694    fn invalid_data_size_works() {
695        let error = StdError::invalid_data_size(31, 14);
696        match error {
697            StdError::InvalidDataSize {
698                expected, actual, ..
699            } => {
700                assert_eq!(expected, 31);
701                assert_eq!(actual, 14);
702            }
703            _ => panic!("expect different error"),
704        }
705    }
706
707    #[test]
708    fn invalid_hex_works_for_strings() {
709        let error = StdError::invalid_hex("my text");
710        match error {
711            StdError::InvalidHex { msg, .. } => {
712                assert_eq!(msg, "my text");
713            }
714            _ => panic!("expect different error"),
715        }
716    }
717
718    #[test]
719    fn invalid_hex_works_for_errors() {
720        let original = hex::FromHexError::OddLength;
721        let error = StdError::invalid_hex(original);
722        match error {
723            StdError::InvalidHex { msg, .. } => {
724                assert_eq!(msg, "Odd number of digits");
725            }
726            _ => panic!("expect different error"),
727        }
728    }
729
730    #[test]
731    fn invalid_utf8_works_for_strings() {
732        let error = StdError::invalid_utf8("my text");
733        match error {
734            StdError::InvalidUtf8 { msg, .. } => {
735                assert_eq!(msg, "my text");
736            }
737            _ => panic!("expect different error"),
738        }
739    }
740
741    #[test]
742    fn invalid_utf8_works_for_errors() {
743        let original = String::from_utf8(vec![0x80]).unwrap_err();
744        let error = StdError::invalid_utf8(original);
745        match error {
746            StdError::InvalidUtf8 { msg, .. } => {
747                assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0");
748            }
749            _ => panic!("expect different error"),
750        }
751    }
752
753    #[test]
754    fn not_found_works() {
755        let error = StdError::not_found("gold");
756        match error {
757            StdError::NotFound { kind, .. } => assert_eq!(kind, "gold"),
758            _ => panic!("expect different error"),
759        }
760    }
761
762    #[test]
763    fn parse_err_works() {
764        let error = StdError::parse_err("Book", "Missing field: title");
765        match error {
766            StdError::ParseErr {
767                target_type, msg, ..
768            } => {
769                assert_eq!(target_type, "Book");
770                assert_eq!(msg, "Missing field: title");
771            }
772            _ => panic!("expect different error"),
773        }
774    }
775
776    #[test]
777    fn serialize_err_works() {
778        let error = StdError::serialize_err("Book", "Content too long");
779        match error {
780            StdError::SerializeErr {
781                source_type, msg, ..
782            } => {
783                assert_eq!(source_type, "Book");
784                assert_eq!(msg, "Content too long");
785            }
786            _ => panic!("expect different error"),
787        }
788    }
789
790    #[test]
791    fn underflow_works_for_u128() {
792        let error =
793            StdError::overflow(OverflowError::new(OverflowOperation::Sub, 123u128, 456u128));
794        match error {
795            StdError::Overflow {
796                source:
797                    OverflowError {
798                        operation,
799                        operand1,
800                        operand2,
801                    },
802                ..
803            } => {
804                assert_eq!(operation, OverflowOperation::Sub);
805                assert_eq!(operand1, "123");
806                assert_eq!(operand2, "456");
807            }
808            _ => panic!("expect different error"),
809        }
810    }
811
812    #[test]
813    fn overflow_works_for_i64() {
814        let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub, 777i64, 1234i64));
815        match error {
816            StdError::Overflow {
817                source:
818                    OverflowError {
819                        operation,
820                        operand1,
821                        operand2,
822                    },
823                ..
824            } => {
825                assert_eq!(operation, OverflowOperation::Sub);
826                assert_eq!(operand1, "777");
827                assert_eq!(operand2, "1234");
828            }
829            _ => panic!("expect different error"),
830        }
831    }
832
833    #[test]
834    fn divide_by_zero_works() {
835        let error = StdError::divide_by_zero(DivideByZeroError::new(123u128));
836        match error {
837            StdError::DivideByZero {
838                source: DivideByZeroError { operand },
839                ..
840            } => assert_eq!(operand, "123"),
841            _ => panic!("expect different error"),
842        }
843    }
844
845    #[test]
846    fn implements_debug() {
847        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub, 3, 5));
848        let embedded = format!("Debug: {error:?}");
849        #[cfg(not(feature = "backtraces"))]
850        let expected = r#"Debug: Overflow { source: OverflowError { operation: Sub, operand1: "3", operand2: "5" } }"#;
851        #[cfg(feature = "backtraces")]
852        let expected = r#"Debug: Overflow { source: OverflowError { operation: Sub, operand1: "3", operand2: "5" }, backtrace: <disabled> }"#;
853        assert_eq!(embedded, expected);
854    }
855
856    #[test]
857    fn implements_display() {
858        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub, 3, 5));
859        let embedded = format!("Display: {error}");
860        assert_eq!(embedded, "Display: Overflow: Cannot Sub with 3 and 5");
861    }
862
863    #[test]
864    fn implements_partial_eq() {
865        let u1 = StdError::from(OverflowError::new(OverflowOperation::Sub, 3, 5));
866        let u2 = StdError::from(OverflowError::new(OverflowOperation::Sub, 3, 5));
867        let u3 = StdError::from(OverflowError::new(OverflowOperation::Sub, 3, 7));
868        let s1 = StdError::serialize_err("Book", "Content too long");
869        let s2 = StdError::serialize_err("Book", "Content too long");
870        let s3 = StdError::serialize_err("Book", "Title too long");
871        assert_eq!(u1, u2);
872        assert_ne!(u1, u3);
873        assert_ne!(u1, s1);
874        assert_eq!(s1, s2);
875        assert_ne!(s1, s3);
876    }
877
878    #[test]
879    fn from_std_str_utf8error_works() {
880        let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]);
881        let error: StdError = str::from_utf8(&broken).unwrap_err().into();
882        match error {
883            StdError::InvalidUtf8 { msg, .. } => {
884                assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
885            }
886            err => panic!("Unexpected error: {err:?}"),
887        }
888    }
889
890    #[test]
891    fn from_std_string_from_utf8error_works() {
892        let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec())
893            .unwrap_err()
894            .into();
895        match error {
896            StdError::InvalidUtf8 { msg, .. } => {
897                assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
898            }
899            err => panic!("Unexpected error: {err:?}"),
900        }
901    }
902}