A pit full of rusty nails
1use std::sync::Arc;
2
3use bytes::BytesMut;
4use winnow::{
5 ModalResult, Parser,
6 ascii::{Caseless, alpha1, space0},
7 combinator::alt,
8 error::ErrMode,
9 token::{literal, rest, take_until},
10};
11
12fn process_template<'any>(template: &'any mut &str) -> ModalResult<(&'any str, TemplateState)> {
13 match take_until(0.., "{{").parse_next(template) {
14 Ok(part) => {
15 literal("{{").parse_next(template)?;
16
17 space0(template)?;
18
19 let mut placeholder = alpha1(template)?;
20
21 space0(template)?;
22
23 literal("}}").parse_next(template)?;
24
25 let placeholder = &mut placeholder;
26
27 let state = alt((
28 Caseless("title").map(|_| (part, TemplateState::Title)),
29 Caseless("initial").map(|_| (part, TemplateState::Initial)),
30 Caseless("main").map(|_| (part, TemplateState::Main)),
31 Caseless("extra").map(|_| (part, TemplateState::Extra)),
32 Caseless("footer").map(|_| (part, TemplateState::Footer)),
33 ))
34 .parse_next(placeholder)?;
35
36 rest.verify(|remaining: &str| remaining.is_empty())
37 .parse_next(placeholder)?;
38
39 Ok(state)
40 }
41 Err(ErrMode::Backtrack(_)) => Ok((template, TemplateState::Finished)),
42 Err(e) => Err(e),
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum Template {
48 Warning(WarningTemplate),
49 Generated(GeneratedTemplate),
50}
51
52impl Template {
53 pub fn get_template(&self) -> &Arc<str> {
54 match self {
55 Self::Warning(index_template) => &index_template.template,
56 Self::Generated(generated_template) => &generated_template.template,
57 }
58 }
59
60 pub fn get_static_content(&self) -> Option<&str> {
61 match self {
62 Self::Warning(index_template) => Some(index_template.static_content.as_ref()),
63 Self::Generated(_) => None,
64 }
65 }
66}
67
68impl From<WarningTemplate> for Template {
69 fn from(value: WarningTemplate) -> Self {
70 Self::Warning(value)
71 }
72}
73
74impl From<GeneratedTemplate> for Template {
75 fn from(value: GeneratedTemplate) -> Self {
76 Self::Generated(value)
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81pub struct WarningTemplate {
82 template: Arc<str>,
83 static_content: Arc<str>,
84}
85
86impl WarningTemplate {
87 pub fn init(template: Arc<str>, static_content: Arc<str>) -> Result<Self, TemplateError> {
88 let cursor = TemplateCursor::new(&template);
89
90 if cursor.validate(&[
91 TemplateState::Title,
92 TemplateState::Main,
93 TemplateState::Footer,
94 ]) {
95 Ok(Self {
96 template,
97 static_content,
98 })
99 } else {
100 Err(TemplateError)
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
106pub struct GeneratedTemplate {
107 template: Arc<str>,
108}
109
110impl GeneratedTemplate {
111 pub fn init(template: Arc<str>) -> Result<Self, TemplateError> {
112 let cursor = TemplateCursor::new(&template);
113
114 if cursor.validate(&[
115 TemplateState::Title,
116 TemplateState::Initial,
117 TemplateState::Main,
118 TemplateState::Extra,
119 TemplateState::Footer,
120 ]) {
121 Ok(Self { template })
122 } else {
123 Err(TemplateError)
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct TemplateError;
130
131impl core::fmt::Display for TemplateError {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "Invalid template, contains disallowed placeholders")
134 }
135}
136
137impl core::error::Error for TemplateError {}
138
139pub struct TemplateCursor {
140 cursor: *const str,
141}
142
143impl TemplateCursor {
144 pub fn new(template: &Arc<str>) -> Self {
145 Self {
146 cursor: Arc::as_ptr(template),
147 }
148 }
149
150 fn cast_cursor(&self) -> &str {
151 // SAFETY: The pointer will always be valid as we own an Arc instance
152 // within the future, meaning as long as the future lives, the
153 // Arc will never be dropped and cleaned.
154 unsafe { &*self.cursor }
155 }
156
157 /// Advances the template cursor to the next placeholder point, writing the
158 /// portion of the static template to the buffer before returning the placeholder
159 /// reached by the cursor. If the template has no placeholders, the entire
160 /// template will be written to the buffer and the state returned will be
161 /// [`TemplateState::Finished`].
162 pub fn write_template(&mut self, buffer: &mut BytesMut) -> TemplateState {
163 let template = &mut self.cast_cursor();
164
165 match process_template(template) {
166 Ok((part, state)) => {
167 buffer.extend_from_slice(part.as_bytes());
168
169 self.cursor = *template;
170
171 state
172 }
173 Err(_) => TemplateState::Finished,
174 }
175 }
176
177 fn validate(&self, included: &[TemplateState]) -> bool {
178 let template = &mut self.cast_cursor();
179
180 loop {
181 match process_template(template) {
182 Ok((_, state)) => {
183 if state == TemplateState::Finished {
184 return true;
185 }
186
187 if !included.contains(&state) {
188 return false;
189 }
190 }
191 Err(_) => return false,
192 }
193 }
194 }
195}
196
197// SAFETY: TemplateCursor carries an Arc reference to the str allocated on the heap.
198// This means the pointer will never be invalidated as long as the Arc instance lives,
199// and as it is immutable, the allocation will never be changed.
200unsafe impl Send for TemplateCursor {}
201// SAFETY: TemplateCursor carries an Arc reference to the str allocated on the heap.
202// This means the pointer will never be invalidated as long as the Arc instance lives,
203// and as it is immutable, the allocation will never be changed.
204unsafe impl Sync for TemplateCursor {}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum TemplateState {
208 Title,
209 Initial,
210 Main,
211 Extra,
212 Footer,
213 Finished,
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn extracts_placeholders() -> color_eyre::Result<()> {
222 let mut template = "<h1>{{ TITLE }}</h1><p>{{main}}</p>";
223
224 let template = &mut template;
225
226 let (extracted, state) = process_template(template)
227 .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?;
228
229 assert_eq!(extracted, "<h1>");
230 assert_eq!(state, TemplateState::Title);
231 assert_eq!(*template, "</h1><p>{{main}}</p>");
232
233 let (extracted, state) = process_template(template)
234 .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?;
235
236 assert_eq!(extracted, "</h1><p>");
237 assert_eq!(state, TemplateState::Main);
238 assert_eq!(*template, "</p>");
239
240 let (extracted, state) = process_template(template)
241 .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?;
242
243 assert_eq!(extracted, "</p>");
244 assert_eq!(state, TemplateState::Finished);
245 assert_eq!(*template, "</p>");
246
247 Ok(())
248 }
249
250 #[test]
251 fn writes_template_to_buffer() {
252 let template: Arc<str> = Arc::from("<h1>{{ TITLE }}</h1><p>{{ MAIN }}</p>");
253
254 let mut cursor = TemplateCursor::new(&template);
255
256 let mut buffer = BytesMut::new();
257
258 loop {
259 match cursor.write_template(&mut buffer) {
260 TemplateState::Finished => {
261 break;
262 }
263 TemplateState::Title | TemplateState::Main => {
264 buffer.extend_from_slice(b"aaa");
265 }
266 state => panic!("INVALID STATE: {state:?}"),
267 }
268 }
269
270 assert_eq!(buffer.as_ref(), b"<h1>aaa</h1><p>aaa</p>");
271 }
272
273 #[test]
274 fn writes_static_template_to_buffer() {
275 let template: Arc<str> = Arc::from("<h1>Static</h1><p>Template</p>");
276
277 let mut cursor = TemplateCursor::new(&template);
278
279 let mut buffer = BytesMut::new();
280
281 let state = cursor.write_template(&mut buffer);
282
283 assert_eq!(state, TemplateState::Finished);
284 assert_eq!(buffer.as_ref(), b"<h1>Static</h1><p>Template</p>");
285 }
286
287 #[test]
288 fn wont_write_empty_template_to_buffer() {
289 let template: Arc<str> = Arc::from("");
290
291 let mut cursor = TemplateCursor::new(&template);
292
293 let mut buffer = BytesMut::new();
294
295 let state = cursor.write_template(&mut buffer);
296
297 assert_eq!(state, TemplateState::Finished);
298 assert!(buffer.is_empty());
299 }
300
301 #[test]
302 fn validates_templates_with_placeholders() {
303 let template: Arc<str> = Arc::from("<h1>{{ TITLE }}</h1><p>{{ MAIN }}</p>");
304
305 let generated_template = GeneratedTemplate::init(template.clone());
306
307 assert_eq!(generated_template, Ok(GeneratedTemplate { template }));
308 }
309
310 #[test]
311 fn validates_templates_with_invalid_placeholders() {
312 // Partial matches should still be invalid
313 let template: Arc<str> = Arc::from("<h1>{{ TITLE }}</h1><p>{{ MAINZ }}</p>");
314
315 let generated_template = GeneratedTemplate::init(template);
316
317 assert_eq!(generated_template, Err(TemplateError));
318
319 let template: Arc<str> = Arc::from("<h1>{{ TITLE }}</h1><p>{{ huurrrr }}</p>");
320
321 let generated_template = GeneratedTemplate::init(template);
322
323 assert_eq!(generated_template, Err(TemplateError));
324 }
325
326 #[test]
327 fn validates_static_templates() {
328 // Static templates are also valid
329 let template: Arc<str> = Arc::from("<h1>Static</h1><p>Template</p>");
330
331 let generated_template = GeneratedTemplate::init(template.clone());
332
333 assert_eq!(generated_template, Ok(GeneratedTemplate { template }));
334 }
335
336 #[test]
337 fn validates_empty_templates() {
338 // Empty templates should be valid, since they can be handled
339 let template: Arc<str> = Arc::from("");
340
341 let generated_template = GeneratedTemplate::init(template.clone());
342
343 assert_eq!(generated_template, Ok(GeneratedTemplate { template }));
344 }
345}