1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum ConvertError {
6 UnsupportedSyntax { syntax: String },
8 MissingFTemplate,
10 MultipleFTemplates { count: usize },
12 MissingFTemplateName,
14 EmptyFTemplateName,
16 MissingInnerTemplate,
18 MultipleInnerTemplates { count: usize },
20 UnclosedElement { tag: String, context: String },
22 UnclosedTag { context: String },
24 MissingValueAttribute { tag: String, context: String },
26 InvalidDirectiveValue {
28 tag: String,
29 value: Option<String>,
30 context: String,
31 },
32 InvalidRepeatExpression { expr: String, context: String },
34 UnclosedBinding { context: String },
36 EmptyBinding { context: String },
38 UnsupportedFAttribute { attribute: String, context: String },
40 UnsupportedFElement { tag: String, context: String },
42 UnsupportedExpression {
44 expr: String,
45 reason: String,
46 context: String,
47 },
48 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
141pub(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}