1use crate::corety::AzString;
11use alloc::string::{String, ToString};
12
13use crate::props::formatter::PrintAsCssValue;
14
15#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[repr(C)]
24#[derive(Default)]
28pub enum LayoutWritingMode {
29 #[default]
31 HorizontalTb,
32 VerticalRl,
34 VerticalLr,
37}
38
39impl LayoutWritingMode {
40 #[must_use]
42 pub const fn is_vertical(self) -> bool {
43 matches!(self, Self::VerticalRl | Self::VerticalLr)
44 }
45}
46
47impl core::fmt::Debug for LayoutWritingMode {
48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49 write!(f, "{}", self.print_as_css_value())
50 }
51}
52
53impl core::fmt::Display for LayoutWritingMode {
54 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55 write!(f, "{}", self.print_as_css_value())
56 }
57}
58
59impl PrintAsCssValue for LayoutWritingMode {
60 fn print_as_css_value(&self) -> String {
61 match self {
62 Self::HorizontalTb => "horizontal-tb".to_string(),
63 Self::VerticalRl => "vertical-rl".to_string(),
64 Self::VerticalLr => "vertical-lr".to_string(),
65 }
66 }
67}
68
69#[cfg(feature = "parser")]
70#[derive(Clone, PartialEq, Eq)]
71pub enum LayoutWritingModeParseError<'a> {
72 InvalidValue(&'a str),
73}
74
75#[cfg(feature = "parser")]
76impl_debug_as_display!(LayoutWritingModeParseError<'a>);
77#[cfg(feature = "parser")]
78impl_display! { LayoutWritingModeParseError<'a>, {
79 InvalidValue(e) => format!("Invalid writing-mode value: \"{}\"", e),
80}}
81
82#[cfg(feature = "parser")]
83#[derive(Debug, Clone, PartialEq, Eq)]
84#[repr(C, u8)]
85pub enum LayoutWritingModeParseErrorOwned {
86 InvalidValue(AzString),
87}
88
89#[cfg(feature = "parser")]
90impl LayoutWritingModeParseError<'_> {
91 #[must_use]
92 pub fn to_contained(&self) -> LayoutWritingModeParseErrorOwned {
93 match self {
94 LayoutWritingModeParseError::InvalidValue(s) => {
95 LayoutWritingModeParseErrorOwned::InvalidValue((*s).to_string().into())
96 }
97 }
98 }
99}
100
101#[cfg(feature = "parser")]
102impl LayoutWritingModeParseErrorOwned {
103 #[must_use]
104 pub fn to_shared(&self) -> LayoutWritingModeParseError<'_> {
105 match self {
106 Self::InvalidValue(s) => LayoutWritingModeParseError::InvalidValue(s.as_str()),
107 }
108 }
109}
110
111#[cfg(feature = "parser")]
112pub fn parse_layout_writing_mode(
116 input: &str,
117) -> Result<LayoutWritingMode, LayoutWritingModeParseError<'_>> {
118 let input = input.trim();
119 match input {
120 "horizontal-tb" => Ok(LayoutWritingMode::HorizontalTb),
121 "vertical-rl" => Ok(LayoutWritingMode::VerticalRl),
122 "vertical-lr" | "tb-lr" => Ok(LayoutWritingMode::VerticalLr),
124 _ => Err(LayoutWritingModeParseError::InvalidValue(input)),
125 }
126}
127
128#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[repr(C)]
133#[derive(Default)]
134pub enum LayoutClear {
135 #[default]
137 None,
138 Left,
140 Right,
142 Both,
144}
145
146impl core::fmt::Debug for LayoutClear {
147 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148 write!(f, "{}", self.print_as_css_value())
149 }
150}
151
152impl core::fmt::Display for LayoutClear {
153 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154 write!(f, "{}", self.print_as_css_value())
155 }
156}
157
158impl PrintAsCssValue for LayoutClear {
159 fn print_as_css_value(&self) -> String {
160 match self {
161 Self::None => "none".to_string(),
162 Self::Left => "left".to_string(),
163 Self::Right => "right".to_string(),
164 Self::Both => "both".to_string(),
165 }
166 }
167}
168
169#[cfg(feature = "parser")]
170#[derive(Clone, PartialEq, Eq)]
171pub enum LayoutClearParseError<'a> {
172 InvalidValue(&'a str),
173}
174
175#[cfg(feature = "parser")]
176impl_debug_as_display!(LayoutClearParseError<'a>);
177#[cfg(feature = "parser")]
178impl_display! { LayoutClearParseError<'a>, {
179 InvalidValue(e) => format!("Invalid clear value: \"{}\"", e),
180}}
181
182#[cfg(feature = "parser")]
183#[derive(Debug, Clone, PartialEq, Eq)]
184#[repr(C, u8)]
185pub enum LayoutClearParseErrorOwned {
186 InvalidValue(AzString),
187}
188
189#[cfg(feature = "parser")]
190impl LayoutClearParseError<'_> {
191 #[must_use]
192 pub fn to_contained(&self) -> LayoutClearParseErrorOwned {
193 match self {
194 LayoutClearParseError::InvalidValue(s) => {
195 LayoutClearParseErrorOwned::InvalidValue((*s).to_string().into())
196 }
197 }
198 }
199}
200
201#[cfg(feature = "parser")]
202impl LayoutClearParseErrorOwned {
203 #[must_use]
204 pub fn to_shared(&self) -> LayoutClearParseError<'_> {
205 match self {
206 Self::InvalidValue(s) => LayoutClearParseError::InvalidValue(s.as_str()),
207 }
208 }
209}
210
211#[cfg(feature = "parser")]
212pub fn parse_layout_clear(input: &str) -> Result<LayoutClear, LayoutClearParseError<'_>> {
216 let input = input.trim();
217 match input {
218 "none" => Ok(LayoutClear::None),
219 "left" => Ok(LayoutClear::Left),
220 "right" => Ok(LayoutClear::Right),
221 "both" => Ok(LayoutClear::Both),
222 _ => Err(LayoutClearParseError::InvalidValue(input)),
223 }
224}
225
226#[cfg(all(test, feature = "parser"))]
227mod tests {
228 use super::*;
229
230 #[test]
232 fn test_parse_writing_mode_horizontal_tb() {
233 assert_eq!(
234 parse_layout_writing_mode("horizontal-tb").unwrap(),
235 LayoutWritingMode::HorizontalTb
236 );
237 }
238
239 #[test]
240 fn test_parse_writing_mode_vertical_rl() {
241 assert_eq!(
242 parse_layout_writing_mode("vertical-rl").unwrap(),
243 LayoutWritingMode::VerticalRl
244 );
245 }
246
247 #[test]
248 fn test_parse_writing_mode_vertical_lr() {
249 assert_eq!(
250 parse_layout_writing_mode("vertical-lr").unwrap(),
251 LayoutWritingMode::VerticalLr
252 );
253 }
254
255 #[test]
256 fn test_parse_writing_mode_invalid() {
257 assert!(parse_layout_writing_mode("invalid").is_err());
258 assert!(parse_layout_writing_mode("horizontal").is_err());
259 }
260
261 #[test]
262 fn test_parse_writing_mode_whitespace() {
263 assert_eq!(
264 parse_layout_writing_mode(" vertical-rl ").unwrap(),
265 LayoutWritingMode::VerticalRl
266 );
267 }
268
269 #[test]
271 fn test_parse_layout_clear_none() {
272 assert_eq!(parse_layout_clear("none").unwrap(), LayoutClear::None);
273 }
274
275 #[test]
276 fn test_parse_layout_clear_left() {
277 assert_eq!(parse_layout_clear("left").unwrap(), LayoutClear::Left);
278 }
279
280 #[test]
281 fn test_parse_layout_clear_right() {
282 assert_eq!(parse_layout_clear("right").unwrap(), LayoutClear::Right);
283 }
284
285 #[test]
286 fn test_parse_layout_clear_both() {
287 assert_eq!(parse_layout_clear("both").unwrap(), LayoutClear::Both);
288 }
289
290 #[test]
291 fn test_parse_layout_clear_invalid() {
292 assert!(parse_layout_clear("invalid").is_err());
293 assert!(parse_layout_clear("all").is_err());
294 }
295
296 #[test]
297 fn test_parse_layout_clear_whitespace() {
298 assert_eq!(parse_layout_clear(" both ").unwrap(), LayoutClear::Both);
299 }
300
301 #[test]
303 fn test_print_writing_mode() {
304 assert_eq!(
305 LayoutWritingMode::HorizontalTb.print_as_css_value(),
306 "horizontal-tb"
307 );
308 assert_eq!(
309 LayoutWritingMode::VerticalRl.print_as_css_value(),
310 "vertical-rl"
311 );
312 assert_eq!(
313 LayoutWritingMode::VerticalLr.print_as_css_value(),
314 "vertical-lr"
315 );
316 }
317
318 #[test]
319 fn test_print_layout_clear() {
320 assert_eq!(LayoutClear::None.print_as_css_value(), "none");
321 assert_eq!(LayoutClear::Left.print_as_css_value(), "left");
322 assert_eq!(LayoutClear::Right.print_as_css_value(), "right");
323 assert_eq!(LayoutClear::Both.print_as_css_value(), "both");
324 }
325}
326
327#[cfg(test)]
328mod autotest_generated {
329 use super::*;
330
331 const ALL_WRITING_MODES: [LayoutWritingMode; 3] = [
332 LayoutWritingMode::HorizontalTb,
333 LayoutWritingMode::VerticalRl,
334 LayoutWritingMode::VerticalLr,
335 ];
336
337 const ALL_CLEARS: [LayoutClear; 4] = [
338 LayoutClear::None,
339 LayoutClear::Left,
340 LayoutClear::Right,
341 LayoutClear::Both,
342 ];
343
344 #[test]
347 fn is_vertical_known_true_and_false() {
348 assert!(!LayoutWritingMode::HorizontalTb.is_vertical());
349 assert!(LayoutWritingMode::VerticalRl.is_vertical());
350 assert!(LayoutWritingMode::VerticalLr.is_vertical());
351 }
352
353 #[test]
354 fn is_vertical_default_is_horizontal() {
355 assert!(!LayoutWritingMode::default().is_vertical());
356 assert_eq!(
357 LayoutWritingMode::default(),
358 LayoutWritingMode::HorizontalTb
359 );
360 }
361
362 #[test]
363 fn is_vertical_is_const_evaluable() {
364 const HORIZONTAL: bool = LayoutWritingMode::HorizontalTb.is_vertical();
365 const VERTICAL: bool = LayoutWritingMode::VerticalRl.is_vertical();
366 const _: () = assert!(!HORIZONTAL && VERTICAL);
367 }
368
369 #[test]
370 fn is_vertical_agrees_with_css_keyword_prefix() {
371 for mode in ALL_WRITING_MODES {
374 let css = mode.print_as_css_value();
375 assert_eq!(
376 mode.is_vertical(),
377 css.starts_with("vertical-"),
378 "is_vertical() disagrees with keyword {css}"
379 );
380 }
381 }
382
383 #[test]
384 fn is_vertical_is_pure_and_does_not_consume() {
385 let mode = LayoutWritingMode::VerticalLr;
387 assert!(mode.is_vertical());
388 assert!(mode.is_vertical());
389 assert_eq!(mode, LayoutWritingMode::VerticalLr);
390 }
391
392 #[test]
395 fn writing_mode_debug_display_and_css_value_all_agree() {
396 for mode in ALL_WRITING_MODES {
397 let css = mode.print_as_css_value();
398 assert!(!css.is_empty());
399 assert_eq!(format!("{mode:?}"), css);
400 assert_eq!(format!("{mode}"), css);
401 }
402 }
403
404 #[test]
405 fn clear_debug_display_and_css_value_all_agree() {
406 for clear in ALL_CLEARS {
407 let css = clear.print_as_css_value();
408 assert!(!css.is_empty());
409 assert_eq!(format!("{clear:?}"), css);
410 assert_eq!(format!("{clear}"), css);
411 }
412 }
413
414 #[test]
415 fn serializing_defaults_does_not_panic() {
416 assert_eq!(
417 LayoutWritingMode::default().print_as_css_value(),
418 "horizontal-tb"
419 );
420 assert_eq!(LayoutClear::default().print_as_css_value(), "none");
421 }
422
423 #[test]
424 fn serialized_keywords_are_distinct() {
425 let modes: alloc::collections::BTreeSet<String> = ALL_WRITING_MODES
427 .iter()
428 .map(PrintAsCssValue::print_as_css_value)
429 .collect();
430 assert_eq!(modes.len(), ALL_WRITING_MODES.len());
431
432 let clears: alloc::collections::BTreeSet<String> = ALL_CLEARS
433 .iter()
434 .map(PrintAsCssValue::print_as_css_value)
435 .collect();
436 assert_eq!(clears.len(), ALL_CLEARS.len());
437 }
438
439 #[test]
440 fn display_impl_ignores_width_and_precision_flags() {
441 assert_eq!(format!("{:>20}", LayoutClear::Both), "both");
446 assert_eq!(format!("{:.2}", LayoutClear::Both), "both");
447 assert_eq!(
448 format!("{:>20}", LayoutWritingMode::VerticalRl),
449 "vertical-rl"
450 );
451 }
452
453 #[test]
456 fn writing_mode_ord_follows_declaration_order() {
457 assert!(LayoutWritingMode::HorizontalTb < LayoutWritingMode::VerticalRl);
458 assert!(LayoutWritingMode::VerticalRl < LayoutWritingMode::VerticalLr);
459 }
460
461 #[test]
462 fn clear_ord_follows_declaration_order() {
463 assert!(LayoutClear::None < LayoutClear::Left);
464 assert!(LayoutClear::Left < LayoutClear::Right);
465 assert!(LayoutClear::Right < LayoutClear::Both);
466 }
467
468 #[test]
469 fn equal_values_hash_equally() {
470 use core::hash::{Hash, Hasher};
471 use std::collections::hash_map::DefaultHasher;
472
473 fn hash_of<T: Hash>(value: &T) -> u64 {
474 let mut hasher = DefaultHasher::new();
475 value.hash(&mut hasher);
476 hasher.finish()
477 }
478
479 assert_eq!(
481 hash_of(&LayoutWritingMode::VerticalRl),
482 hash_of(&ALL_WRITING_MODES[1])
483 );
484 assert_eq!(hash_of(&LayoutClear::Both), hash_of(&ALL_CLEARS[3]));
485 assert_ne!(
486 hash_of(&LayoutWritingMode::HorizontalTb),
487 hash_of(&LayoutWritingMode::VerticalRl)
488 );
489 }
490
491 #[cfg(feature = "parser")]
494 #[test]
495 fn parse_writing_mode_valid_minimal() {
496 assert_eq!(
497 parse_layout_writing_mode("horizontal-tb"),
498 Ok(LayoutWritingMode::HorizontalTb)
499 );
500 }
501
502 #[cfg(feature = "parser")]
503 #[test]
504 fn parse_writing_mode_accepts_svg_tb_lr_alias() {
505 assert_eq!(
507 parse_layout_writing_mode("tb-lr"),
508 Ok(LayoutWritingMode::VerticalLr)
509 );
510 for unsupported in ["lr", "lr-tb", "rl", "rl-tb", "tb"] {
512 assert!(
513 parse_layout_writing_mode(unsupported).is_err(),
514 "{unsupported} unexpectedly parsed"
515 );
516 }
517 }
518
519 #[cfg(feature = "parser")]
520 #[test]
521 fn parse_clear_valid_minimal() {
522 assert_eq!(parse_layout_clear("none"), Ok(LayoutClear::None));
523 }
524
525 #[cfg(feature = "parser")]
526 #[test]
527 fn parse_empty_input_errors_with_empty_payload() {
528 let err = parse_layout_writing_mode("").unwrap_err();
529 assert_eq!(err, LayoutWritingModeParseError::InvalidValue(""));
530 let err = parse_layout_clear("").unwrap_err();
531 assert_eq!(err, LayoutClearParseError::InvalidValue(""));
532 }
533
534 #[cfg(feature = "parser")]
535 #[test]
536 fn parse_whitespace_only_input_errors_after_trimming() {
537 for blank in [" ", "\t\n", "\r\n\r\n", "\u{0c}", " \t \n \r "] {
538 assert_eq!(
539 parse_layout_writing_mode(blank).unwrap_err(),
540 LayoutWritingModeParseError::InvalidValue(""),
541 "whitespace {blank:?} should trim to the empty payload"
542 );
543 assert_eq!(
544 parse_layout_clear(blank).unwrap_err(),
545 LayoutClearParseError::InvalidValue("")
546 );
547 }
548 }
549
550 #[cfg(feature = "parser")]
551 #[test]
552 fn parse_garbage_returns_err_and_echoes_trimmed_input() {
553 for garbage in [
554 "invalid",
555 "!!!",
556 "\u{0}",
557 "none\u{0}",
558 "{}[]();",
559 "-",
560 "--",
561 "vertical-",
562 "-rl",
563 "vertical rl",
564 "vertical - rl",
565 "vertical_rl",
566 "verticalrl",
567 ] {
568 let err = parse_layout_writing_mode(garbage).unwrap_err();
569 assert_eq!(
570 err,
571 LayoutWritingModeParseError::InvalidValue(garbage.trim())
572 );
573 let err = parse_layout_clear(garbage).unwrap_err();
574 assert_eq!(err, LayoutClearParseError::InvalidValue(garbage.trim()));
575 }
576 }
577
578 #[cfg(feature = "parser")]
579 #[test]
580 fn parse_rejects_css_wide_keywords() {
581 for wide in ["inherit", "initial", "unset", "revert", "revert-layer"] {
584 assert!(parse_layout_writing_mode(wide).is_err(), "{wide} accepted");
585 assert!(parse_layout_clear(wide).is_err(), "{wide} accepted");
586 }
587 }
588
589 #[cfg(feature = "parser")]
590 #[test]
591 fn parse_is_ascii_case_sensitive() {
592 for upper in ["HORIZONTAL-TB", "Vertical-Rl", "VERTICAL-LR", "TB-LR"] {
596 assert!(
597 parse_layout_writing_mode(upper).is_err(),
598 "{upper} parsed — case-insensitive matching was added, update this test"
599 );
600 }
601 for upper in ["NONE", "Left", "RIGHT", "Both"] {
602 assert!(
603 parse_layout_clear(upper).is_err(),
604 "{upper} parsed — case-insensitive matching was added, update this test"
605 );
606 }
607 }
608
609 #[cfg(feature = "parser")]
610 #[test]
611 fn parse_leading_trailing_ascii_whitespace_is_trimmed() {
612 assert_eq!(
613 parse_layout_writing_mode(" vertical-rl "),
614 Ok(LayoutWritingMode::VerticalRl)
615 );
616 assert_eq!(
617 parse_layout_writing_mode("\t\nvertical-lr\r\n"),
618 Ok(LayoutWritingMode::VerticalLr)
619 );
620 assert_eq!(parse_layout_clear("\t both \n"), Ok(LayoutClear::Both));
621 }
622
623 #[cfg(feature = "parser")]
624 #[test]
625 fn parse_rejects_trailing_junk_after_valid_keyword() {
626 for junk in [
627 "both;garbage",
628 "both;",
629 "both both",
630 "both!important",
631 "both,",
632 ] {
633 assert_eq!(
634 parse_layout_clear(junk).unwrap_err(),
635 LayoutClearParseError::InvalidValue(junk.trim())
636 );
637 }
638 assert!(parse_layout_writing_mode("vertical-rl;garbage").is_err());
639 }
640
641 #[cfg(feature = "parser")]
642 #[test]
643 fn parse_trims_unicode_whitespace_beyond_css_whitespace() {
644 assert_eq!(
649 parse_layout_clear("\u{00a0}both\u{00a0}"),
650 Ok(LayoutClear::Both)
651 );
652 assert_eq!(
653 parse_layout_writing_mode("\u{2003}vertical-rl\u{2003}"),
654 Ok(LayoutWritingMode::VerticalRl)
655 );
656 assert_eq!(
658 parse_layout_clear("\u{200b}both").unwrap_err(),
659 LayoutClearParseError::InvalidValue("\u{200b}both")
660 );
661 assert_eq!(
662 parse_layout_clear("\u{feff}both").unwrap_err(),
663 LayoutClearParseError::InvalidValue("\u{feff}both")
664 );
665 }
666
667 #[cfg(feature = "parser")]
668 #[test]
669 fn parse_unicode_input_does_not_panic_on_char_boundaries() {
670 for weird in [
671 "\u{1F600}",
672 "🙂🙃",
673 "e\u{0301}", "n\u{0303}one", "none", "نص", "\u{202E}both", "both\u{0301}",
679 ] {
680 let err = parse_layout_writing_mode(weird).unwrap_err();
681 assert_eq!(err, LayoutWritingModeParseError::InvalidValue(weird.trim()));
682 let err = parse_layout_clear(weird).unwrap_err();
683 assert_eq!(err, LayoutClearParseError::InvalidValue(weird.trim()));
684 assert_eq!(err.to_contained().to_shared(), err);
686 }
687 }
688
689 #[cfg(feature = "parser")]
690 #[test]
691 fn parse_boundary_numeric_strings_are_rejected() {
692 for numeric in [
693 "0",
694 "-0",
695 "0.0",
696 "NaN",
697 "nan",
698 "inf",
699 "-inf",
700 "infinity",
701 "1e999",
702 "-1e-999",
703 "9223372036854775807", "-9223372036854775808", "18446744073709551616", "179769313486231570000000000000000000000000000000000000000000000000000000000000000",
707 ] {
708 assert!(
709 parse_layout_writing_mode(numeric).is_err(),
710 "{numeric} parsed"
711 );
712 assert!(parse_layout_clear(numeric).is_err(), "{numeric} parsed");
713 }
714 }
715
716 #[cfg(feature = "parser")]
717 #[test]
718 fn parse_extremely_long_input_does_not_panic_or_hang() {
719 let long = "a".repeat(1_000_000);
720 let err = parse_layout_clear(&long).unwrap_err();
721 assert_eq!(err, LayoutClearParseError::InvalidValue(long.as_str()));
722
723 let blank = " ".repeat(1_000_000);
725 assert_eq!(
726 parse_layout_writing_mode(&blank).unwrap_err(),
727 LayoutWritingModeParseError::InvalidValue("")
728 );
729
730 let padded = format!("{blank}vertical-rl{blank}");
732 assert_eq!(
733 parse_layout_writing_mode(&padded),
734 Ok(LayoutWritingMode::VerticalRl)
735 );
736
737 let repeated = "both ".repeat(200_000);
739 assert!(parse_layout_clear(&repeated).is_err());
740 }
741
742 #[cfg(feature = "parser")]
743 #[test]
744 fn parse_deeply_nested_input_does_not_stack_overflow() {
745 let nested = "(".repeat(10_000) + &")".repeat(10_000);
746 assert!(parse_layout_clear(&nested).is_err());
747 assert!(parse_layout_writing_mode(&nested).is_err());
748
749 let brackets = "[".repeat(10_000);
750 assert_eq!(
751 parse_layout_clear(&brackets).unwrap_err(),
752 LayoutClearParseError::InvalidValue(brackets.as_str())
753 );
754 }
755
756 #[cfg(feature = "parser")]
757 #[test]
758 fn parse_error_payload_borrows_the_trimmed_slice_not_the_whole_input() {
759 let input = " bogus-value ";
760 let LayoutClearParseError::InvalidValue(payload) = parse_layout_clear(input).unwrap_err();
761 assert_eq!(payload, "bogus-value");
763 assert_eq!(payload.len(), 11);
764 assert!(input.as_ptr() <= payload.as_ptr());
766 }
767
768 #[test]
771 #[cfg(feature = "parser")]
772 fn writing_mode_round_trips_through_css_value() {
773 for mode in ALL_WRITING_MODES {
774 let css = mode.print_as_css_value();
775 assert_eq!(
776 parse_layout_writing_mode(&css),
777 Ok(mode),
778 "round-trip {css}"
779 );
780 assert_eq!(parse_layout_writing_mode(&format!("{mode}")), Ok(mode));
782 assert_eq!(parse_layout_writing_mode(&format!("{mode:?}")), Ok(mode));
783 }
784 }
785
786 #[test]
787 #[cfg(feature = "parser")]
788 fn clear_round_trips_through_css_value() {
789 for clear in ALL_CLEARS {
790 let css = clear.print_as_css_value();
791 assert_eq!(parse_layout_clear(&css), Ok(clear), "round-trip {css}");
792 assert_eq!(parse_layout_clear(&format!("{clear}")), Ok(clear));
793 assert_eq!(parse_layout_clear(&format!("{clear:?}")), Ok(clear));
794 }
795 }
796
797 #[cfg(feature = "parser")]
800 #[test]
801 fn writing_mode_error_to_contained_preserves_the_value() {
802 let err = parse_layout_writing_mode("bogus").unwrap_err();
803 assert_eq!(
804 err.to_contained(),
805 LayoutWritingModeParseErrorOwned::InvalidValue("bogus".into())
806 );
807 }
808
809 #[cfg(feature = "parser")]
810 #[test]
811 fn clear_error_to_contained_preserves_the_value() {
812 let err = parse_layout_clear("bogus").unwrap_err();
813 assert_eq!(
814 err.to_contained(),
815 LayoutClearParseErrorOwned::InvalidValue("bogus".into())
816 );
817 }
818
819 #[cfg(feature = "parser")]
820 #[test]
821 fn error_to_contained_outlives_the_parsed_input() {
822 let owned = {
824 let scratch = String::from("temporary-garbage");
825 parse_layout_clear(&scratch).unwrap_err().to_contained()
826 };
827 assert_eq!(
828 owned,
829 LayoutClearParseErrorOwned::InvalidValue("temporary-garbage".into())
830 );
831 assert_eq!(
832 owned.to_shared(),
833 LayoutClearParseError::InvalidValue("temporary-garbage")
834 );
835 }
836
837 #[cfg(feature = "parser")]
838 #[test]
839 fn error_shared_owned_round_trip_is_lossless() {
840 for value in [
841 "",
842 " ",
843 "\u{0}",
844 "🙂",
845 "e\u{0301}",
846 "quote\"inside",
847 "back\\slash",
848 "new\nline",
849 ] {
850 let shared = LayoutClearParseError::InvalidValue(value);
851 let owned = shared.to_contained();
852 assert_eq!(owned.to_shared(), shared, "clear round-trip {value:?}");
853 assert_eq!(owned.to_shared().to_contained(), owned);
855
856 let shared = LayoutWritingModeParseError::InvalidValue(value);
857 let owned = shared.to_contained();
858 assert_eq!(
859 owned.to_shared(),
860 shared,
861 "writing-mode round-trip {value:?}"
862 );
863 assert_eq!(owned.to_shared().to_contained(), owned);
864 }
865 }
866
867 #[cfg(feature = "parser")]
868 #[test]
869 fn error_round_trip_survives_a_huge_payload() {
870 let huge = "x".repeat(100_000);
871 let shared = LayoutClearParseError::InvalidValue(huge.as_str());
872 let owned = shared.to_contained();
873 let LayoutClearParseError::InvalidValue(back) = owned.to_shared();
874 assert_eq!(back.len(), 100_000);
875 assert_eq!(back, huge.as_str());
876 }
877
878 #[cfg(feature = "parser")]
879 #[test]
880 fn error_to_shared_on_default_azstring_does_not_panic() {
881 let owned = LayoutClearParseErrorOwned::InvalidValue(AzString::default());
884 assert_eq!(owned.to_shared(), LayoutClearParseError::InvalidValue(""));
885
886 let owned = LayoutWritingModeParseErrorOwned::InvalidValue(AzString::default());
887 assert_eq!(
888 owned.to_shared(),
889 LayoutWritingModeParseError::InvalidValue("")
890 );
891 }
892
893 #[cfg(feature = "parser")]
894 #[test]
895 fn error_display_and_debug_include_the_offending_value() {
896 let err = parse_layout_writing_mode(" bogus ").unwrap_err();
897 let display = format!("{err}");
898 assert_eq!(display, "Invalid writing-mode value: \"bogus\"");
899 assert_eq!(format!("{err:?}"), display);
901
902 let err = parse_layout_clear("bogus").unwrap_err();
903 let display = format!("{err}");
904 assert_eq!(display, "Invalid clear value: \"bogus\"");
905 assert_eq!(format!("{err:?}"), display);
906 }
907
908 #[cfg(feature = "parser")]
909 #[test]
910 fn error_display_does_not_panic_on_empty_or_unicode_payloads() {
911 for value in ["", "\u{0}", "🙂", "\u{202E}"] {
912 let err = LayoutClearParseError::InvalidValue(value);
913 assert!(format!("{err}").contains(value));
914 let owned = err.to_contained();
915 assert!(format!("{:?}", owned.to_shared()).contains(value));
916 }
917 }
918}