Skip to main content

sim_table_core/
citizen_fields.rs

1//! Field encoders used by table/list citizen descriptors.
2//!
3//! These helpers keep descriptor read constructors on the same validators as
4//! live table backends: path fields go through [`TablePath`], and operation
5//! fields go through [`decode_table_op`].
6
7use sim_kernel::{Error, Expr, Result, Symbol};
8
9use crate::{TablePath, decode_table_op};
10
11/// Encode/decode `Vec<(Symbol, Expr)>` as an expression map.
12pub mod entries {
13    use super::*;
14
15    /// Encodes the symbol/expr entries as an [`Expr::Map`].
16    pub fn encode(entries: &[(Symbol, Expr)]) -> Expr {
17        Expr::Map(
18            entries
19                .iter()
20                .map(|(key, value)| (Expr::Symbol(key.clone()), value.clone()))
21                .collect(),
22        )
23    }
24
25    /// Decodes an [`Expr::Map`] into symbol/expr entries, erroring on a non-map
26    /// expression or a non-symbol key.
27    pub fn decode(expr: &Expr) -> Result<Vec<(Symbol, Expr)>> {
28        let Expr::Map(entries) = expr else {
29            return Err(field_error("entries", "expected map"));
30        };
31        entries
32            .iter()
33            .map(|(key, value)| {
34                let Expr::Symbol(key) = key else {
35                    return Err(field_error("entries", "expected symbol key"));
36                };
37                Ok((key.clone(), value.clone()))
38            })
39            .collect()
40    }
41}
42
43/// Encode/decode `Vec<Expr>` as an expression list.
44pub mod expr_list {
45    use super::*;
46
47    /// Encodes the items as an [`Expr::List`].
48    pub fn encode(items: &[Expr]) -> Expr {
49        Expr::List(items.to_vec())
50    }
51
52    /// Decodes an [`Expr::List`] into its items, erroring on a non-list
53    /// expression.
54    pub fn decode(expr: &Expr) -> Result<Vec<Expr>> {
55        let Expr::List(items) = expr else {
56            return Err(field_error("items", "expected list"));
57        };
58        Ok(items.clone())
59    }
60}
61
62/// Encode/decode `Symbol` as an expression symbol.
63pub mod symbol {
64    use super::*;
65
66    /// Encodes the symbol as an [`Expr::Symbol`].
67    pub fn encode(symbol: &Symbol) -> Expr {
68        Expr::Symbol(symbol.clone())
69    }
70
71    /// Decodes an [`Expr::Symbol`], erroring on any other expression.
72    pub fn decode(expr: &Expr) -> Result<Symbol> {
73        let Expr::Symbol(symbol) = expr else {
74            return Err(field_error("symbol", "expected symbol"));
75        };
76        Ok(symbol.clone())
77    }
78}
79
80/// Encode/decode validated path segments.
81pub mod path_segments {
82    use super::*;
83
84    /// Encodes the path segments as an [`Expr::List`] of strings.
85    pub fn encode(segments: &[String]) -> Expr {
86        Expr::List(
87            segments
88                .iter()
89                .map(|segment| Expr::String(segment.clone()))
90                .collect(),
91        )
92    }
93
94    /// Decodes a list of string segments, validating each through [`TablePath`]
95    /// and erroring on a non-list, a non-string segment, or an illegal segment.
96    pub fn decode(expr: &Expr) -> Result<Vec<String>> {
97        let Expr::List(items) = expr else {
98            return Err(field_error("path", "expected list"));
99        };
100        let mut path = TablePath::new();
101        for item in items {
102            let Expr::String(segment) = item else {
103                return Err(field_error("path", "expected string segment"));
104            };
105            path.push(segment)
106                .map_err(|_| field_error("path", format!("illegal segment {segment:?}")))?;
107        }
108        Ok(path.segments().to_vec())
109    }
110}
111
112/// Encode/decode a table operation expression validated by `decode_table_op`.
113pub mod table_op_expr {
114    use super::*;
115
116    /// Returns the table-operation expression unchanged for encoding.
117    pub fn encode(op: &Expr) -> Expr {
118        op.clone()
119    }
120
121    /// Validates the expression as a table operation via [`decode_table_op`] and
122    /// returns it unchanged, erroring when it is not a valid operation.
123    pub fn decode(expr: &Expr) -> Result<Expr> {
124        decode_table_op(expr)
125            .map_err(|err| field_error("operation", format!("invalid table operation: {err:?}")))?;
126        Ok(expr.clone())
127    }
128}
129
130fn field_error(field: &str, message: impl Into<String>) -> Error {
131    Error::Eval(format!("table citizen field {field}: {}", message.into()))
132}