use sim_citizen_derive::Citizen;
use sim_kernel::{Error, Expr, Result, Symbol};
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "topology/Package", version = 1)]
pub struct TopologyPackageDescriptor {
#[citizen(with = "package_source")]
source: String,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "topology/Node", version = 1)]
pub struct TopologyNodeDescriptor {
#[citizen(with = "node_expr")]
node: Expr,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "topology/Edge", version = 1)]
pub struct TopologyEdgeDescriptor {
#[citizen(with = "edge_expr")]
edge: Expr,
}
impl TopologyPackageDescriptor {
pub fn new(source: impl Into<String>) -> Result<Self> {
let source = source.into();
package_source::decode(&Expr::String(source.clone()))?;
Ok(Self { source })
}
pub fn source(&self) -> &str {
&self.source
}
}
impl Default for TopologyPackageDescriptor {
fn default() -> Self {
Self::new(
r#"
graph:
topology citizen-package
node in verb=in
node out verb=out
wire in -> out
"#,
)
.expect("default topology package descriptor should be valid")
}
}
impl TopologyNodeDescriptor {
pub fn from_expr(node: Expr) -> Result<Self> {
node_expr::decode(&node)?;
Ok(Self { node })
}
pub fn as_expr(&self) -> &Expr {
&self.node
}
}
impl Default for TopologyNodeDescriptor {
fn default() -> Self {
Self::from_expr(Expr::Map(vec![
field("id", Expr::Symbol(Symbol::new("citizen-node"))),
field("verb", Expr::Symbol(Symbol::new("wire"))),
]))
.expect("default topology node descriptor should be valid")
}
}
impl TopologyEdgeDescriptor {
pub fn from_expr(edge: Expr) -> Result<Self> {
edge_expr::decode(&edge)?;
Ok(Self { edge })
}
pub fn as_expr(&self) -> &Expr {
&self.edge
}
}
impl Default for TopologyEdgeDescriptor {
fn default() -> Self {
Self::from_expr(Expr::Map(vec![
field("from", Expr::Symbol(Symbol::new("citizen-node/out"))),
field("to", Expr::Symbol(Symbol::new("citizen-out/in"))),
]))
.expect("default topology edge descriptor should be valid")
}
}
pub fn topology_package_class_symbol() -> Symbol {
Symbol::qualified("topology", "Package")
}
pub fn topology_node_class_symbol() -> Symbol {
Symbol::qualified("topology", "Node")
}
pub fn topology_edge_class_symbol() -> Symbol {
Symbol::qualified("topology", "Edge")
}
pub(crate) mod package_source {
use sim_kernel::{Error, Expr, Result};
use crate::parse_package;
pub fn encode(source: &str) -> sim_kernel::Expr {
sim_kernel::Expr::String(source.to_owned())
}
pub fn decode(expr: &Expr) -> Result<String> {
let Expr::String(source) = expr else {
return Err(Error::Eval(
"topology package descriptor source must be a string".to_owned(),
));
};
parse_package(source)?;
Ok(source.clone())
}
}
pub(crate) mod node_expr {
use sim_kernel::{Error, Expr, Result};
use super::has_required_field;
pub fn encode(expr: &Expr) -> Expr {
expr.clone()
}
pub fn decode(expr: &Expr) -> Result<Expr> {
let Expr::Map(entries) = expr else {
return Err(Error::Eval(
"topology node descriptor must be a map".to_owned(),
));
};
has_required_field(entries, "id", "topology node descriptor")?;
has_required_field(entries, "verb", "topology node descriptor")?;
Ok(expr.clone())
}
}
pub(crate) mod edge_expr {
use sim_kernel::{Error, Expr, Result};
use super::has_required_field;
pub fn encode(expr: &Expr) -> Expr {
expr.clone()
}
pub fn decode(expr: &Expr) -> Result<Expr> {
let Expr::Map(entries) = expr else {
return Err(Error::Eval(
"topology edge descriptor must be a map".to_owned(),
));
};
has_required_field(entries, "from", "topology edge descriptor")?;
has_required_field(entries, "to", "topology edge descriptor")?;
Ok(expr.clone())
}
}
fn has_required_field(entries: &[(Expr, Expr)], name: &str, context: &str) -> Result<()> {
if entries
.iter()
.any(|(key, _)| field_name(key).as_deref() == Some(name))
{
return Ok(());
}
Err(Error::Eval(format!("{context} is missing {name}")))
}
fn field_name(expr: &Expr) -> Option<String> {
match expr {
Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(symbol.name.to_string()),
Expr::String(value) => Some(value.clone()),
_ => None,
}
}
fn field(name: &str, value: Expr) -> (Expr, Expr) {
(Expr::Symbol(Symbol::new(name.to_owned())), value)
}