Skip to main content

sim_lib_topology/
citizen.rs

1//! Citizen descriptors that present topology packages, nodes, and edges as
2//! runtime objects with stable class symbols.
3
4use sim_citizen_derive::Citizen;
5use sim_kernel::{Error, Expr, Result, Symbol};
6use sim_value::build::entry;
7
8/// Citizen object wrapping a validated `.simtopo` package source string.
9#[derive(Clone, Debug, PartialEq, Citizen)]
10#[citizen(symbol = "topology/Package", version = 1)]
11pub struct TopologyPackageDescriptor {
12    #[citizen(with = "package_source")]
13    source: String,
14}
15
16/// Citizen object wrapping a validated topology node expression.
17#[derive(Clone, Debug, PartialEq, Citizen)]
18#[citizen(symbol = "topology/Node", version = 1)]
19pub struct TopologyNodeDescriptor {
20    #[citizen(with = "node_expr")]
21    node: Expr,
22}
23
24/// Citizen object wrapping a validated topology edge expression.
25#[derive(Clone, Debug, PartialEq, Citizen)]
26#[citizen(symbol = "topology/Edge", version = 1)]
27pub struct TopologyEdgeDescriptor {
28    #[citizen(with = "edge_expr")]
29    edge: Expr,
30}
31
32impl TopologyPackageDescriptor {
33    /// Builds a descriptor, validating the package source.
34    pub fn new(source: impl Into<String>) -> Result<Self> {
35        let source = source.into();
36        package_source::decode(&Expr::String(source.clone()))?;
37        Ok(Self { source })
38    }
39
40    /// Returns the package source string.
41    pub fn source(&self) -> &str {
42        &self.source
43    }
44}
45
46impl Default for TopologyPackageDescriptor {
47    fn default() -> Self {
48        Self::new(
49            r#"
50graph:
51topology citizen-package
52node in verb=in
53node out verb=out
54wire in -> out
55"#,
56        )
57        .expect("default topology package descriptor should be valid")
58    }
59}
60
61impl TopologyNodeDescriptor {
62    /// Builds a descriptor, validating the node expression.
63    pub fn from_expr(node: Expr) -> Result<Self> {
64        node_expr::decode(&node)?;
65        Ok(Self { node })
66    }
67
68    /// Returns the node expression.
69    pub fn as_expr(&self) -> &Expr {
70        &self.node
71    }
72}
73
74impl Default for TopologyNodeDescriptor {
75    fn default() -> Self {
76        Self::from_expr(Expr::Map(vec![
77            entry("id", Expr::Symbol(Symbol::new("citizen-node"))),
78            entry("verb", Expr::Symbol(Symbol::new("wire"))),
79        ]))
80        .expect("default topology node descriptor should be valid")
81    }
82}
83
84impl TopologyEdgeDescriptor {
85    /// Builds a descriptor, validating the edge expression.
86    pub fn from_expr(edge: Expr) -> Result<Self> {
87        edge_expr::decode(&edge)?;
88        Ok(Self { edge })
89    }
90
91    /// Returns the edge expression.
92    pub fn as_expr(&self) -> &Expr {
93        &self.edge
94    }
95}
96
97impl Default for TopologyEdgeDescriptor {
98    fn default() -> Self {
99        Self::from_expr(Expr::Map(vec![
100            entry("from", Expr::Symbol(Symbol::new("citizen-node/out"))),
101            entry("to", Expr::Symbol(Symbol::new("citizen-out/in"))),
102        ]))
103        .expect("default topology edge descriptor should be valid")
104    }
105}
106
107/// Returns the class symbol for topology package descriptors.
108pub fn topology_package_class_symbol() -> Symbol {
109    Symbol::qualified("topology", "Package")
110}
111
112/// Returns the class symbol for topology node descriptors.
113pub fn topology_node_class_symbol() -> Symbol {
114    Symbol::qualified("topology", "Node")
115}
116
117/// Returns the class symbol for topology edge descriptors.
118pub fn topology_edge_class_symbol() -> Symbol {
119    Symbol::qualified("topology", "Edge")
120}
121
122pub(crate) mod package_source {
123    use sim_kernel::{Error, Expr, Result};
124
125    use crate::parse_package;
126
127    pub fn encode(source: &str) -> sim_kernel::Expr {
128        sim_kernel::Expr::String(source.to_owned())
129    }
130
131    pub fn decode(expr: &Expr) -> Result<String> {
132        let Expr::String(source) = expr else {
133            return Err(Error::Eval(
134                "topology package descriptor source must be a string".to_owned(),
135            ));
136        };
137        parse_package(source)?;
138        Ok(source.clone())
139    }
140}
141
142pub(crate) mod node_expr {
143    use sim_kernel::{Error, Expr, Result};
144
145    use super::has_required_field;
146
147    pub fn encode(expr: &Expr) -> Expr {
148        expr.clone()
149    }
150
151    pub fn decode(expr: &Expr) -> Result<Expr> {
152        let Expr::Map(entries) = expr else {
153            return Err(Error::Eval(
154                "topology node descriptor must be a map".to_owned(),
155            ));
156        };
157        has_required_field(entries, "id", "topology node descriptor")?;
158        has_required_field(entries, "verb", "topology node descriptor")?;
159        Ok(expr.clone())
160    }
161}
162
163pub(crate) mod edge_expr {
164    use sim_kernel::{Error, Expr, Result};
165
166    use super::has_required_field;
167
168    pub fn encode(expr: &Expr) -> Expr {
169        expr.clone()
170    }
171
172    pub fn decode(expr: &Expr) -> Result<Expr> {
173        let Expr::Map(entries) = expr else {
174            return Err(Error::Eval(
175                "topology edge descriptor must be a map".to_owned(),
176            ));
177        };
178        has_required_field(entries, "from", "topology edge descriptor")?;
179        has_required_field(entries, "to", "topology edge descriptor")?;
180        Ok(expr.clone())
181    }
182}
183
184fn has_required_field(entries: &[(Expr, Expr)], name: &str, context: &str) -> Result<()> {
185    if entries
186        .iter()
187        .any(|(key, _)| field_name(key).as_deref() == Some(name))
188    {
189        return Ok(());
190    }
191    Err(Error::Eval(format!("{context} is missing {name}")))
192}
193
194fn field_name(expr: &Expr) -> Option<String> {
195    match expr {
196        Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(symbol.name.to_string()),
197        Expr::String(value) => Some(value.clone()),
198        _ => None,
199    }
200}