use std::sync::Arc; use bytes::BytesMut; use winnow::{ ModalResult, Parser, ascii::{Caseless, alpha1, space0}, combinator::alt, error::ErrMode, token::{literal, rest, take_until}, }; fn process_template<'any>(template: &'any mut &str) -> ModalResult<(&'any str, TemplateState)> { match take_until(0.., "{{").parse_next(template) { Ok(part) => { literal("{{").parse_next(template)?; space0(template)?; let mut placeholder = alpha1(template)?; space0(template)?; literal("}}").parse_next(template)?; let placeholder = &mut placeholder; let state = alt(( Caseless("title").map(|_| (part, TemplateState::Title)), Caseless("initial").map(|_| (part, TemplateState::Initial)), Caseless("main").map(|_| (part, TemplateState::Main)), Caseless("extra").map(|_| (part, TemplateState::Extra)), Caseless("footer").map(|_| (part, TemplateState::Footer)), )) .parse_next(placeholder)?; rest.verify(|remaining: &str| remaining.is_empty()) .parse_next(placeholder)?; Ok(state) } Err(ErrMode::Backtrack(_)) => Ok((template, TemplateState::Finished)), Err(e) => Err(e), } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Template { Warning(WarningTemplate), Generated(GeneratedTemplate), } impl Template { pub fn get_template(&self) -> &Arc { match self { Self::Warning(index_template) => &index_template.template, Self::Generated(generated_template) => &generated_template.template, } } pub fn get_static_content(&self) -> Option<&str> { match self { Self::Warning(index_template) => Some(index_template.static_content.as_ref()), Self::Generated(_) => None, } } } impl From for Template { fn from(value: WarningTemplate) -> Self { Self::Warning(value) } } impl From for Template { fn from(value: GeneratedTemplate) -> Self { Self::Generated(value) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct WarningTemplate { template: Arc, static_content: Arc, } impl WarningTemplate { pub fn init(template: Arc, static_content: Arc) -> Result { let cursor = TemplateCursor::new(&template); if cursor.validate(&[ TemplateState::Title, TemplateState::Main, TemplateState::Footer, ]) { Ok(Self { template, static_content, }) } else { Err(TemplateError) } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GeneratedTemplate { template: Arc, } impl GeneratedTemplate { pub fn init(template: Arc) -> Result { let cursor = TemplateCursor::new(&template); if cursor.validate(&[ TemplateState::Title, TemplateState::Initial, TemplateState::Main, TemplateState::Extra, TemplateState::Footer, ]) { Ok(Self { template }) } else { Err(TemplateError) } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TemplateError; impl core::fmt::Display for TemplateError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Invalid template, contains disallowed placeholders") } } impl core::error::Error for TemplateError {} pub struct TemplateCursor { cursor: *const str, } impl TemplateCursor { pub fn new(template: &Arc) -> Self { Self { cursor: Arc::as_ptr(template), } } fn cast_cursor(&self) -> &str { // SAFETY: The pointer will always be valid as we own an Arc instance // within the future, meaning as long as the future lives, the // Arc will never be dropped and cleaned. unsafe { &*self.cursor } } /// Advances the template cursor to the next placeholder point, writing the /// portion of the static template to the buffer before returning the placeholder /// reached by the cursor. If the template has no placeholders, the entire /// template will be written to the buffer and the state returned will be /// [`TemplateState::Finished`]. pub fn write_template(&mut self, buffer: &mut BytesMut) -> TemplateState { let template = &mut self.cast_cursor(); match process_template(template) { Ok((part, state)) => { buffer.extend_from_slice(part.as_bytes()); self.cursor = *template; state } Err(_) => TemplateState::Finished, } } fn validate(&self, included: &[TemplateState]) -> bool { let template = &mut self.cast_cursor(); loop { match process_template(template) { Ok((_, state)) => { if state == TemplateState::Finished { return true; } if !included.contains(&state) { return false; } } Err(_) => return false, } } } } // SAFETY: TemplateCursor carries an Arc reference to the str allocated on the heap. // This means the pointer will never be invalidated as long as the Arc instance lives, // and as it is immutable, the allocation will never be changed. unsafe impl Send for TemplateCursor {} // SAFETY: TemplateCursor carries an Arc reference to the str allocated on the heap. // This means the pointer will never be invalidated as long as the Arc instance lives, // and as it is immutable, the allocation will never be changed. unsafe impl Sync for TemplateCursor {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TemplateState { Title, Initial, Main, Extra, Footer, Finished, } #[cfg(test)] mod tests { use super::*; #[test] fn extracts_placeholders() -> color_eyre::Result<()> { let mut template = "

{{ TITLE }}

{{main}}

"; let template = &mut template; let (extracted, state) = process_template(template) .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?; assert_eq!(extracted, "

"); assert_eq!(state, TemplateState::Title); assert_eq!(*template, "

{{main}}

"); let (extracted, state) = process_template(template) .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?; assert_eq!(extracted, "

"); assert_eq!(state, TemplateState::Main); assert_eq!(*template, "

"); let (extracted, state) = process_template(template) .map_err(|a| color_eyre::eyre::eyre!("Template parsing error:\n{a}"))?; assert_eq!(extracted, "

"); assert_eq!(state, TemplateState::Finished); assert_eq!(*template, "

"); Ok(()) } #[test] fn writes_template_to_buffer() { let template: Arc = Arc::from("

{{ TITLE }}

{{ MAIN }}

"); let mut cursor = TemplateCursor::new(&template); let mut buffer = BytesMut::new(); loop { match cursor.write_template(&mut buffer) { TemplateState::Finished => { break; } TemplateState::Title | TemplateState::Main => { buffer.extend_from_slice(b"aaa"); } state => panic!("INVALID STATE: {state:?}"), } } assert_eq!(buffer.as_ref(), b"

aaa

aaa

"); } #[test] fn writes_static_template_to_buffer() { let template: Arc = Arc::from("

Static

Template

"); let mut cursor = TemplateCursor::new(&template); let mut buffer = BytesMut::new(); let state = cursor.write_template(&mut buffer); assert_eq!(state, TemplateState::Finished); assert_eq!(buffer.as_ref(), b"

Static

Template

"); } #[test] fn wont_write_empty_template_to_buffer() { let template: Arc = Arc::from(""); let mut cursor = TemplateCursor::new(&template); let mut buffer = BytesMut::new(); let state = cursor.write_template(&mut buffer); assert_eq!(state, TemplateState::Finished); assert!(buffer.is_empty()); } #[test] fn validates_templates_with_placeholders() { let template: Arc = Arc::from("

{{ TITLE }}

{{ MAIN }}

"); let generated_template = GeneratedTemplate::init(template.clone()); assert_eq!(generated_template, Ok(GeneratedTemplate { template })); } #[test] fn validates_templates_with_invalid_placeholders() { // Partial matches should still be invalid let template: Arc = Arc::from("

{{ TITLE }}

{{ MAINZ }}

"); let generated_template = GeneratedTemplate::init(template); assert_eq!(generated_template, Err(TemplateError)); let template: Arc = Arc::from("

{{ TITLE }}

{{ huurrrr }}

"); let generated_template = GeneratedTemplate::init(template); assert_eq!(generated_template, Err(TemplateError)); } #[test] fn validates_static_templates() { // Static templates are also valid let template: Arc = Arc::from("

Static

Template

"); let generated_template = GeneratedTemplate::init(template.clone()); assert_eq!(generated_template, Ok(GeneratedTemplate { template })); } #[test] fn validates_empty_templates() { // Empty templates should be valid, since they can be handled let template: Arc = Arc::from(""); let generated_template = GeneratedTemplate::init(template.clone()); assert_eq!(generated_template, Ok(GeneratedTemplate { template })); } }