Skip to main content

greentic_pack/
messaging.rs

1use std::collections::BTreeSet;
2
3use anyhow::{Result, bail};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Default)]
8pub struct MessagingSection {
9    #[serde(default, skip_serializing_if = "Option::is_none")]
10    pub adapters: Option<Vec<MessagingAdapter>>,
11}
12
13impl MessagingSection {
14    pub fn validate(&self) -> Result<()> {
15        let mut seen = BTreeSet::new();
16        if let Some(adapters) = &self.adapters {
17            for adapter in adapters {
18                adapter.validate()?;
19                if !seen.insert(adapter.name.clone()) {
20                    bail!("duplicate messaging adapter name: {}", adapter.name);
21                }
22            }
23        }
24        Ok(())
25    }
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
29pub struct MessagingAdapter {
30    pub name: String,
31    pub kind: MessagingAdapterKind,
32    pub component: String,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub default_flow: Option<String>,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub custom_flow: Option<String>,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub capabilities: Option<MessagingAdapterCapabilities>,
39}
40
41impl MessagingAdapter {
42    fn validate(&self) -> Result<()> {
43        if self.name.trim().is_empty() {
44            bail!("messaging.adapters[].name is required");
45        }
46        if self.component.trim().is_empty() {
47            bail!(
48                "messaging.adapters[{}].component must not be empty",
49                self.name
50            );
51        }
52        if let Some(cap) = &self.capabilities {
53            cap.validate(&self.name)?;
54        }
55        Ok(())
56    }
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
60#[serde(rename_all = "kebab-case")]
61pub enum MessagingAdapterKind {
62    Ingress,
63    Egress,
64    IngressEgress,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Default)]
68#[serde(deny_unknown_fields)]
69pub struct MessagingAdapterCapabilities {
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
71    pub direction: Vec<String>,
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub features: Vec<String>,
74}
75
76impl MessagingAdapterCapabilities {
77    fn validate(&self, name: &str) -> Result<()> {
78        for entry in &self.direction {
79            if entry.trim().is_empty() {
80                bail!(
81                    "messaging.adapters[{name}].capabilities.direction must not contain empty values"
82                );
83            }
84        }
85        for entry in &self.features {
86            if entry.trim().is_empty() {
87                bail!(
88                    "messaging.adapters[{name}].capabilities.features must not contain empty values"
89                );
90            }
91        }
92        Ok(())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    fn valid_adapter() -> MessagingAdapter {
101        MessagingAdapter {
102            name: "inbox".to_string(),
103            kind: MessagingAdapterKind::Ingress,
104            component: "component.messaging".to_string(),
105            default_flow: Some("flow.main".to_string()),
106            custom_flow: None,
107            capabilities: Some(MessagingAdapterCapabilities {
108                direction: vec!["ingress".to_string()],
109                features: vec!["retry".to_string()],
110            }),
111        }
112    }
113
114    #[test]
115    fn validate_accepts_unique_adapter() {
116        MessagingSection {
117            adapters: Some(vec![valid_adapter()]),
118        }
119        .validate()
120        .expect("valid adapter should pass");
121    }
122
123    #[test]
124    fn validate_rejects_duplicate_adapter_names() {
125        let adapter = valid_adapter();
126        let err = MessagingSection {
127            adapters: Some(vec![adapter.clone(), adapter]),
128        }
129        .validate()
130        .expect_err("duplicate names should fail");
131
132        assert!(err.to_string().contains("duplicate messaging adapter name"));
133    }
134
135    #[test]
136    fn validate_rejects_blank_feature_entry() {
137        let mut adapter = valid_adapter();
138        adapter
139            .capabilities
140            .as_mut()
141            .expect("caps")
142            .features
143            .push(" ".to_string());
144
145        let err = MessagingSection {
146            adapters: Some(vec![adapter]),
147        }
148        .validate()
149        .expect_err("blank feature should fail");
150
151        assert!(err.to_string().contains("capabilities.features"));
152    }
153}