1use core::{fmt, ops::Range};
6use std::borrow::Cow;
7
8mod parser;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct ErrorTemplate<'a> {
36 source: &'a str,
37 segments: Vec<TemplateSegment<'a>>
38}
39
40impl<'a> ErrorTemplate<'a> {
41 pub fn parse(source: &'a str) -> Result<Self, TemplateError> {
43 let segments = parser::parse_template(source)?;
44 Ok(Self {
45 source,
46 segments
47 })
48 }
49
50 pub const fn source(&self) -> &'a str {
52 self.source
53 }
54
55 pub fn segments(&self) -> &[TemplateSegment<'a>] {
57 &self.segments
58 }
59
60 pub fn placeholders(&self) -> impl Iterator<Item = &TemplatePlaceholder<'a>> {
62 self.segments.iter().filter_map(|segment| match segment {
63 TemplateSegment::Placeholder(placeholder) => Some(placeholder),
64 TemplateSegment::Literal(_) => None
65 })
66 }
67
68 pub fn display_with<F>(&'a self, resolver: F) -> DisplayWith<'a, 'a, F>
71 where
72 F: Fn(&TemplatePlaceholder<'a>, &mut fmt::Formatter<'_>) -> fmt::Result
73 {
74 DisplayWith {
75 template: self,
76 resolver
77 }
78 }
79}
80
81#[derive(Debug)]
83pub struct DisplayWith<'a, 't, F>
84where
85 F: Fn(&TemplatePlaceholder<'a>, &mut fmt::Formatter<'_>) -> fmt::Result
86{
87 template: &'t ErrorTemplate<'a>,
88 resolver: F
89}
90
91impl<'a, 't, F> fmt::Display for DisplayWith<'a, 't, F>
92where
93 F: Fn(&TemplatePlaceholder<'a>, &mut fmt::Formatter<'_>) -> fmt::Result
94{
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 for segment in &self.template.segments {
97 match segment {
98 TemplateSegment::Literal(literal) => f.write_str(literal)?,
99 TemplateSegment::Placeholder(placeholder) => {
100 (self.resolver)(placeholder, f)?;
101 }
102 }
103 }
104
105 Ok(())
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
111pub enum TemplateSegment<'a> {
112 Literal(&'a str),
114 Placeholder(TemplatePlaceholder<'a>)
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct TemplatePlaceholder<'a> {
121 span: Range<usize>,
122 identifier: TemplateIdentifier<'a>,
123 formatter: TemplateFormatter
124}
125
126impl<'a> TemplatePlaceholder<'a> {
127 pub fn span(&self) -> Range<usize> {
130 self.span.clone()
131 }
132
133 pub const fn identifier(&self) -> &TemplateIdentifier<'a> {
135 &self.identifier
136 }
137
138 pub fn formatter(&self) -> &TemplateFormatter {
140 &self.formatter
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum TemplateIdentifier<'a> {
147 Implicit(usize),
150 Positional(usize),
152 Named(&'a str)
154}
155
156impl<'a> TemplateIdentifier<'a> {
157 pub const fn as_str(&self) -> Option<&'a str> {
159 match self {
160 Self::Named(value) => Some(value),
161 Self::Positional(_) | Self::Implicit(_) => None
162 }
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum TemplateFormatterKind {
183 Display,
185 Debug,
187 LowerHex,
189 UpperHex,
191 Pointer,
193 Binary,
195 Octal,
197 LowerExp,
199 UpperExp
201}
202
203impl TemplateFormatterKind {
204 pub const fn from_specifier(specifier: char) -> Option<Self> {
220 match specifier {
221 '?' => Some(Self::Debug),
222 'x' => Some(Self::LowerHex),
223 'X' => Some(Self::UpperHex),
224 'p' => Some(Self::Pointer),
225 'b' => Some(Self::Binary),
226 'o' => Some(Self::Octal),
227 'e' => Some(Self::LowerExp),
228 'E' => Some(Self::UpperExp),
229 _ => None
230 }
231 }
232
233 pub const fn specifier(self) -> Option<char> {
247 match self {
248 Self::Display => None,
249 Self::Debug => Some('?'),
250 Self::LowerHex => Some('x'),
251 Self::UpperHex => Some('X'),
252 Self::Pointer => Some('p'),
253 Self::Binary => Some('b'),
254 Self::Octal => Some('o'),
255 Self::LowerExp => Some('e'),
256 Self::UpperExp => Some('E')
257 }
258 }
259
260 pub const fn supports_alternate(self) -> bool {
271 !matches!(self, Self::Display)
272 }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
277pub enum TemplateFormatter {
278 Display {
280 spec: Option<Box<str>>
282 },
283 Debug {
285 alternate: bool
287 },
288 LowerHex {
290 alternate: bool
292 },
293 UpperHex {
295 alternate: bool
297 },
298 Pointer {
300 alternate: bool
302 },
303 Binary {
305 alternate: bool
307 },
308 Octal {
310 alternate: bool
312 },
313 LowerExp {
315 alternate: bool
317 },
318 UpperExp {
320 alternate: bool
322 }
323}
324
325impl TemplateFormatter {
326 pub const fn from_kind(kind: TemplateFormatterKind, alternate: bool) -> Self {
346 match kind {
347 TemplateFormatterKind::Display => Self::Display {
348 spec: None
349 },
350 TemplateFormatterKind::Debug => Self::Debug {
351 alternate
352 },
353 TemplateFormatterKind::LowerHex => Self::LowerHex {
354 alternate
355 },
356 TemplateFormatterKind::UpperHex => Self::UpperHex {
357 alternate
358 },
359 TemplateFormatterKind::Pointer => Self::Pointer {
360 alternate
361 },
362 TemplateFormatterKind::Binary => Self::Binary {
363 alternate
364 },
365 TemplateFormatterKind::Octal => Self::Octal {
366 alternate
367 },
368 TemplateFormatterKind::LowerExp => Self::LowerExp {
369 alternate
370 },
371 TemplateFormatterKind::UpperExp => Self::UpperExp {
372 alternate
373 }
374 }
375 }
376
377 pub const fn kind(&self) -> TemplateFormatterKind {
391 match self {
392 Self::Display {
393 ..
394 } => TemplateFormatterKind::Display,
395 Self::Debug {
396 ..
397 } => TemplateFormatterKind::Debug,
398 Self::LowerHex {
399 ..
400 } => TemplateFormatterKind::LowerHex,
401 Self::UpperHex {
402 ..
403 } => TemplateFormatterKind::UpperHex,
404 Self::Pointer {
405 ..
406 } => TemplateFormatterKind::Pointer,
407 Self::Binary {
408 ..
409 } => TemplateFormatterKind::Binary,
410 Self::Octal {
411 ..
412 } => TemplateFormatterKind::Octal,
413 Self::LowerExp {
414 ..
415 } => TemplateFormatterKind::LowerExp,
416 Self::UpperExp {
417 ..
418 } => TemplateFormatterKind::UpperExp
419 }
420 }
421
422 pub fn from_format_spec(spec: &str) -> Option<Self> {
424 Self::parse_specifier(spec)
425 }
426
427 pub(crate) fn parse_specifier(spec: &str) -> Option<Self> {
428 parser::parse_formatter_spec(spec)
429 }
430
431 pub fn display_spec(&self) -> Option<&str> {
433 match self {
434 Self::Display {
435 spec: Some(spec)
436 } => Some(spec),
437 _ => None
438 }
439 }
440
441 pub fn has_display_spec(&self) -> bool {
444 matches!(
445 self,
446 Self::Display {
447 spec: Some(_)
448 }
449 )
450 }
451
452 pub fn format_fragment(&self) -> Option<Cow<'_, str>> {
455 match self {
456 Self::Display {
457 spec
458 } => spec.as_deref().map(Cow::Borrowed),
459 Self::Debug {
460 alternate
461 } => {
462 if *alternate {
463 Some(Cow::Borrowed("#?"))
464 } else {
465 Some(Cow::Borrowed("?"))
466 }
467 }
468 Self::LowerHex {
469 alternate
470 } => {
471 if *alternate {
472 Some(Cow::Borrowed("#x"))
473 } else {
474 Some(Cow::Borrowed("x"))
475 }
476 }
477 Self::UpperHex {
478 alternate
479 } => {
480 if *alternate {
481 Some(Cow::Borrowed("#X"))
482 } else {
483 Some(Cow::Borrowed("X"))
484 }
485 }
486 Self::Pointer {
487 alternate
488 } => {
489 if *alternate {
490 Some(Cow::Borrowed("#p"))
491 } else {
492 Some(Cow::Borrowed("p"))
493 }
494 }
495 Self::Binary {
496 alternate
497 } => {
498 if *alternate {
499 Some(Cow::Borrowed("#b"))
500 } else {
501 Some(Cow::Borrowed("b"))
502 }
503 }
504 Self::Octal {
505 alternate
506 } => {
507 if *alternate {
508 Some(Cow::Borrowed("#o"))
509 } else {
510 Some(Cow::Borrowed("o"))
511 }
512 }
513 Self::LowerExp {
514 alternate
515 } => {
516 if *alternate {
517 Some(Cow::Borrowed("#e"))
518 } else {
519 Some(Cow::Borrowed("e"))
520 }
521 }
522 Self::UpperExp {
523 alternate
524 } => {
525 if *alternate {
526 Some(Cow::Borrowed("#E"))
527 } else {
528 Some(Cow::Borrowed("E"))
529 }
530 }
531 }
532 }
533
534 pub const fn is_alternate(&self) -> bool {
536 match self {
537 Self::Display {
538 ..
539 } => false,
540 Self::Debug {
541 alternate
542 }
543 | Self::LowerHex {
544 alternate
545 }
546 | Self::UpperHex {
547 alternate
548 }
549 | Self::Pointer {
550 alternate
551 }
552 | Self::Binary {
553 alternate
554 }
555 | Self::Octal {
556 alternate
557 }
558 | Self::LowerExp {
559 alternate
560 }
561 | Self::UpperExp {
562 alternate
563 } => *alternate
564 }
565 }
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
570pub enum TemplateError {
571 UnmatchedClosingBrace {
573 index: usize
575 },
576 UnterminatedPlaceholder {
578 start: usize
580 },
581 NestedPlaceholder {
583 index: usize
585 },
586 EmptyPlaceholder {
588 start: usize
590 },
591 InvalidIdentifier {
593 span: Range<usize>
595 },
596 InvalidIndex {
598 span: Range<usize>
600 },
601 InvalidFormatter {
603 span: Range<usize>
605 }
606}
607
608impl fmt::Display for TemplateError {
609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610 match self {
611 Self::UnmatchedClosingBrace {
612 index
613 } => {
614 write!(f, "unmatched closing brace at byte {}", index)
615 }
616 Self::UnterminatedPlaceholder {
617 start
618 } => {
619 write!(f, "placeholder starting at byte {} is not closed", start)
620 }
621 Self::NestedPlaceholder {
622 index
623 } => {
624 write!(
625 f,
626 "nested placeholder starting at byte {} is not supported",
627 index
628 )
629 }
630 Self::EmptyPlaceholder {
631 start
632 } => {
633 write!(f, "placeholder starting at byte {} is empty", start)
634 }
635 Self::InvalidIdentifier {
636 span
637 } => {
638 write!(
639 f,
640 "invalid placeholder identifier spanning bytes {}..{}",
641 span.start, span.end
642 )
643 }
644 Self::InvalidIndex {
645 span
646 } => {
647 write!(
648 f,
649 "positional placeholder spanning bytes {}..{} is not a valid unsigned integer",
650 span.start, span.end
651 )
652 }
653 Self::InvalidFormatter {
654 span
655 } => {
656 write!(
657 f,
658 "placeholder spanning bytes {}..{} uses an unsupported formatter",
659 span.start, span.end
660 )
661 }
662 }
663 }
664}
665
666impl std::error::Error for TemplateError {}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 fn named(name: &str) -> TemplateIdentifier<'_> {
673 TemplateIdentifier::Named(name)
674 }
675
676 #[test]
677 fn parses_basic_template() {
678 let template = ErrorTemplate::parse("{code}: {message}").expect("parse");
679 let segments = template.segments();
680
681 assert_eq!(segments.len(), 3);
682 assert!(matches!(segments[0], TemplateSegment::Placeholder(_)));
683 assert!(matches!(segments[1], TemplateSegment::Literal(": ")));
684 assert!(matches!(segments[2], TemplateSegment::Placeholder(_)));
685
686 let placeholders: Vec<_> = template.placeholders().collect();
687 assert_eq!(placeholders.len(), 2);
688 assert_eq!(placeholders[0].identifier(), &named("code"));
689 assert_eq!(placeholders[1].identifier(), &named("message"));
690 }
691
692 #[test]
693 fn parses_implicit_identifiers() {
694 let template = ErrorTemplate::parse("{}, {:?}, {name}, {}").expect("parse");
695 let mut placeholders = template.placeholders();
696
697 let first = placeholders.next().expect("first placeholder");
698 assert_eq!(first.identifier(), &TemplateIdentifier::Implicit(0));
699 assert_eq!(
700 first.formatter(),
701 &TemplateFormatter::Display {
702 spec: None
703 }
704 );
705
706 let second = placeholders.next().expect("second placeholder");
707 assert_eq!(second.identifier(), &TemplateIdentifier::Implicit(1));
708 assert_eq!(
709 second.formatter(),
710 &TemplateFormatter::Debug {
711 alternate: false
712 }
713 );
714
715 let third = placeholders.next().expect("third placeholder");
716 assert_eq!(third.identifier(), &named("name"));
717
718 let fourth = placeholders.next().expect("fourth placeholder");
719 assert_eq!(fourth.identifier(), &TemplateIdentifier::Implicit(2));
720 assert!(placeholders.next().is_none());
721 }
722
723 #[test]
724 fn parses_debug_formatter() {
725 let template = ErrorTemplate::parse("{0:#?}").expect("parse");
726 let placeholders: Vec<_> = template.placeholders().collect();
727
728 assert_eq!(placeholders.len(), 1);
729 assert_eq!(
730 placeholders[0].identifier(),
731 &TemplateIdentifier::Positional(0)
732 );
733 assert_eq!(
734 placeholders[0].formatter(),
735 &TemplateFormatter::Debug {
736 alternate: true
737 }
738 );
739 assert!(placeholders[0].formatter().is_alternate());
740 }
741
742 #[test]
743 fn parses_extended_formatters() {
744 let cases = [
745 (
746 "{value:x}",
747 TemplateFormatter::LowerHex {
748 alternate: false
749 }
750 ),
751 (
752 "{value:#x}",
753 TemplateFormatter::LowerHex {
754 alternate: true
755 }
756 ),
757 (
758 "{value:X}",
759 TemplateFormatter::UpperHex {
760 alternate: false
761 }
762 ),
763 (
764 "{value:#X}",
765 TemplateFormatter::UpperHex {
766 alternate: true
767 }
768 ),
769 (
770 "{value:p}",
771 TemplateFormatter::Pointer {
772 alternate: false
773 }
774 ),
775 (
776 "{value:#p}",
777 TemplateFormatter::Pointer {
778 alternate: true
779 }
780 ),
781 (
782 "{value:b}",
783 TemplateFormatter::Binary {
784 alternate: false
785 }
786 ),
787 (
788 "{value:#b}",
789 TemplateFormatter::Binary {
790 alternate: true
791 }
792 ),
793 (
794 "{value:o}",
795 TemplateFormatter::Octal {
796 alternate: false
797 }
798 ),
799 (
800 "{value:#o}",
801 TemplateFormatter::Octal {
802 alternate: true
803 }
804 ),
805 (
806 "{value:e}",
807 TemplateFormatter::LowerExp {
808 alternate: false
809 }
810 ),
811 (
812 "{value:#e}",
813 TemplateFormatter::LowerExp {
814 alternate: true
815 }
816 ),
817 (
818 "{value:E}",
819 TemplateFormatter::UpperExp {
820 alternate: false
821 }
822 ),
823 (
824 "{value:#E}",
825 TemplateFormatter::UpperExp {
826 alternate: true
827 }
828 )
829 ];
830
831 for (template_str, expected) in &cases {
832 let template = ErrorTemplate::parse(template_str).expect("parse");
833 let placeholder = template.placeholders().next().expect("placeholder present");
834 assert_eq!(placeholder.formatter(), expected, "case: {template_str}");
835 }
836 }
837
838 #[test]
839 fn preserves_hash_fill_display_specs() {
840 let template = ErrorTemplate::parse("{value:#>4}").expect("parse");
841 let placeholder = template.placeholders().next().expect("placeholder present");
842
843 assert_eq!(placeholder.formatter().display_spec(), Some("#>4"));
844 assert_eq!(
845 placeholder.formatter().format_fragment().as_deref(),
846 Some("#>4")
847 );
848
849 let expected = TemplateFormatter::Display {
850 spec: Some("#>4".into())
851 };
852
853 assert_eq!(placeholder.formatter(), &expected);
854 }
855
856 #[test]
857 fn formatter_kind_helpers_cover_all_variants() {
858 let table = [
859 (TemplateFormatterKind::Debug, '?'),
860 (TemplateFormatterKind::LowerHex, 'x'),
861 (TemplateFormatterKind::UpperHex, 'X'),
862 (TemplateFormatterKind::Pointer, 'p'),
863 (TemplateFormatterKind::Binary, 'b'),
864 (TemplateFormatterKind::Octal, 'o'),
865 (TemplateFormatterKind::LowerExp, 'e'),
866 (TemplateFormatterKind::UpperExp, 'E')
867 ];
868
869 for (kind, specifier) in table {
870 assert_eq!(TemplateFormatterKind::from_specifier(specifier), Some(kind));
871 assert_eq!(kind.specifier(), Some(specifier));
872
873 let with_alternate = TemplateFormatter::from_kind(kind, true);
874 let without_alternate = TemplateFormatter::from_kind(kind, false);
875
876 assert_eq!(with_alternate.kind(), kind);
877 assert_eq!(without_alternate.kind(), kind);
878
879 if kind.supports_alternate() {
880 assert!(with_alternate.is_alternate());
881 assert!(!without_alternate.is_alternate());
882 } else {
883 assert!(!with_alternate.is_alternate());
884 assert!(!without_alternate.is_alternate());
885 }
886 }
887
888 let display = TemplateFormatter::from_kind(TemplateFormatterKind::Display, true);
889 assert_eq!(display.kind(), TemplateFormatterKind::Display);
890 assert!(!display.is_alternate());
891 assert_eq!(TemplateFormatterKind::Display.specifier(), None);
892 assert!(!TemplateFormatterKind::Display.supports_alternate());
893 }
894
895 #[test]
896 fn handles_brace_escaping() {
897 let template = ErrorTemplate::parse("{{}} -> {value}").expect("parse");
898 let mut iter = template.segments().iter();
899
900 assert!(matches!(iter.next(), Some(TemplateSegment::Literal("{"))));
901 assert!(matches!(iter.next(), Some(TemplateSegment::Literal("}"))));
902 assert!(matches!(
903 iter.next(),
904 Some(TemplateSegment::Literal(" -> "))
905 ));
906 assert!(matches!(
907 iter.next(),
908 Some(TemplateSegment::Placeholder(TemplatePlaceholder { .. }))
909 ));
910 assert!(iter.next().is_none());
911 }
912
913 #[test]
914 fn rejects_unmatched_closing_brace() {
915 let err = ErrorTemplate::parse("oops}").expect_err("should fail");
916 assert!(matches!(
917 err,
918 TemplateError::UnmatchedClosingBrace {
919 index: 4
920 }
921 ));
922 }
923
924 #[test]
925 fn rejects_unterminated_placeholder() {
926 let err = ErrorTemplate::parse("{oops").expect_err("should fail");
927 assert!(matches!(
928 err,
929 TemplateError::UnterminatedPlaceholder {
930 start: 0
931 }
932 ));
933 }
934
935 #[test]
936 fn rejects_invalid_identifier() {
937 let err = ErrorTemplate::parse("{invalid-name}").expect_err("should fail");
938 assert!(matches!(err, TemplateError::InvalidIdentifier { span } if span == (0..14)));
939 }
940
941 #[test]
942 fn rejects_unknown_formatter() {
943 let err = ErrorTemplate::parse("{value:%}").expect_err("should fail");
944 assert!(matches!(err, TemplateError::InvalidFormatter { span } if span == (0..9)));
945 }
946
947 #[test]
948 fn display_with_resolves_placeholders() {
949 let template = ErrorTemplate::parse("{code}: {message}").expect("parse");
950 let code = 418;
951 let message = "I'm a teapot";
952
953 let rendered = format!(
954 "{}",
955 template.display_with(|placeholder, f| match placeholder.identifier() {
956 TemplateIdentifier::Named("code") => write!(f, "{}", code),
957 TemplateIdentifier::Named("message") => f.write_str(message),
958 other => panic!("unexpected placeholder: {:?}", other)
959 })
960 );
961
962 assert_eq!(rendered, "418: I'm a teapot");
963 }
964}