1use crate::corety::AzString;
7
8#[derive(Debug, Copy, Clone, Eq, PartialEq)]
10pub struct InvalidValueErr<'a>(pub &'a str);
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14#[repr(C)]
15pub struct InvalidValueErrOwned {
16 pub value: AzString,
17}
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
24#[repr(C)]
25pub enum ParseFloatError {
26 Empty,
28 Invalid,
30}
31
32impl ParseFloatError {
33 fn from_std(e: &core::num::ParseFloatError) -> Self {
35 let empty_err = "".parse::<f32>().unwrap_err();
38 if *e == empty_err {
39 Self::Empty
40 } else {
41 Self::Invalid
42 }
43 }
44
45 #[must_use]
47 pub fn to_std(&self) -> core::num::ParseFloatError {
48 match self {
49 Self::Empty => "".parse::<f32>().unwrap_err(),
50 Self::Invalid => "x".parse::<f32>().unwrap_err(),
51 }
52 }
53}
54
55impl core::fmt::Display for ParseFloatError {
56 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57 match self {
58 Self::Empty => write!(f, "cannot parse float from empty string"),
59 Self::Invalid => write!(f, "invalid float literal"),
60 }
61 }
62}
63
64impl From<core::num::ParseFloatError> for ParseFloatError {
65 fn from(e: core::num::ParseFloatError) -> Self {
66 Self::from_std(&e)
67 }
68}
69
70#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
75#[repr(C)]
76pub enum ParseIntError {
77 Empty,
79 InvalidDigit,
81 PosOverflow,
83 NegOverflow,
85 Zero,
87}
88
89impl ParseIntError {
90 const fn from_std(e: &core::num::ParseIntError) -> Self {
92 use core::num::IntErrorKind;
93 match e.kind() {
94 IntErrorKind::Empty => Self::Empty,
95 IntErrorKind::PosOverflow => Self::PosOverflow,
96 IntErrorKind::NegOverflow => Self::NegOverflow,
97 IntErrorKind::Zero => Self::Zero,
98 _ => Self::InvalidDigit, }
100 }
101
102 #[must_use]
104 pub fn to_std(&self) -> core::num::ParseIntError {
105 match self {
106 Self::Empty => "".parse::<i32>().unwrap_err(),
107 Self::InvalidDigit => "x".parse::<i32>().unwrap_err(),
108 Self::PosOverflow => "99999999999999999999".parse::<i32>().unwrap_err(),
109 Self::NegOverflow => "-99999999999999999999".parse::<i32>().unwrap_err(),
110 Self::Zero => {
111 "x".parse::<i32>().unwrap_err()
114 }
115 }
116 }
117}
118
119impl core::fmt::Display for ParseIntError {
120 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121 match self {
122 Self::Empty => write!(f, "cannot parse integer from empty string"),
123 Self::InvalidDigit => write!(f, "invalid digit found in string"),
124 Self::PosOverflow => write!(f, "number too large to fit in target type"),
125 Self::NegOverflow => write!(f, "number too small to fit in target type"),
126 Self::Zero => write!(f, "number would be zero for non-zero type"),
127 }
128 }
129}
130
131impl From<core::num::ParseIntError> for ParseIntError {
132 fn from(e: core::num::ParseIntError) -> Self {
133 Self::from_std(&e)
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
140#[repr(C)]
141pub struct ParseFloatErrorWithInput {
142 pub error: ParseFloatError,
143 pub input: AzString,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
148#[repr(C)]
149pub struct WrongComponentCountError {
150 pub expected: usize,
151 pub got: usize,
152 pub input: AzString,
153}
154
155impl InvalidValueErr<'_> {
156 #[must_use]
157 pub fn to_contained(&self) -> InvalidValueErrOwned {
158 InvalidValueErrOwned {
159 value: self.0.to_string().into(),
160 }
161 }
162}
163
164impl InvalidValueErrOwned {
165 #[must_use]
166 pub fn to_shared(&self) -> InvalidValueErr<'_> {
167 InvalidValueErr(self.value.as_str())
168 }
169}
170
171#[cfg(test)]
172#[allow(clippy::too_many_lines)]
173mod autotest_generated {
174 use core::num::IntErrorKind;
175 use std::{
176 collections::hash_map::DefaultHasher,
177 hash::{Hash, Hasher},
178 };
179
180 use super::*;
181
182 fn float_kind<T>(s: &str) -> ParseFloatError
189 where
190 T: core::str::FromStr<Err = core::num::ParseFloatError>,
191 {
192 match s.parse::<T>() {
193 Ok(_) => panic!("expected {s:?} to FAIL to parse as a float"),
194 Err(e) => ParseFloatError::from_std(&e),
195 }
196 }
197
198 fn int_kind<T>(s: &str) -> ParseIntError
199 where
200 T: core::str::FromStr<Err = core::num::ParseIntError>,
201 {
202 match s.parse::<T>() {
203 Ok(_) => panic!("expected {s:?} to FAIL to parse as an integer"),
204 Err(e) => ParseIntError::from_std(&e),
205 }
206 }
207
208 fn std_float_err(s: &str) -> core::num::ParseFloatError {
209 s.parse::<f32>().expect_err("input should not parse")
210 }
211
212 fn std_int_err(s: &str) -> core::num::ParseIntError {
213 s.parse::<i32>().expect_err("input should not parse")
214 }
215
216 fn hash_of<T: Hash>(v: &T) -> u64 {
217 let mut h = DefaultHasher::new();
218 v.hash(&mut h);
219 h.finish()
220 }
221
222 const ALL_FLOAT: [ParseFloatError; 2] = [ParseFloatError::Empty, ParseFloatError::Invalid];
223
224 const ALL_INT: [ParseIntError; 5] = [
225 ParseIntError::Empty,
226 ParseIntError::InvalidDigit,
227 ParseIntError::PosOverflow,
228 ParseIntError::NegOverflow,
229 ParseIntError::Zero,
230 ];
231
232 #[test]
237 fn float_from_std_empty_string_maps_to_empty() {
238 assert_eq!(float_kind::<f32>(""), ParseFloatError::Empty);
239 assert_eq!(float_kind::<f64>(""), ParseFloatError::Empty);
243 }
244
245 #[test]
246 fn float_from_std_blank_input_is_invalid_not_empty() {
247 for s in [
250 " ", " ", "\t", "\n", "\r\n", "\u{a0}", "\u{feff}", "\u{200b}", "\u{0}", ] {
255 assert_eq!(
256 float_kind::<f32>(s),
257 ParseFloatError::Invalid,
258 "blank-ish input {s:?} must be Invalid, not Empty"
259 );
260 }
261 }
262
263 #[test]
264 fn float_from_std_malformed_inputs_are_invalid() {
265 for s in [
266 "x", ".", "-", "+", "e", "e5", "5e", "1.2.3", "0x1f", "1,5", "--1", "++1", "1 ", " 1",
267 "1_0", "NaNx", "infinit", "1/2", "abc", "1e", "1e+", "-.",
268 ] {
269 assert_eq!(
270 float_kind::<f32>(s),
271 ParseFloatError::Invalid,
272 "malformed input {s:?} should be Invalid"
273 );
274 }
275 }
276
277 #[test]
278 fn float_from_std_non_ascii_digits_are_invalid() {
279 for s in [
280 "١٢٣", "123", "½", "😀",
284 "٣.٥",
285 "1\u{301}", "Ⅻ", ] {
288 assert_eq!(
289 float_kind::<f32>(s),
290 ParseFloatError::Invalid,
291 "unicode input {s:?} should be Invalid"
292 );
293 }
294 }
295
296 #[test]
297 fn float_from_std_huge_malformed_input_does_not_panic_or_hang() {
298 let mut huge = "9".repeat(100_000);
299 huge.push('x');
300 assert_eq!(float_kind::<f32>(&huge), ParseFloatError::Invalid);
301
302 let mut zeros = "0".repeat(100_000);
304 zeros.push_str("..");
305 assert_eq!(float_kind::<f32>(&zeros), ParseFloatError::Invalid);
306 }
307
308 #[test]
309 fn float_from_impl_agrees_with_from_std() {
310 for s in ["", " ", "x", "1.2.3", "😀"] {
311 let a: ParseFloatError = std_float_err(s).into();
312 let b = ParseFloatError::from_std(&std_float_err(s));
313 assert_eq!(a, b, "From<> and from_std disagree for {s:?}");
314 }
315 }
316
317 #[test]
322 fn float_magnitude_overflow_saturates_to_infinity_instead_of_erroring() {
323 assert!(
326 "1e400"
327 .parse::<f32>()
328 .expect("saturates, does not error")
329 .is_infinite(),
330 "huge positive exponent should saturate to +inf"
331 );
332 assert!("-1e400"
333 .parse::<f32>()
334 .expect("saturates")
335 .is_sign_negative());
336 assert_eq!("1e-400".parse::<f32>().expect("underflows to zero"), 0.0);
337
338 let huge = "9".repeat(100_000);
339 assert!(huge.parse::<f32>().expect("saturates").is_infinite());
340 }
341
342 #[test]
343 fn float_nan_and_inf_literals_parse_and_never_error() {
344 assert!("nan".parse::<f32>().expect("nan is valid").is_nan());
345 assert!("NaN".parse::<f32>().expect("NaN is valid").is_nan());
346 assert!("inf".parse::<f32>().expect("inf is valid").is_infinite());
347 assert!("infinity"
348 .parse::<f32>()
349 .expect("infinity is valid")
350 .is_infinite());
351 assert!("-inf"
352 .parse::<f32>()
353 .expect("-inf is valid")
354 .is_sign_negative());
355 assert!("-0".parse::<f32>().expect("-0 is valid").is_sign_negative());
356 }
357
358 #[test]
363 fn float_to_std_returns_the_matching_std_error() {
364 assert_eq!(ParseFloatError::Empty.to_std(), std_float_err(""));
365 assert_eq!(ParseFloatError::Invalid.to_std(), std_float_err("x"));
366 }
367
368 #[test]
369 fn float_to_std_variants_stay_distinct() {
370 assert_ne!(
372 ParseFloatError::Empty.to_std(),
373 ParseFloatError::Invalid.to_std()
374 );
375 }
376
377 #[test]
378 fn float_to_std_is_deterministic() {
379 for v in ALL_FLOAT {
380 assert_eq!(v.to_std(), v.to_std(), "to_std() must be stable for {v:?}");
381 }
382 }
383
384 #[test]
385 fn float_round_trip_encode_decode_is_identity() {
386 for v in ALL_FLOAT {
387 assert_eq!(
388 ParseFloatError::from_std(&v.to_std()),
389 v,
390 "round-trip lost {v:?}"
391 );
392 assert_eq!(ParseFloatError::from(v.to_std()), v);
393 }
394 }
395
396 #[test]
401 fn int_from_std_empty_string_maps_to_empty_for_every_width() {
402 assert_eq!(int_kind::<i8>(""), ParseIntError::Empty);
403 assert_eq!(int_kind::<u8>(""), ParseIntError::Empty);
404 assert_eq!(int_kind::<i32>(""), ParseIntError::Empty);
405 assert_eq!(int_kind::<u128>(""), ParseIntError::Empty);
406 assert_eq!(int_kind::<usize>(""), ParseIntError::Empty);
407 assert_eq!(int_kind::<isize>(""), ParseIntError::Empty);
408 }
409
410 #[test]
411 fn int_from_std_malformed_inputs_are_invalid_digit() {
412 for s in [
413 "x",
414 " ",
415 " ",
416 "\t",
417 "+",
418 "-",
419 "+-1",
420 "--1",
421 "1 ",
422 " 1",
423 "1_000",
424 "0x10",
425 "1.0",
426 "1e3",
427 "abc",
428 "\u{0}",
429 "1\u{0}",
430 "٣",
431 "123",
432 "😀",
433 "½",
434 ",",
435 "1,000",
436 ] {
437 assert_eq!(
438 int_kind::<i32>(s),
439 ParseIntError::InvalidDigit,
440 "malformed input {s:?} should be InvalidDigit"
441 );
442 }
443 }
444
445 #[test]
446 fn int_from_std_negative_into_unsigned_is_invalid_digit_not_neg_overflow() {
447 assert_eq!(int_kind::<u32>("-1"), ParseIntError::InvalidDigit);
450 assert_eq!(int_kind::<u8>("-0"), ParseIntError::InvalidDigit);
451 assert_eq!(
452 int_kind::<u128>("-99999999999999999999999999"),
453 ParseIntError::InvalidDigit
454 );
455 }
456
457 #[test]
458 fn int_from_std_positive_overflow_boundaries() {
459 assert_eq!(i32::MAX.to_string().parse::<i32>(), Ok(i32::MAX));
461 assert_eq!(int_kind::<i32>("2147483648"), ParseIntError::PosOverflow);
462 assert_eq!(u8::MAX.to_string().parse::<u8>(), Ok(u8::MAX));
463 assert_eq!(int_kind::<u8>("256"), ParseIntError::PosOverflow);
464 assert_eq!(i8::MAX.to_string().parse::<i8>(), Ok(i8::MAX));
465 assert_eq!(int_kind::<i8>("128"), ParseIntError::PosOverflow);
466 assert_eq!(
467 int_kind::<u128>("340282366920938463463374607431768211456"),
468 ParseIntError::PosOverflow
469 );
470 }
471
472 #[test]
473 fn int_from_std_negative_overflow_boundaries() {
474 assert_eq!(i32::MIN.to_string().parse::<i32>(), Ok(i32::MIN));
475 assert_eq!(int_kind::<i32>("-2147483649"), ParseIntError::NegOverflow);
476 assert_eq!(i8::MIN.to_string().parse::<i8>(), Ok(i8::MIN));
477 assert_eq!(int_kind::<i8>("-129"), ParseIntError::NegOverflow);
478 assert_eq!(
479 int_kind::<i128>("-99999999999999999999999999999999999999999"),
480 ParseIntError::NegOverflow
481 );
482 }
483
484 #[test]
485 fn int_from_std_huge_digit_runs_overflow_without_panic() {
486 let huge = "9".repeat(10_000);
487 assert_eq!(int_kind::<i32>(&huge), ParseIntError::PosOverflow);
488 assert_eq!(int_kind::<u128>(&huge), ParseIntError::PosOverflow);
489
490 let huge_neg = format!("-{huge}");
491 assert_eq!(int_kind::<i64>(&huge_neg), ParseIntError::NegOverflow);
492 }
493
494 #[test]
495 fn int_leading_zeros_do_not_produce_a_false_overflow() {
496 let padded = format!("{}5", "0".repeat(10_000));
499 assert_eq!(padded.parse::<i32>(), Ok(5));
500 assert_eq!("0000000000000000000000000000005".parse::<i32>(), Ok(5));
501 }
502
503 #[test]
504 fn int_from_std_zero_variant_is_reachable_via_nonzero_types() {
505 assert_eq!(int_kind::<core::num::NonZeroU8>("0"), ParseIntError::Zero);
508 assert_eq!(int_kind::<core::num::NonZeroI32>("0"), ParseIntError::Zero);
509 assert_eq!(
510 int_kind::<core::num::NonZeroUsize>("0"),
511 ParseIntError::Zero
512 );
513 assert_eq!(int_kind::<core::num::NonZeroU8>(""), ParseIntError::Empty);
515 assert_eq!(
516 int_kind::<core::num::NonZeroU8>("x"),
517 ParseIntError::InvalidDigit
518 );
519 assert_eq!(
520 int_kind::<core::num::NonZeroU8>("256"),
521 ParseIntError::PosOverflow
522 );
523 }
524
525 #[test]
526 fn int_from_impl_agrees_with_from_std() {
527 for s in [
528 "",
529 "x",
530 "99999999999999999999",
531 "-99999999999999999999",
532 "😀",
533 ] {
534 let a: ParseIntError = std_int_err(s).into();
535 let b = ParseIntError::from_std(&std_int_err(s));
536 assert_eq!(a, b, "From<> and from_std disagree for {s:?}");
537 }
538 }
539
540 #[test]
545 fn int_to_std_maps_each_variant_onto_the_expected_std_kind() {
546 assert!(matches!(
547 ParseIntError::Empty.to_std().kind(),
548 IntErrorKind::Empty
549 ));
550 assert!(matches!(
551 ParseIntError::InvalidDigit.to_std().kind(),
552 IntErrorKind::InvalidDigit
553 ));
554 assert!(matches!(
555 ParseIntError::PosOverflow.to_std().kind(),
556 IntErrorKind::PosOverflow
557 ));
558 assert!(matches!(
559 ParseIntError::NegOverflow.to_std().kind(),
560 IntErrorKind::NegOverflow
561 ));
562 assert!(matches!(
564 ParseIntError::Zero.to_std().kind(),
565 IntErrorKind::InvalidDigit
566 ));
567 }
568
569 #[test]
570 fn int_to_std_is_deterministic() {
571 for v in ALL_INT {
572 assert_eq!(v.to_std(), v.to_std(), "to_std() must be stable for {v:?}");
573 }
574 }
575
576 #[test]
577 fn int_round_trip_encode_decode_is_identity_except_for_zero() {
578 for v in [
579 ParseIntError::Empty,
580 ParseIntError::InvalidDigit,
581 ParseIntError::PosOverflow,
582 ParseIntError::NegOverflow,
583 ] {
584 assert_eq!(
585 ParseIntError::from_std(&v.to_std()),
586 v,
587 "round-trip lost {v:?}"
588 );
589 assert_eq!(ParseIntError::from(v.to_std()), v);
590 }
591
592 assert_eq!(
596 ParseIntError::from_std(&ParseIntError::Zero.to_std()),
597 ParseIntError::InvalidDigit,
598 "Zero round-trip is documented as lossy"
599 );
600 }
601
602 #[test]
603 fn int_to_std_variants_stay_distinct_where_they_must() {
604 let empty = ParseIntError::Empty.to_std();
605 let invalid = ParseIntError::InvalidDigit.to_std();
606 let pos = ParseIntError::PosOverflow.to_std();
607 let neg = ParseIntError::NegOverflow.to_std();
608 assert_ne!(empty, invalid);
609 assert_ne!(invalid, pos);
610 assert_ne!(pos, neg);
611 assert_ne!(empty, neg);
612 assert_eq!(ParseIntError::Zero.to_std(), invalid);
614 }
615
616 #[test]
621 fn display_output_is_non_empty_and_unique_per_variant() {
622 let float_msgs: Vec<String> = ALL_FLOAT.iter().map(ToString::to_string).collect();
623 for m in &float_msgs {
624 assert!(!m.is_empty(), "float Display must not be empty");
625 }
626 assert_ne!(
627 float_msgs[0], float_msgs[1],
628 "float variants must be distinguishable"
629 );
630
631 let int_msgs: Vec<String> = ALL_INT.iter().map(ToString::to_string).collect();
632 for m in &int_msgs {
633 assert!(!m.is_empty(), "int Display must not be empty");
634 }
635 for i in 0..int_msgs.len() {
636 for j in (i + 1)..int_msgs.len() {
637 assert_ne!(
638 int_msgs[i], int_msgs[j],
639 "int variants {i}/{j} share a message"
640 );
641 }
642 }
643 }
644
645 #[test]
646 fn display_mirrors_the_std_error_messages() {
647 assert_eq!(
650 ParseFloatError::Empty.to_string(),
651 std_float_err("").to_string()
652 );
653 assert_eq!(
654 ParseFloatError::Invalid.to_string(),
655 std_float_err("x").to_string()
656 );
657
658 assert_eq!(
659 ParseIntError::Empty.to_string(),
660 std_int_err("").to_string()
661 );
662 assert_eq!(
663 ParseIntError::InvalidDigit.to_string(),
664 std_int_err("x").to_string()
665 );
666 assert_eq!(
667 ParseIntError::PosOverflow.to_string(),
668 std_int_err("99999999999999999999").to_string()
669 );
670 assert_eq!(
671 ParseIntError::NegOverflow.to_string(),
672 std_int_err("-99999999999999999999").to_string()
673 );
674 let std_zero = "0"
677 .parse::<core::num::NonZeroU8>()
678 .expect_err("parsing 0 as NonZeroU8 must fail");
679 assert!(matches!(std_zero.kind(), IntErrorKind::Zero));
680 assert_eq!(ParseIntError::Zero.to_string(), std_zero.to_string());
681 }
682
683 #[test]
684 fn display_with_formatter_flags_does_not_panic() {
685 for v in ALL_INT {
686 let msg = v.to_string();
687 let padded = format!("{v:>60}");
688 assert!(!padded.is_empty());
689 assert!(
690 padded.contains(&msg),
691 "padding must not corrupt the message"
692 );
693 assert!(!format!("{v:.3}").is_empty());
695 assert!(!format!("{v:*^10}").is_empty());
696 assert!(!format!("{v:#?}").is_empty());
697 }
698 for v in ALL_FLOAT {
699 assert!(!format!("{v:>60}").is_empty());
700 assert!(!format!("{v:.1}").is_empty());
701 assert!(!format!("{v:?}").is_empty());
702 }
703 }
704
705 #[test]
706 fn debug_output_names_the_variant() {
707 assert_eq!(format!("{:?}", ParseFloatError::Empty), "Empty");
708 assert_eq!(format!("{:?}", ParseIntError::PosOverflow), "PosOverflow");
709 assert_eq!(format!("{:?}", ParseIntError::Zero), "Zero");
710 }
711
712 #[test]
717 fn error_enums_have_consistent_eq_hash_and_ord() {
718 for (i, a) in ALL_INT.iter().enumerate() {
719 assert_eq!(
720 hash_of(a),
721 hash_of(&ALL_INT[i]),
722 "equal values must hash equal"
723 );
724 for (j, b) in ALL_INT.iter().enumerate() {
725 assert_eq!(a == b, i == j, "only identical variants may compare equal");
726 assert_eq!(a.cmp(b), i.cmp(&j), "Ord must follow declaration order");
727 }
728 }
729 assert!(ParseFloatError::Empty < ParseFloatError::Invalid);
730 assert_eq!(
731 hash_of(&ParseFloatError::Empty),
732 hash_of(&ParseFloatError::Empty)
733 );
734 assert_ne!(ParseFloatError::Empty, ParseFloatError::Invalid);
735 }
736
737 #[test]
738 fn error_enums_are_copy_and_survive_a_sort() {
739 let mut v = [
740 ParseIntError::Zero,
741 ParseIntError::Empty,
742 ParseIntError::NegOverflow,
743 ParseIntError::InvalidDigit,
744 ParseIntError::PosOverflow,
745 ];
746 v.sort_unstable();
747 assert_eq!(v, ALL_INT);
748
749 let a = ParseIntError::Zero;
750 let b = a; assert_eq!(a, b);
752 }
753
754 #[test]
759 fn invalid_value_err_round_trips_through_owned() {
760 for s in [
761 "",
762 "a",
763 "border-radius",
764 " ",
765 "\n\t",
766 "börder-radiüs 😀",
767 "٣.٥",
768 "\u{feff}leading-bom",
769 "trailing-nul\u{0}",
770 "a\u{0}b",
771 ] {
772 let shared = InvalidValueErr(s);
773 let owned = shared.to_contained();
774 assert_eq!(owned.value.as_str(), s, "to_contained lost {s:?}");
775 assert_eq!(owned.to_shared(), shared, "round-trip changed {s:?}");
776 assert_eq!(owned.to_shared().0, s);
777 }
778 }
779
780 #[test]
781 fn invalid_value_err_empty_string_is_not_confused_with_default() {
782 let owned = InvalidValueErr("").to_contained();
783 assert_eq!(owned.value, AzString::default());
784 assert!(owned.value.as_str().is_empty());
785 assert_eq!(owned.to_shared(), InvalidValueErr(""));
786 assert_eq!(
787 owned,
788 InvalidValueErrOwned {
789 value: AzString::default()
790 }
791 );
792 }
793
794 #[test]
795 fn invalid_value_err_preserves_interior_nul_bytes() {
796 let s = "a\u{0}b";
799 let owned = InvalidValueErr(s).to_contained();
800 assert_eq!(owned.value.as_bytes(), b"a\0b");
801 assert_eq!(owned.value.as_str().len(), 3);
802 assert_eq!(owned.to_shared().0.len(), 3);
803 }
804
805 #[test]
806 fn to_contained_deep_copies_and_outlives_its_source() {
807 let owned = {
808 let src = String::from("temporary-buffer");
809 let copied = InvalidValueErr(src.as_str()).to_contained();
810 assert!(
811 !core::ptr::eq(copied.value.as_str().as_ptr(), src.as_str().as_ptr()),
812 "to_contained must copy, not alias the borrowed input"
813 );
814 drop(src);
815 copied
816 };
817 assert_eq!(owned.value.as_str(), "temporary-buffer");
818 }
819
820 #[test]
821 fn to_shared_borrows_the_owned_buffer_without_copying() {
822 let owned = InvalidValueErr("shared-buffer").to_contained();
823 let shared = owned.to_shared();
824 assert!(
825 core::ptr::eq(shared.0.as_ptr(), owned.value.as_str().as_ptr()),
826 "to_shared must borrow the existing buffer"
827 );
828 assert_eq!(owned.to_shared(), owned.to_shared());
830 }
831
832 #[test]
833 fn invalid_value_err_handles_a_huge_payload() {
834 let big = "ü".repeat(100_000); let owned = InvalidValueErr(big.as_str()).to_contained();
836 assert_eq!(owned.value.as_str().len(), big.len());
837 assert_eq!(owned.value.as_bytes().len(), 200_000);
838 assert_eq!(owned.to_shared().0, big.as_str());
839 assert_eq!(owned.clone(), owned);
840 }
841
842 #[test]
843 fn invalid_value_err_owned_equality_is_by_content() {
844 let a = InvalidValueErr("x").to_contained();
845 let b = InvalidValueErrOwned {
846 value: AzString::from("x"),
847 };
848 let c = InvalidValueErrOwned {
849 value: AzString::from("y"),
850 };
851 assert_eq!(a, b);
852 assert_ne!(a, c);
853 assert_eq!(a.clone(), a);
854 assert_eq!(a.to_shared(), b.to_shared());
855 }
856
857 #[test]
862 fn parse_float_error_with_input_keeps_error_and_input_together() {
863 let input = "1.2.3";
864 let err = ParseFloatErrorWithInput {
865 error: ParseFloatError::from(std_float_err(input)),
866 input: AzString::from(input),
867 };
868 assert_eq!(err.error, ParseFloatError::Invalid);
869 assert_eq!(err.input.as_str(), input);
870 assert_eq!(err.clone(), err);
871
872 let empty = ParseFloatErrorWithInput {
873 error: ParseFloatError::from(std_float_err("")),
874 input: AzString::default(),
875 };
876 assert_eq!(empty.error, ParseFloatError::Empty);
877 assert!(empty.input.as_str().is_empty());
878 assert_ne!(empty, err);
879 assert!(!format!("{err:?}").is_empty());
880 }
881
882 #[test]
883 fn wrong_component_count_error_survives_usize_extremes() {
884 let e = WrongComponentCountError {
885 expected: usize::MAX,
886 got: 0,
887 input: AzString::from("rgba(1)"),
888 };
889 assert_eq!(e.expected, usize::MAX);
890 assert_eq!(e.got, 0);
891 assert_eq!(e.input.as_str(), "rgba(1)");
892 assert_eq!(e.clone(), e);
893 assert!(!format!("{e:?}").is_empty());
894
895 let same_but_got_max = WrongComponentCountError {
896 expected: usize::MAX,
897 got: usize::MAX,
898 input: AzString::from("rgba(1)"),
899 };
900 assert_ne!(e, same_but_got_max, "`got` participates in equality");
901
902 let zeroed = WrongComponentCountError {
904 expected: 0,
905 got: 0,
906 input: AzString::default(),
907 };
908 assert_eq!(zeroed.expected, zeroed.got);
909 assert!(zeroed.input.as_str().is_empty());
910 }
911}