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 Ok(())
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum TemplateSegment<'a> {
111 Literal(&'a str),
113 Placeholder(TemplatePlaceholder<'a>)
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct TemplatePlaceholder<'a> {
120 span: Range<usize>,
121 identifier: TemplateIdentifier<'a>,
122 formatter: TemplateFormatter
123}
124
125impl<'a> TemplatePlaceholder<'a> {
126 pub fn span(&self) -> Range<usize> {
129 self.span.clone()
130 }
131
132 pub const fn identifier(&self) -> &TemplateIdentifier<'a> {
134 &self.identifier
135 }
136
137 pub fn formatter(&self) -> &TemplateFormatter {
139 &self.formatter
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum TemplateIdentifier<'a> {
146 Implicit(usize),
149 Positional(usize),
151 Named(&'a str)
153}
154
155impl<'a> TemplateIdentifier<'a> {
156 pub const fn as_str(&self) -> Option<&'a str> {
158 match self {
159 Self::Named(value) => Some(value),
160 Self::Positional(_) | Self::Implicit(_) => None
161 }
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum TemplateFormatterKind {
182 Display,
184 Debug,
186 LowerHex,
188 UpperHex,
190 Pointer,
192 Binary,
194 Octal,
196 LowerExp,
198 UpperExp
200}
201
202impl TemplateFormatterKind {
203 pub const fn from_specifier(specifier: char) -> Option<Self> {
219 match specifier {
220 '?' => Some(Self::Debug),
221 'x' => Some(Self::LowerHex),
222 'X' => Some(Self::UpperHex),
223 'p' => Some(Self::Pointer),
224 'b' => Some(Self::Binary),
225 'o' => Some(Self::Octal),
226 'e' => Some(Self::LowerExp),
227 'E' => Some(Self::UpperExp),
228 _ => None
229 }
230 }
231
232 pub const fn specifier(self) -> Option<char> {
246 match self {
247 Self::Display => None,
248 Self::Debug => Some('?'),
249 Self::LowerHex => Some('x'),
250 Self::UpperHex => Some('X'),
251 Self::Pointer => Some('p'),
252 Self::Binary => Some('b'),
253 Self::Octal => Some('o'),
254 Self::LowerExp => Some('e'),
255 Self::UpperExp => Some('E')
256 }
257 }
258
259 pub const fn supports_alternate(self) -> bool {
270 !matches!(self, Self::Display)
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum TemplateFormatter {
277 Display {
279 spec: Option<Box<str>>
281 },
282 Debug {
284 alternate: bool
286 },
287 LowerHex {
289 alternate: bool
291 },
292 UpperHex {
294 alternate: bool
296 },
297 Pointer {
299 alternate: bool
301 },
302 Binary {
304 alternate: bool
306 },
307 Octal {
309 alternate: bool
311 },
312 LowerExp {
314 alternate: bool
316 },
317 UpperExp {
319 alternate: bool
321 }
322}
323
324impl TemplateFormatter {
325 pub const fn from_kind(kind: TemplateFormatterKind, alternate: bool) -> Self {
345 match kind {
346 TemplateFormatterKind::Display => Self::Display {
347 spec: None
348 },
349 TemplateFormatterKind::Debug => Self::Debug {
350 alternate
351 },
352 TemplateFormatterKind::LowerHex => Self::LowerHex {
353 alternate
354 },
355 TemplateFormatterKind::UpperHex => Self::UpperHex {
356 alternate
357 },
358 TemplateFormatterKind::Pointer => Self::Pointer {
359 alternate
360 },
361 TemplateFormatterKind::Binary => Self::Binary {
362 alternate
363 },
364 TemplateFormatterKind::Octal => Self::Octal {
365 alternate
366 },
367 TemplateFormatterKind::LowerExp => Self::LowerExp {
368 alternate
369 },
370 TemplateFormatterKind::UpperExp => Self::UpperExp {
371 alternate
372 }
373 }
374 }
375
376 pub const fn kind(&self) -> TemplateFormatterKind {
390 match self {
391 Self::Display {
392 ..
393 } => TemplateFormatterKind::Display,
394 Self::Debug {
395 ..
396 } => TemplateFormatterKind::Debug,
397 Self::LowerHex {
398 ..
399 } => TemplateFormatterKind::LowerHex,
400 Self::UpperHex {
401 ..
402 } => TemplateFormatterKind::UpperHex,
403 Self::Pointer {
404 ..
405 } => TemplateFormatterKind::Pointer,
406 Self::Binary {
407 ..
408 } => TemplateFormatterKind::Binary,
409 Self::Octal {
410 ..
411 } => TemplateFormatterKind::Octal,
412 Self::LowerExp {
413 ..
414 } => TemplateFormatterKind::LowerExp,
415 Self::UpperExp {
416 ..
417 } => TemplateFormatterKind::UpperExp
418 }
419 }
420
421 pub fn from_format_spec(spec: &str) -> Option<Self> {
423 Self::parse_specifier(spec)
424 }
425
426 pub(crate) fn parse_specifier(spec: &str) -> Option<Self> {
427 parser::parse_formatter_spec(spec)
428 }
429
430 pub fn display_spec(&self) -> Option<&str> {
432 match self {
433 Self::Display {
434 spec: Some(spec)
435 } => Some(spec),
436 _ => None
437 }
438 }
439
440 pub fn has_display_spec(&self) -> bool {
443 matches!(
444 self,
445 Self::Display {
446 spec: Some(_)
447 }
448 )
449 }
450
451 pub fn format_fragment(&self) -> Option<Cow<'_, str>> {
454 match self {
455 Self::Display {
456 spec
457 } => spec.as_deref().map(Cow::Borrowed),
458 Self::Debug {
459 alternate
460 } => {
461 if *alternate {
462 Some(Cow::Borrowed("#?"))
463 } else {
464 Some(Cow::Borrowed("?"))
465 }
466 }
467 Self::LowerHex {
468 alternate
469 } => {
470 if *alternate {
471 Some(Cow::Borrowed("#x"))
472 } else {
473 Some(Cow::Borrowed("x"))
474 }
475 }
476 Self::UpperHex {
477 alternate
478 } => {
479 if *alternate {
480 Some(Cow::Borrowed("#X"))
481 } else {
482 Some(Cow::Borrowed("X"))
483 }
484 }
485 Self::Pointer {
486 alternate
487 } => {
488 if *alternate {
489 Some(Cow::Borrowed("#p"))
490 } else {
491 Some(Cow::Borrowed("p"))
492 }
493 }
494 Self::Binary {
495 alternate
496 } => {
497 if *alternate {
498 Some(Cow::Borrowed("#b"))
499 } else {
500 Some(Cow::Borrowed("b"))
501 }
502 }
503 Self::Octal {
504 alternate
505 } => {
506 if *alternate {
507 Some(Cow::Borrowed("#o"))
508 } else {
509 Some(Cow::Borrowed("o"))
510 }
511 }
512 Self::LowerExp {
513 alternate
514 } => {
515 if *alternate {
516 Some(Cow::Borrowed("#e"))
517 } else {
518 Some(Cow::Borrowed("e"))
519 }
520 }
521 Self::UpperExp {
522 alternate
523 } => {
524 if *alternate {
525 Some(Cow::Borrowed("#E"))
526 } else {
527 Some(Cow::Borrowed("E"))
528 }
529 }
530 }
531 }
532
533 pub const fn is_alternate(&self) -> bool {
535 match self {
536 Self::Display {
537 ..
538 } => false,
539 Self::Debug {
540 alternate
541 }
542 | Self::LowerHex {
543 alternate
544 }
545 | Self::UpperHex {
546 alternate
547 }
548 | Self::Pointer {
549 alternate
550 }
551 | Self::Binary {
552 alternate
553 }
554 | Self::Octal {
555 alternate
556 }
557 | Self::LowerExp {
558 alternate
559 }
560 | Self::UpperExp {
561 alternate
562 } => *alternate
563 }
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Eq)]
569pub enum TemplateError {
570 UnmatchedClosingBrace {
572 index: usize
574 },
575 UnterminatedPlaceholder {
577 start: usize
579 },
580 NestedPlaceholder {
582 index: usize
584 },
585 EmptyPlaceholder {
587 start: usize
589 },
590 InvalidIdentifier {
592 span: Range<usize>
594 },
595 InvalidIndex {
597 span: Range<usize>
599 },
600 InvalidFormatter {
602 span: Range<usize>
604 }
605}
606
607impl fmt::Display for TemplateError {
608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609 match self {
610 Self::UnmatchedClosingBrace {
611 index
612 } => {
613 write!(f, "unmatched closing brace at byte {}", index)
614 }
615 Self::UnterminatedPlaceholder {
616 start
617 } => {
618 write!(f, "placeholder starting at byte {} is not closed", start)
619 }
620 Self::NestedPlaceholder {
621 index
622 } => {
623 write!(
624 f,
625 "nested placeholder starting at byte {} is not supported",
626 index
627 )
628 }
629 Self::EmptyPlaceholder {
630 start
631 } => {
632 write!(f, "placeholder starting at byte {} is empty", start)
633 }
634 Self::InvalidIdentifier {
635 span
636 } => {
637 write!(
638 f,
639 "invalid placeholder identifier spanning bytes {}..{}",
640 span.start, span.end
641 )
642 }
643 Self::InvalidIndex {
644 span
645 } => {
646 write!(
647 f,
648 "positional placeholder spanning bytes {}..{} is not a valid unsigned integer",
649 span.start, span.end
650 )
651 }
652 Self::InvalidFormatter {
653 span
654 } => {
655 write!(
656 f,
657 "placeholder spanning bytes {}..{} uses an unsupported formatter",
658 span.start, span.end
659 )
660 }
661 }
662 }
663}
664
665impl std::error::Error for TemplateError {}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670
671 fn named(name: &str) -> TemplateIdentifier<'_> {
672 TemplateIdentifier::Named(name)
673 }
674
675 #[test]
676 fn parses_basic_template() {
677 let template = ErrorTemplate::parse("{code}: {message}").expect("parse");
678 let segments = template.segments();
679 assert_eq!(segments.len(), 3);
680 assert!(matches!(segments[0], TemplateSegment::Placeholder(_)));
681 assert!(matches!(segments[1], TemplateSegment::Literal(": ")));
682 assert!(matches!(segments[2], TemplateSegment::Placeholder(_)));
683 let placeholders: Vec<_> = template.placeholders().collect();
684 assert_eq!(placeholders.len(), 2);
685 assert_eq!(placeholders[0].identifier(), &named("code"));
686 assert_eq!(placeholders[1].identifier(), &named("message"));
687 }
688
689 #[test]
690 fn parses_implicit_identifiers() {
691 let template = ErrorTemplate::parse("{}, {:?}, {name}, {}").expect("parse");
692 let mut placeholders = template.placeholders();
693 let first = placeholders.next().expect("first placeholder");
694 assert_eq!(first.identifier(), &TemplateIdentifier::Implicit(0));
695 assert_eq!(
696 first.formatter(),
697 &TemplateFormatter::Display {
698 spec: None
699 }
700 );
701 let second = placeholders.next().expect("second placeholder");
702 assert_eq!(second.identifier(), &TemplateIdentifier::Implicit(1));
703 assert_eq!(
704 second.formatter(),
705 &TemplateFormatter::Debug {
706 alternate: false
707 }
708 );
709 let third = placeholders.next().expect("third placeholder");
710 assert_eq!(third.identifier(), &named("name"));
711 let fourth = placeholders.next().expect("fourth placeholder");
712 assert_eq!(fourth.identifier(), &TemplateIdentifier::Implicit(2));
713 assert!(placeholders.next().is_none());
714 }
715
716 #[test]
717 fn parses_debug_formatter() {
718 let template = ErrorTemplate::parse("{0:#?}").expect("parse");
719 let placeholders: Vec<_> = template.placeholders().collect();
720 assert_eq!(placeholders.len(), 1);
721 assert_eq!(
722 placeholders[0].identifier(),
723 &TemplateIdentifier::Positional(0)
724 );
725 assert_eq!(
726 placeholders[0].formatter(),
727 &TemplateFormatter::Debug {
728 alternate: true
729 }
730 );
731 assert!(placeholders[0].formatter().is_alternate());
732 }
733
734 #[test]
735 fn parses_extended_formatters() {
736 let cases = [
737 (
738 "{value:x}",
739 TemplateFormatter::LowerHex {
740 alternate: false
741 }
742 ),
743 (
744 "{value:#x}",
745 TemplateFormatter::LowerHex {
746 alternate: true
747 }
748 ),
749 (
750 "{value:X}",
751 TemplateFormatter::UpperHex {
752 alternate: false
753 }
754 ),
755 (
756 "{value:#X}",
757 TemplateFormatter::UpperHex {
758 alternate: true
759 }
760 ),
761 (
762 "{value:p}",
763 TemplateFormatter::Pointer {
764 alternate: false
765 }
766 ),
767 (
768 "{value:#p}",
769 TemplateFormatter::Pointer {
770 alternate: true
771 }
772 ),
773 (
774 "{value:b}",
775 TemplateFormatter::Binary {
776 alternate: false
777 }
778 ),
779 (
780 "{value:#b}",
781 TemplateFormatter::Binary {
782 alternate: true
783 }
784 ),
785 (
786 "{value:o}",
787 TemplateFormatter::Octal {
788 alternate: false
789 }
790 ),
791 (
792 "{value:#o}",
793 TemplateFormatter::Octal {
794 alternate: true
795 }
796 ),
797 (
798 "{value:e}",
799 TemplateFormatter::LowerExp {
800 alternate: false
801 }
802 ),
803 (
804 "{value:#e}",
805 TemplateFormatter::LowerExp {
806 alternate: true
807 }
808 ),
809 (
810 "{value:E}",
811 TemplateFormatter::UpperExp {
812 alternate: false
813 }
814 ),
815 (
816 "{value:#E}",
817 TemplateFormatter::UpperExp {
818 alternate: true
819 }
820 )
821 ];
822 for (template_str, expected) in &cases {
823 let template = ErrorTemplate::parse(template_str).expect("parse");
824 let placeholder = template.placeholders().next().expect("placeholder present");
825 assert_eq!(placeholder.formatter(), expected, "case: {template_str}");
826 }
827 }
828
829 #[test]
830 fn preserves_hash_fill_display_specs() {
831 let template = ErrorTemplate::parse("{value:#>4}").expect("parse");
832 let placeholder = template.placeholders().next().expect("placeholder present");
833 assert_eq!(placeholder.formatter().display_spec(), Some("#>4"));
834 assert_eq!(
835 placeholder.formatter().format_fragment().as_deref(),
836 Some("#>4")
837 );
838 let expected = TemplateFormatter::Display {
839 spec: Some("#>4".into())
840 };
841 assert_eq!(placeholder.formatter(), &expected);
842 }
843
844 #[test]
845 fn formatter_kind_helpers_cover_all_variants() {
846 let table = [
847 (TemplateFormatterKind::Debug, '?'),
848 (TemplateFormatterKind::LowerHex, 'x'),
849 (TemplateFormatterKind::UpperHex, 'X'),
850 (TemplateFormatterKind::Pointer, 'p'),
851 (TemplateFormatterKind::Binary, 'b'),
852 (TemplateFormatterKind::Octal, 'o'),
853 (TemplateFormatterKind::LowerExp, 'e'),
854 (TemplateFormatterKind::UpperExp, 'E')
855 ];
856 for (kind, specifier) in table {
857 assert_eq!(TemplateFormatterKind::from_specifier(specifier), Some(kind));
858 assert_eq!(kind.specifier(), Some(specifier));
859 let with_alternate = TemplateFormatter::from_kind(kind, true);
860 let without_alternate = TemplateFormatter::from_kind(kind, false);
861 assert_eq!(with_alternate.kind(), kind);
862 assert_eq!(without_alternate.kind(), kind);
863 if kind.supports_alternate() {
864 assert!(with_alternate.is_alternate());
865 assert!(!without_alternate.is_alternate());
866 } else {
867 assert!(!with_alternate.is_alternate());
868 assert!(!without_alternate.is_alternate());
869 }
870 }
871 let display = TemplateFormatter::from_kind(TemplateFormatterKind::Display, true);
872 assert_eq!(display.kind(), TemplateFormatterKind::Display);
873 assert!(!display.is_alternate());
874 assert_eq!(TemplateFormatterKind::Display.specifier(), None);
875 assert!(!TemplateFormatterKind::Display.supports_alternate());
876 }
877
878 #[test]
879 fn handles_brace_escaping() {
880 let template = ErrorTemplate::parse("{{}} -> {value}").expect("parse");
881 let mut iter = template.segments().iter();
882 assert!(matches!(iter.next(), Some(TemplateSegment::Literal("{"))));
883 assert!(matches!(iter.next(), Some(TemplateSegment::Literal("}"))));
884 assert!(matches!(
885 iter.next(),
886 Some(TemplateSegment::Literal(" -> "))
887 ));
888 assert!(matches!(
889 iter.next(),
890 Some(TemplateSegment::Placeholder(TemplatePlaceholder { .. }))
891 ));
892 assert!(iter.next().is_none());
893 }
894
895 #[test]
896 fn rejects_unmatched_closing_brace() {
897 let err = ErrorTemplate::parse("oops}").expect_err("should fail");
898 assert!(matches!(
899 err,
900 TemplateError::UnmatchedClosingBrace {
901 index: 4
902 }
903 ));
904 }
905
906 #[test]
907 fn rejects_unterminated_placeholder() {
908 let err = ErrorTemplate::parse("{oops").expect_err("should fail");
909 assert!(matches!(
910 err,
911 TemplateError::UnterminatedPlaceholder {
912 start: 0
913 }
914 ));
915 }
916
917 #[test]
918 fn rejects_invalid_identifier() {
919 let err = ErrorTemplate::parse("{invalid-name}").expect_err("should fail");
920 assert!(matches!(err, TemplateError::InvalidIdentifier { span } if span == (0..14)));
921 }
922
923 #[test]
924 fn rejects_unknown_formatter() {
925 let err = ErrorTemplate::parse("{value:%}").expect_err("should fail");
926 assert!(matches!(err, TemplateError::InvalidFormatter { span } if span == (0..9)));
927 }
928
929 #[test]
930 fn display_with_resolves_placeholders() {
931 let template = ErrorTemplate::parse("{code}: {message}").expect("parse");
932 let code = 418;
933 let message = "I'm a teapot";
934 let rendered = format!(
935 "{}",
936 template.display_with(|placeholder, f| match placeholder.identifier() {
937 TemplateIdentifier::Named("code") => write!(f, "{}", code),
938 TemplateIdentifier::Named("message") => f.write_str(message),
939 other => panic!("unexpected placeholder: {:?}", other)
940 })
941 );
942 assert_eq!(rendered, "418: I'm a teapot");
943 }
944}