Skip to main content

microsoft_fast_convert/
error.rs

1use std::fmt;
2
3/// An error encountered while converting a FAST declarative template.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum ConvertError {
6    /// The requested target syntax is not supported.
7    UnsupportedSyntax { syntax: String },
8    /// No `<f-template>` element was present in the input.
9    MissingFTemplate,
10    /// More than one `<f-template>` element was present in the input.
11    MultipleFTemplates { count: usize },
12    /// The `<f-template>` element is missing a string-valued `name` attribute.
13    MissingFTemplateName,
14    /// The `<f-template name="…">` value is empty or whitespace.
15    EmptyFTemplateName,
16    /// The `<f-template>` element does not contain an inner `<template>` element.
17    MissingInnerTemplate,
18    /// The `<f-template>` element contains more than one inner `<template>` element.
19    MultipleInnerTemplates { count: usize },
20    /// An HTML element was not closed.
21    UnclosedElement { tag: String, context: String },
22    /// An opening tag was not terminated with `>`.
23    UnclosedTag { context: String },
24    /// A directive is missing its required `value="{{…}}"` attribute.
25    MissingValueAttribute { tag: String, context: String },
26    /// A directive `value` attribute is not wrapped in declarative binding delimiters.
27    InvalidDirectiveValue {
28        tag: String,
29        value: Option<String>,
30        context: String,
31    },
32    /// An `<f-repeat>` expression is not in `item in items` form.
33    InvalidRepeatExpression { expr: String, context: String },
34    /// A double-brace binding is not closed.
35    UnclosedBinding { context: String },
36    /// A double-brace binding has no expression.
37    EmptyBinding { context: String },
38    /// An `f-*` attribute is not supported by the converter.
39    UnsupportedFAttribute { attribute: String, context: String },
40    /// An `f-*` element is not supported by the converter.
41    UnsupportedFElement { tag: String, context: String },
42    /// A declarative expression is outside the supported converter grammar.
43    UnsupportedExpression {
44        expr: String,
45        reason: String,
46        context: String,
47    },
48    /// An event handler expression could not be converted.
49    UnsupportedEventHandler {
50        value: String,
51        reason: String,
52        context: String,
53    },
54}
55
56impl fmt::Display for ConvertError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::UnsupportedSyntax { syntax } => write!(
60                f,
61                "unsupported syntax '{syntax}': accepted values are {}",
62                crate::syntax::accepted_syntax_names()
63            ),
64            Self::MissingFTemplate => write!(
65                f,
66                "template validation error: expected exactly one '<f-template>' element"
67            ),
68            Self::MultipleFTemplates { count } => write!(
69                f,
70                "template validation error: expected exactly one '<f-template>' element, found {count}"
71            ),
72            Self::MissingFTemplateName => write!(
73                f,
74                "template validation error: '<f-template>' must include a string 'name' attribute"
75            ),
76            Self::EmptyFTemplateName => write!(
77                f,
78                "template validation error: '<f-template name>' cannot be empty"
79            ),
80            Self::MissingInnerTemplate => write!(
81                f,
82                "template validation error: '<f-template>' must contain exactly one inner '<template>' element"
83            ),
84            Self::MultipleInnerTemplates { count } => write!(
85                f,
86                "template validation error: '<f-template>' must contain exactly one inner '<template>' element, found {count}"
87            ),
88            Self::UnclosedElement { tag, context } => write!(
89                f,
90                "unclosed element '<{tag}>': no matching '</{tag}>' closing tag was found — template: \"{context}\""
91            ),
92            Self::UnclosedTag { context } => write!(
93                f,
94                "unclosed tag: no closing '>' was found — template: \"{context}\""
95            ),
96            Self::MissingValueAttribute { tag, context } => write!(
97                f,
98                "directive '<{tag}>' is missing a valid 'value=\"{{{{…}}}}\"' attribute — template: \"{context}\""
99            ),
100            Self::InvalidDirectiveValue { tag, value, context } => {
101                let value = value.as_deref().unwrap_or("");
102                write!(
103                    f,
104                    "directive '<{tag}>' has invalid value '{value}': expected 'value=\"{{{{…}}}}\"' — template: \"{context}\""
105                )
106            }
107            Self::InvalidRepeatExpression { expr, context } => write!(
108                f,
109                "invalid repeat expression '{{{{{expr}}}}}': expected 'item in items' format — template: \"{context}\""
110            ),
111            Self::UnclosedBinding { context } => write!(
112                f,
113                "unclosed binding '{{{{…': no closing '}}}}' found — template: \"{context}\""
114            ),
115            Self::EmptyBinding { context } => write!(
116                f,
117                "empty binding '{{{{}}}}': the expression between '{{{{' and '}}}}' cannot be empty — template: \"{context}\""
118            ),
119            Self::UnsupportedFAttribute { attribute, context } => write!(
120                f,
121                "unsupported f-* attribute '{attribute}' — template: \"{context}\""
122            ),
123            Self::UnsupportedFElement { tag, context } => write!(
124                f,
125                "unsupported f-* element '<{tag}>' — template: \"{context}\""
126            ),
127            Self::UnsupportedExpression { expr, reason, context } => write!(
128                f,
129                "unsupported expression '{{{{{expr}}}}}': {reason} — template: \"{context}\""
130            ),
131            Self::UnsupportedEventHandler { value, reason, context } => write!(
132                f,
133                "unsupported event handler '{{{value}}}': {reason} — template: \"{context}\""
134            ),
135        }
136    }
137}
138
139impl std::error::Error for ConvertError {}
140
141/// Extract a short snippet of `template` around `at` for use in error messages.
142pub(crate) fn template_context(template: &str, at: usize) -> String {
143    const PRE: usize = 20;
144    const POST: usize = 80;
145
146    let mut start = at.saturating_sub(PRE);
147    while start > 0 && !template.is_char_boundary(start) {
148        start -= 1;
149    }
150
151    let mut end = (at + POST).min(template.len());
152    while end < template.len() && !template.is_char_boundary(end) {
153        end += 1;
154    }
155
156    let prefix = if start > 0 { "…" } else { "" };
157    let suffix = if end < template.len() { "…" } else { "" };
158    format!("{prefix}{}{suffix}", &template[start..end])
159}