Skip to main content

lmn_core/request_template/validators/
object.rs

1use std::collections::HashMap;
2
3use crate::request_template::definition::TemplateDef;
4use crate::request_template::error::TemplateError;
5use crate::request_template::validators::Validator;
6
7// ── Validated ─────────────────────────────────────────────────────────────────
8
9pub struct ObjectDef {
10    /// Maps output field name → referenced placeholder name (without `{{}}`)
11    pub composition: HashMap<String, String>,
12}
13
14// ── Validator ─────────────────────────────────────────────────────────────────
15
16pub struct ObjectValidator {
17    pub composition: HashMap<String, String>,
18}
19
20impl Validator for ObjectValidator {
21    fn validate(self, _name: &str) -> Result<TemplateDef, TemplateError> {
22        let composition = self
23            .composition
24            .into_iter()
25            .map(|(field, placeholder)| {
26                extract_plain_name(&placeholder)
27                    .map(|n| (field, n.to_string()))
28                    .ok_or_else(|| {
29                        TemplateError::InvalidConstraint(format!(
30                            "object composition value '{placeholder}' is not a valid \
31                             placeholder — expected '{{{{name}}}}'"
32                        ))
33                    })
34            })
35            .collect::<Result<HashMap<_, _>, _>>()?;
36
37        Ok(TemplateDef::Object(ObjectDef { composition }))
38    }
39}
40
41fn extract_plain_name(s: &str) -> Option<&str> {
42    let inner = s.trim().strip_prefix("{{")?.strip_suffix("}}")?;
43    Some(inner.trim_end_matches(":global"))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::request_template::validators::Validator;
50
51    #[test]
52    fn validates_valid_composition() {
53        let comp = [("amount".to_string(), "{{price}}".to_string())]
54            .into_iter()
55            .collect();
56        assert!(ObjectValidator { composition: comp }.validate("x").is_ok());
57    }
58
59    #[test]
60    fn rejects_non_placeholder_value() {
61        let comp = [("amount".to_string(), "price".to_string())]
62            .into_iter()
63            .collect();
64        assert!(ObjectValidator { composition: comp }.validate("x").is_err());
65    }
66
67    #[test]
68    fn extract_plain_name_strips_braces() {
69        assert_eq!(extract_plain_name("{{name}}"), Some("name"));
70    }
71
72    #[test]
73    fn extract_plain_name_strips_global_suffix() {
74        assert_eq!(extract_plain_name("{{name:global}}"), Some("name"));
75    }
76
77    #[test]
78    fn extract_plain_name_returns_none_for_plain_string() {
79        assert_eq!(extract_plain_name("plain"), None);
80    }
81}