A pit full of rusty nails
at main 345 lines 10 kB view raw
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}