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