use std::marker::PhantomData;
use std::path::Path;
pub struct Uninitialized;
pub struct Initialized;
pub struct Validated;
pub struct Registry<State = Uninitialized> {
templates: Vec<Template>,
_state: PhantomData<State>,
}
impl Registry<Uninitialized> {
pub fn new() -> Self {
Registry {
templates: Vec::new(),
_state: PhantomData,
}
}
pub fn initialize(self, path: &Path) -> Result<Registry<Initialized>, RegistryError> {
let templates = discover_templates(path)?;
if templates.is_empty() {
return Err(RegistryError::NoTemplatesFound {
path: path.to_string_lossy().to_string(),
});
}
Ok(Registry {
templates,
_state: PhantomData,
})
}
}
impl Registry<Initialized> {
pub fn validate(self) -> Result<Registry<Validated>, RegistryError> {
for template in &self.templates {
validate_template(template)?;
}
Ok(Registry {
templates: self.templates,
_state: PhantomData,
})
}
pub fn count(&self) -> usize {
self.templates.len()
}
}
impl Registry<Validated> {
pub fn search(&self, query: &str) -> Result<Vec<&Template>, RegistryError> {
let results = self
.templates
.iter()
.filter(|t| t.matches_query(query))
.collect();
Ok(results)
}
pub fn render(&self, template: &Template, context: Context) -> Result<String, RegistryError> {
render_template(template, context)
}
pub fn templates(&self) -> &[Template] {
&self.templates
}
pub fn count(&self) -> usize {
self.templates.len()
}
}
#[derive(Debug, Clone)]
pub struct Template {
name: String,
path: String,
content: String,
}
impl Template {
fn matches_query(&self, query: &str) -> bool {
self.name.contains(query) || self.path.contains(query)
}
}
#[derive(Debug, Clone, Default)]
pub struct Context {
variables: std::collections::HashMap<String, String>,
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum RegistryError {
#[error("No templates found at path: {path}")]
NoTemplatesFound { path: String },
#[error("Template validation failed: {reason}")]
ValidationFailed { reason: String },
#[error("Template not found: {name}")]
TemplateNotFound { name: String },
#[error("Rendering failed: {reason}")]
RenderingFailed { reason: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
fn discover_templates(path: &Path) -> Result<Vec<Template>, RegistryError> {
Ok(vec![Template {
name: "example".to_string(),
path: path.join("example.tmpl").to_string_lossy().to_string(),
content: "{{ content }}".to_string(),
}])
}
fn validate_template(template: &Template) -> Result<(), RegistryError> {
if template.content.is_empty() {
return Err(RegistryError::ValidationFailed {
reason: "Template content is empty".to_string(),
});
}
Ok(())
}
fn render_template(template: &Template, _context: Context) -> Result<String, RegistryError> {
Ok(template.content.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::expect_used)]
fn test_state_machine_valid_transitions() {
let registry = Registry::new();
let registry = registry
.initialize(Path::new("templates"))
.expect("initialize failed");
let registry = registry.validate().expect("validate failed");
let _results = registry.search("test").expect("search failed");
}
#[test]
fn test_uninitialized_count_not_available() {
}
#[test]
fn test_initialized_search_not_available() {
}
#[test]
#[allow(clippy::expect_used)]
fn test_validated_state_methods_available() {
let registry = Registry::new()
.initialize(Path::new("templates"))
.expect("initialize failed")
.validate()
.expect("validate failed");
assert!(registry.count() > 0);
assert!(registry.templates().len() > 0);
assert!(registry.search("example").is_ok());
}
}