Skip to main content

composable_runtime/config/
types.rs

1use anyhow::Result;
2use std::collections::HashMap;
3use std::path::Path;
4
5/// Source-agnostic property map with JSON values.
6pub type PropertyMap = HashMap<String, serde_json::Value>;
7
8/// A definition entry from any source (TOML file, .wasm path, programmatic API).
9#[derive(Debug, Clone)]
10pub struct GenericDefinition {
11    pub category: String,
12    pub name: String,
13    pub properties: PropertyMap,
14}
15
16// --- Selector types ---
17
18/// Comparison operator for a selector condition.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Operator {
21    Equals(String),
22    NotEquals(String),
23    In(Vec<String>),
24    NotIn(Vec<String>),
25    Exists,
26    DoesNotExist,
27}
28
29/// A single condition within a selector.
30#[derive(Debug, Clone)]
31pub struct Condition {
32    pub key: String,
33    pub operator: Operator,
34}
35
36/// Matches against a flattened string-to-string map.
37/// All conditions must match (AND semantics).
38#[derive(Debug, Clone)]
39pub struct Selector {
40    pub conditions: Vec<Condition>,
41}
42
43impl Selector {
44    pub fn matches(&self, properties: &HashMap<String, Option<String>>) -> bool {
45        self.conditions.iter().all(|c| c.matches(properties))
46    }
47}
48
49impl Condition {
50    fn matches(&self, properties: &HashMap<String, Option<String>>) -> bool {
51        match &self.operator {
52            Operator::Equals(expected) => properties
53                .get(&self.key)
54                .is_some_and(|v| v.as_ref().is_some_and(|v| v == expected)),
55            Operator::NotEquals(expected) => properties
56                .get(&self.key)
57                .is_some_and(|v| v.as_ref().is_some_and(|v| v != expected)),
58            Operator::In(values) => properties
59                .get(&self.key)
60                .is_some_and(|v| v.as_ref().is_some_and(|v| values.iter().any(|e| v == e))),
61            Operator::NotIn(values) => properties
62                .get(&self.key)
63                .is_some_and(|v| v.as_ref().is_some_and(|v| values.iter().all(|e| v != e))),
64            Operator::Exists => properties.contains_key(&self.key),
65            Operator::DoesNotExist => !properties.contains_key(&self.key),
66        }
67    }
68}
69
70/// A category claim with an optional selector for discriminator-based dispatch.
71#[derive(Debug, Clone)]
72pub struct CategoryClaim {
73    pub category: &'static str,
74    pub selector: Option<Selector>,
75}
76
77impl CategoryClaim {
78    /// Claim all definitions in a category (no selector filtering).
79    pub fn all(category: &'static str) -> Self {
80        Self {
81            category,
82            selector: None,
83        }
84    }
85
86    /// Claim definitions in a category that match a selector.
87    pub fn with_selector(category: &'static str, selector: Selector) -> Self {
88        Self {
89            category,
90            selector: Some(selector),
91        }
92    }
93}
94
95/// Reads configuration from a source and produces generic definitions.
96pub trait DefinitionLoader {
97    /// Claim a path and store it if this loader should handle it.
98    /// Default returns false for self-contained loaders.
99    fn claim(&mut self, _path: &Path) -> bool {
100        false
101    }
102
103    /// Load definitions from all claimed paths and/or internal sources.
104    fn load(&self) -> Result<Vec<GenericDefinition>>;
105}
106
107/// Handles configuration for one or more categories.
108pub trait ConfigHandler {
109    /// Categories this handler owns, with optional selector filtering.
110    /// A claim with no selector owns all definitions in that category.
111    /// Multiple handlers may claim the same category only if all use selectors.
112    fn claimed_categories(&self) -> Vec<CategoryClaim>;
113
114    /// Properties this handler uses, keyed by category.
115    /// Category owners should include their own properties (under their own category)
116    /// to prevent other handlers from claiming them. Properties claimed on other
117    /// handlers' categories will be split off and routed via `handle_properties`.
118    fn claimed_properties(&self) -> HashMap<&str, &[&str]> {
119        HashMap::new()
120    }
121
122    /// Handle a definition in an owned category.
123    /// Properties claimed by other handlers will be excluded.
124    fn handle_category(
125        &mut self,
126        category: &str,
127        name: &str,
128        properties: PropertyMap,
129    ) -> Result<()>;
130
131    /// Handle claimed properties from a category this handler does not own.
132    fn handle_properties(
133        &mut self,
134        _category: &str,
135        _name: &str,
136        _properties: PropertyMap,
137    ) -> Result<()> {
138        Ok(())
139    }
140}