use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct QueryMacro {
pub name: String,
pub params: Vec<MacroParam>,
pub body: String,
}
#[derive(Debug, Clone)]
pub struct MacroParam {
pub name: String,
pub default: Option<String>,
}
#[derive(Debug, Default)]
pub struct MacroRegistry {
macros: HashMap<String, QueryMacro>,
}
impl MacroRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn define(&mut self, macro_def: QueryMacro) -> Result<(), String> {
if macro_def.name.is_empty() {
return Err("macro name cannot be empty".into());
}
self.macros.insert(macro_def.name.clone(), macro_def);
Ok(())
}
pub fn expand(&self, name: &str, args: &[&str]) -> Result<String, String> {
let macro_def = self
.macros
.get(name)
.ok_or_else(|| format!("undefined macro: {}", name))?;
let mut body = macro_def.body.clone();
for (i, param) in macro_def.params.iter().enumerate() {
let value = if i < args.len() {
args[i].to_string()
} else if let Some(default) = ¶m.default {
default.clone()
} else {
return Err(format!(
"missing argument '{}' for macro '{}'",
param.name, name
));
};
body = body.replace(&format!("${}", param.name), &value);
}
Ok(body)
}
pub fn list(&self) -> Vec<&QueryMacro> {
self.macros.values().collect()
}
pub fn exists(&self, name: &str) -> bool {
self.macros.contains_key(name)
}
pub fn remove(&mut self, name: &str) -> bool {
self.macros.remove(name).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_define_and_expand() {
let mut registry = MacroRegistry::new();
registry
.define(QueryMacro {
name: "recent_avg".into(),
params: vec![
MacroParam {
name: "pattern".into(),
default: None,
},
MacroParam {
name: "duration".into(),
default: Some("1h".into()),
},
],
body:
"SELECT AVG(value) FROM $pattern WHERE timestamp > NOW() - INTERVAL $duration"
.into(),
})
.unwrap();
let expanded = registry
.expand("recent_avg", &["\"sensor/*\"", "24h"])
.unwrap();
assert!(expanded.contains("\"sensor/*\""));
assert!(expanded.contains("24h"));
}
#[test]
fn test_default_parameter() {
let mut registry = MacroRegistry::new();
registry
.define(QueryMacro {
name: "check".into(),
params: vec![
MacroParam {
name: "key".into(),
default: None,
},
MacroParam {
name: "limit".into(),
default: Some("10".into()),
},
],
body: "SELECT * FROM $key LIMIT $limit".into(),
})
.unwrap();
let expanded = registry.expand("check", &["\"k\""]).unwrap();
assert!(expanded.contains("LIMIT 10"));
}
#[test]
fn test_missing_required_param() {
let mut registry = MacroRegistry::new();
registry
.define(QueryMacro {
name: "m".into(),
params: vec![MacroParam {
name: "x".into(),
default: None,
}],
body: "$x".into(),
})
.unwrap();
let err = registry.expand("m", &[]).unwrap_err();
assert!(err.contains("missing argument"));
}
#[test]
fn test_undefined_macro() {
let registry = MacroRegistry::new();
let err = registry.expand("nonexistent", &[]).unwrap_err();
assert!(err.contains("undefined macro"));
}
#[test]
fn test_list_and_remove() {
let mut registry = MacroRegistry::new();
registry
.define(QueryMacro {
name: "a".into(),
params: vec![],
body: "SELECT 1".into(),
})
.unwrap();
assert_eq!(registry.list().len(), 1);
assert!(registry.exists("a"));
assert!(registry.remove("a"));
assert!(!registry.exists("a"));
}
}