Skip to main content

sim_codec_bridge/
packet.rs

1use std::collections::BTreeSet;
2
3use sim_kernel::{ContentId, Error, Expr, Result, Symbol};
4use sim_value::build::entry as field;
5
6use crate::warrant::parse_content_id_string;
7use crate::{BridgeWarrant, content_id_string};
8
9/// One BRIDGE packet, carrying a header, ordered typed parts, and optional warrant.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct BridgePacket {
12    /// Fixed packet header.
13    pub header: BridgeHeader,
14    /// Ordered packet body parts.
15    pub body: Vec<BridgePart>,
16    /// Optional cross-trust warrant for book and spec content ids.
17    pub warrant: Option<BridgeWarrant>,
18}
19
20/// Fixed, fast-scan BRIDGE packet header.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct BridgeHeader {
23    /// Packet content id as rendered text, or `None` while canonicalizing.
24    pub cid: Option<String>,
25    /// Dialogue move intent.
26    pub move_kind: Symbol,
27    /// Sender seat.
28    pub from: String,
29    /// Receiver seats.
30    pub to: Vec<String>,
31    /// Sender role for this packet.
32    pub role: Symbol,
33    /// Parent packet content ids.
34    pub parents: Vec<String>,
35    /// Header task part id.
36    pub task: Symbol,
37    /// Header output/return part id.
38    pub output: Symbol,
39    /// Capability ceiling names, resolved by later bridge runtime code.
40    pub ceiling: Vec<Symbol>,
41    /// Context part ids available to this packet.
42    pub context: Vec<Symbol>,
43    /// Packet provenance.
44    pub provenance: BridgeProvenance,
45}
46
47/// Provenance carried in the BRIDGE header.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct BridgeProvenance {
50    /// Author seat or system symbol.
51    pub author: Symbol,
52    /// Optional card id or descriptor.
53    pub card: Option<String>,
54}
55
56/// One typed body part in a BRIDGE packet.
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct BridgePart {
59    /// Part id, referenced by header fields and other parts.
60    pub id: Symbol,
61    /// Registered part kind.
62    pub kind: Symbol,
63    /// Part payload expression.
64    pub payload: Expr,
65}
66
67impl BridgePacket {
68    /// Returns this packet with its header cid cleared for canonical hashing.
69    pub fn canonicalized(&self) -> Self {
70        let mut packet = self.clone();
71        packet.header.cid = None;
72        packet
73    }
74}
75
76impl Default for BridgeProvenance {
77    fn default() -> Self {
78        Self {
79            author: Symbol::new("sim"),
80            card: None,
81        }
82    }
83}
84
85/// Project a packet to the canonical expression record used by `codec:bridge`.
86pub fn packet_to_expr(packet: &BridgePacket) -> Expr {
87    Expr::Map(vec![
88        field("bridge", Expr::String("1".to_owned())),
89        field("header", header_to_expr(&packet.header)),
90        field(
91            "body",
92            Expr::Vector(packet.body.iter().map(part_to_expr).collect()),
93        ),
94        field(
95            "warrant",
96            packet
97                .warrant
98                .as_ref()
99                .map(warrant_to_expr)
100                .unwrap_or(Expr::Nil),
101        ),
102    ])
103}
104
105/// Validate a canonical packet expression back into a typed packet.
106pub fn expr_to_packet(expr: &Expr) -> Result<BridgePacket> {
107    let fields = map_fields(expr, "BRIDGE packet")?;
108    reject_unknown(fields, &["bridge", "header", "body", "warrant"])?;
109    match required_field(fields, "bridge")? {
110        Expr::String(version) if version == "1" => {}
111        _ => {
112            return Err(Error::Eval(
113                "BRIDGE packet must declare bridge version 1".to_owned(),
114            ));
115        }
116    }
117    Ok(BridgePacket {
118        header: header_from_expr(required_field(fields, "header")?)?,
119        body: vector_field(required_field(fields, "body")?, "body")?
120            .iter()
121            .map(part_from_expr)
122            .collect::<Result<Vec<_>>>()?,
123        warrant: match required_field(fields, "warrant")? {
124            Expr::Nil => None,
125            value => Some(warrant_from_expr(value)?),
126        },
127    })
128}
129
130fn header_to_expr(header: &BridgeHeader) -> Expr {
131    Expr::Map(vec![
132        field(
133            "cid",
134            header
135                .cid
136                .as_ref()
137                .map(|cid| Expr::String(cid.clone()))
138                .unwrap_or(Expr::Nil),
139        ),
140        field("move", Expr::Symbol(header.move_kind.clone())),
141        field("from", Expr::String(header.from.clone())),
142        field(
143            "to",
144            Expr::Vector(
145                header
146                    .to
147                    .iter()
148                    .map(|value| Expr::String(value.clone()))
149                    .collect(),
150            ),
151        ),
152        field("role", Expr::Symbol(header.role.clone())),
153        field(
154            "parents",
155            Expr::Vector(
156                header
157                    .parents
158                    .iter()
159                    .map(|value| Expr::String(value.clone()))
160                    .collect(),
161            ),
162        ),
163        field("task", Expr::Symbol(header.task.clone())),
164        field("output", Expr::Symbol(header.output.clone())),
165        field(
166            "ceiling",
167            Expr::Vector(
168                header
169                    .ceiling
170                    .iter()
171                    .map(|symbol| Expr::Symbol(symbol.clone()))
172                    .collect(),
173            ),
174        ),
175        field(
176            "context",
177            Expr::Vector(
178                header
179                    .context
180                    .iter()
181                    .map(|symbol| Expr::Symbol(symbol.clone()))
182                    .collect(),
183            ),
184        ),
185        field("provenance", provenance_to_expr(&header.provenance)),
186    ])
187}
188
189fn header_from_expr(expr: &Expr) -> Result<BridgeHeader> {
190    let fields = map_fields(expr, "BRIDGE header")?;
191    reject_unknown(
192        fields,
193        &[
194            "cid",
195            "move",
196            "from",
197            "to",
198            "role",
199            "parents",
200            "task",
201            "output",
202            "ceiling",
203            "context",
204            "provenance",
205        ],
206    )?;
207    Ok(BridgeHeader {
208        cid: optional_string_or_nil(fields, "cid")?,
209        move_kind: required_symbol(fields, "move")?,
210        from: required_string(fields, "from")?,
211        to: required_string_vector(fields, "to")?,
212        role: required_symbol(fields, "role")?,
213        parents: required_string_vector(fields, "parents")?,
214        task: required_symbol(fields, "task")?,
215        output: required_symbol(fields, "output")?,
216        ceiling: required_symbol_vector(fields, "ceiling")?,
217        context: required_symbol_vector(fields, "context")?,
218        provenance: provenance_from_expr(required_field(fields, "provenance")?)?,
219    })
220}
221
222fn part_to_expr(part: &BridgePart) -> Expr {
223    Expr::Map(vec![
224        field("id", Expr::Symbol(part.id.clone())),
225        field("kind", Expr::Symbol(part.kind.clone())),
226        field("payload", part.payload.clone()),
227    ])
228}
229
230fn part_from_expr(expr: &Expr) -> Result<BridgePart> {
231    let fields = map_fields(expr, "BRIDGE part")?;
232    reject_unknown(fields, &["id", "kind", "payload"])?;
233    Ok(BridgePart {
234        id: required_symbol(fields, "id")?,
235        kind: required_symbol(fields, "kind")?,
236        payload: required_field(fields, "payload")?.clone(),
237    })
238}
239
240fn provenance_to_expr(provenance: &BridgeProvenance) -> Expr {
241    Expr::Map(vec![
242        field("author", Expr::Symbol(provenance.author.clone())),
243        field(
244            "card",
245            provenance
246                .card
247                .as_ref()
248                .map(|card| Expr::String(card.clone()))
249                .unwrap_or(Expr::Nil),
250        ),
251    ])
252}
253
254fn provenance_from_expr(expr: &Expr) -> Result<BridgeProvenance> {
255    let fields = map_fields(expr, "BRIDGE provenance")?;
256    reject_unknown(fields, &["author", "card"])?;
257    Ok(BridgeProvenance {
258        author: required_symbol(fields, "author")?,
259        card: optional_string_or_nil(fields, "card")?,
260    })
261}
262
263fn warrant_to_expr(warrant: &BridgeWarrant) -> Expr {
264    Expr::Map(vec![
265        field("moves", content_id_expr(&warrant.moves)),
266        field("frames", content_id_expr(&warrant.frames)),
267        field(
268            "parts",
269            Expr::Vector(
270                warrant
271                    .parts
272                    .iter()
273                    .map(|(kind, id)| {
274                        Expr::Map(vec![
275                            field("kind", Expr::Symbol(kind.clone())),
276                            field("cid", content_id_expr(id)),
277                        ])
278                    })
279                    .collect(),
280            ),
281        ),
282    ])
283}
284
285fn warrant_from_expr(expr: &Expr) -> Result<BridgeWarrant> {
286    let fields = map_fields(expr, "BRIDGE warrant")?;
287    reject_unknown(fields, &["moves", "frames", "parts"])?;
288    Ok(BridgeWarrant {
289        moves: required_content_id(fields, "moves")?,
290        frames: required_content_id(fields, "frames")?,
291        parts: vector_field(required_field(fields, "parts")?, "parts")?
292            .iter()
293            .map(warrant_part_from_expr)
294            .collect::<Result<Vec<_>>>()?,
295    })
296}
297
298fn warrant_part_from_expr(expr: &Expr) -> Result<(Symbol, ContentId)> {
299    let fields = map_fields(expr, "BRIDGE warrant part")?;
300    reject_unknown(fields, &["kind", "cid"])?;
301    Ok((
302        required_symbol(fields, "kind")?,
303        required_content_id(fields, "cid")?,
304    ))
305}
306
307fn content_id_expr(id: &ContentId) -> Expr {
308    Expr::String(content_id_string(id))
309}
310
311fn required_content_id(fields: &[(Expr, Expr)], name: &str) -> Result<ContentId> {
312    match required_field(fields, name)? {
313        Expr::String(value) => parse_content_id_string(value),
314        _ => Err(Error::TypeMismatch {
315            expected: "content id string",
316            found: "non-string",
317        }),
318    }
319}
320
321fn required_symbol(fields: &[(Expr, Expr)], name: &str) -> Result<Symbol> {
322    match required_field(fields, name)? {
323        Expr::Symbol(symbol) => Ok(symbol.clone()),
324        _ => Err(Error::TypeMismatch {
325            expected: "symbol",
326            found: "non-symbol",
327        }),
328    }
329}
330
331fn required_string(fields: &[(Expr, Expr)], name: &str) -> Result<String> {
332    match required_field(fields, name)? {
333        Expr::String(value) => Ok(value.clone()),
334        _ => Err(Error::TypeMismatch {
335            expected: "string",
336            found: "non-string",
337        }),
338    }
339}
340
341fn optional_string_or_nil(fields: &[(Expr, Expr)], name: &str) -> Result<Option<String>> {
342    match required_field(fields, name)? {
343        Expr::Nil => Ok(None),
344        Expr::String(value) => Ok(Some(value.clone())),
345        _ => Err(Error::TypeMismatch {
346            expected: "string or nil",
347            found: "invalid optional string",
348        }),
349    }
350}
351
352fn required_string_vector(fields: &[(Expr, Expr)], name: &str) -> Result<Vec<String>> {
353    vector_field(required_field(fields, name)?, name)?
354        .iter()
355        .map(|value| match value {
356            Expr::String(value) => Ok(value.clone()),
357            _ => Err(Error::TypeMismatch {
358                expected: "string vector",
359                found: "non-string item",
360            }),
361        })
362        .collect()
363}
364
365fn required_symbol_vector(fields: &[(Expr, Expr)], name: &str) -> Result<Vec<Symbol>> {
366    vector_field(required_field(fields, name)?, name)?
367        .iter()
368        .map(|value| match value {
369            Expr::Symbol(symbol) => Ok(symbol.clone()),
370            _ => Err(Error::TypeMismatch {
371                expected: "symbol vector",
372                found: "non-symbol item",
373            }),
374        })
375        .collect()
376}
377
378fn vector_field<'a>(expr: &'a Expr, name: &str) -> Result<&'a [Expr]> {
379    match expr {
380        Expr::Vector(items) | Expr::List(items) => Ok(items),
381        _ => Err(Error::Eval(format!("BRIDGE {name} field must be a vector"))),
382    }
383}
384
385fn required_field<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
386    optional_field(fields, name)
387        .ok_or_else(|| Error::Eval(format!("BRIDGE record is missing {name}")))
388}
389
390fn optional_field<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
391    fields
392        .iter()
393        .find_map(|(key, value)| (field_name(key).ok()?.as_str() == name).then_some(value))
394}
395
396fn reject_unknown(fields: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
397    let mut seen = BTreeSet::new();
398    for (key, _) in fields {
399        let name = field_name(key)?;
400        if !seen.insert(name.clone()) {
401            return Err(Error::Eval(format!("duplicate BRIDGE field {name}")));
402        }
403        if !allowed.contains(&name.as_str()) {
404            return Err(Error::Eval(format!("unknown BRIDGE field {name}")));
405        }
406    }
407    Ok(())
408}
409
410fn field_name(expr: &Expr) -> Result<String> {
411    match expr {
412        Expr::Symbol(symbol) if symbol.namespace.is_none() => Ok(symbol.name.to_string()),
413        Expr::String(value) => Ok(value.clone()),
414        _ => Err(Error::TypeMismatch {
415            expected: "BRIDGE field symbol",
416            found: "invalid field key",
417        }),
418    }
419}
420
421use sim_value::access::map_entries as map_fields;