Skip to main content

sim_lib_forge/
packet_artifact.rs

1use sim_codec::{DecodeBudget, DecodeLimits};
2use sim_codec_bridge::{
3    BridgeHeader, BridgePacket, BridgePart, BridgeProvenance, BridgeWarrant,
4    canonical_packet_datum, content_id_string, packet_content_id, stamp_packet_cid,
5};
6use sim_kernel::{CodecId, ContentId, Cx, Datum, DatumStore, Error, Expr, Result, Symbol};
7
8/// Stores a BRIDGE packet artifact in the context datum store and returns its
9/// canonical packet content id.
10pub fn store_packet_artifact(cx: &mut Cx, packet: &BridgePacket) -> Result<ContentId> {
11    let id = packet_content_id(packet)?;
12    let stored = cx
13        .datum_store_mut()
14        .intern(canonical_packet_datum(packet))?;
15    if stored == id {
16        Ok(id)
17    } else {
18        Err(Error::Eval(format!(
19            "stored packet id {} did not match canonical id {}",
20            content_id_string(&stored),
21            content_id_string(&id)
22        )))
23    }
24}
25
26pub(crate) fn resolve_packet(cx: &mut Cx, id: &ContentId) -> Result<BridgePacket> {
27    let Some(datum) = cx.datum_store().get(id)?.cloned() else {
28        return Err(Error::Eval(format!(
29            "compiled packet artifact {} is not interned",
30            content_id_string(id)
31        )));
32    };
33    let packet = packet_from_canonical_datum(&datum)?;
34    let actual = packet_content_id(&packet)?;
35    if &actual == id {
36        stamp_packet_cid(&packet)
37    } else {
38        Err(Error::Eval(format!(
39            "compiled packet artifact {} decoded as {}",
40            content_id_string(id),
41            content_id_string(&actual)
42        )))
43    }
44}
45
46fn packet_from_canonical_datum(datum: &Datum) -> Result<BridgePacket> {
47    let fields = node_fields(datum, "bridge", "Packet", "BRIDGE packet datum")?;
48    Ok(BridgePacket {
49        header: header_from_datum(required_field(fields, "header", "BRIDGE packet datum")?)?,
50        body: vector_field(
51            required_field(fields, "body", "BRIDGE packet datum")?,
52            "body",
53        )?
54        .iter()
55        .map(part_from_datum)
56        .collect::<Result<Vec<_>>>()?,
57        warrant: warrant_from_datum(required_field(fields, "warrant", "BRIDGE packet datum")?)?,
58    })
59}
60
61fn header_from_datum(datum: &Datum) -> Result<BridgeHeader> {
62    let fields = node_fields(datum, "bridge", "Header", "BRIDGE header datum")?;
63    Ok(BridgeHeader {
64        cid: string_or_nil(required_field(fields, "cid", "BRIDGE header datum")?, "cid")?,
65        move_kind: symbol_field(fields, "move", "BRIDGE header datum")?,
66        from: string_field(fields, "from", "BRIDGE header datum")?,
67        to: string_vec_field(fields, "to", "BRIDGE header datum")?,
68        role: symbol_field(fields, "role", "BRIDGE header datum")?,
69        parents: string_vec_field(fields, "parents", "BRIDGE header datum")?,
70        task: symbol_field(fields, "task", "BRIDGE header datum")?,
71        output: symbol_field(fields, "output", "BRIDGE header datum")?,
72        ceiling: symbol_vec_field(fields, "ceiling", "BRIDGE header datum")?,
73        context: symbol_vec_field(fields, "context", "BRIDGE header datum")?,
74        provenance: provenance_from_datum(required_field(
75            fields,
76            "provenance",
77            "BRIDGE header datum",
78        )?)?,
79    })
80}
81
82fn provenance_from_datum(datum: &Datum) -> Result<BridgeProvenance> {
83    let fields = node_fields(datum, "bridge", "Provenance", "BRIDGE provenance datum")?;
84    Ok(BridgeProvenance {
85        author: symbol_field(fields, "author", "BRIDGE provenance datum")?,
86        card: string_or_nil(
87            required_field(fields, "card", "BRIDGE provenance datum")?,
88            "card",
89        )?,
90    })
91}
92
93fn part_from_datum(datum: &Datum) -> Result<BridgePart> {
94    let fields = node_fields(datum, "bridge", "Part", "BRIDGE part datum")?;
95    Ok(BridgePart {
96        id: symbol_field(fields, "id", "BRIDGE part datum")?,
97        kind: symbol_field(fields, "kind", "BRIDGE part datum")?,
98        payload: expr_json_field(fields, "payload", "BRIDGE part datum")?,
99    })
100}
101
102fn warrant_from_datum(datum: &Datum) -> Result<Option<BridgeWarrant>> {
103    if matches!(datum, Datum::Nil) {
104        return Ok(None);
105    }
106    let fields = node_fields(datum, "bridge", "Warrant", "BRIDGE warrant datum")?;
107    Ok(Some(BridgeWarrant {
108        moves: content_id_field(fields, "moves", "BRIDGE warrant datum")?,
109        frames: content_id_field(fields, "frames", "BRIDGE warrant datum")?,
110        parts: vector_field(
111            required_field(fields, "parts", "BRIDGE warrant datum")?,
112            "parts",
113        )?
114        .iter()
115        .map(warrant_part_from_datum)
116        .collect::<Result<Vec<_>>>()?,
117    }))
118}
119
120fn warrant_part_from_datum(datum: &Datum) -> Result<(Symbol, ContentId)> {
121    let fields = node_fields(datum, "bridge", "WarrantPart", "BRIDGE warrant part datum")?;
122    Ok((
123        symbol_field(fields, "kind", "BRIDGE warrant part datum")?,
124        content_id_field(fields, "cid", "BRIDGE warrant part datum")?,
125    ))
126}
127
128fn content_id_from_datum(datum: &Datum, context: &str) -> Result<ContentId> {
129    let fields = node_fields(datum, "core", "ContentId", context)?;
130    let algorithm = symbol_field(fields, "algorithm", context)?;
131    let bytes = match required_field(fields, "bytes", context)? {
132        Datum::Bytes(bytes) if bytes.len() == 32 => {
133            let mut digest = [0u8; 32];
134            digest.copy_from_slice(bytes);
135            digest
136        }
137        Datum::Bytes(bytes) => {
138            return Err(Error::Eval(format!(
139                "{context} bytes must contain 32 digest bytes, found {}",
140                bytes.len()
141            )));
142        }
143        _ => return Err(Error::Eval(format!("{context} bytes must be bytes"))),
144    };
145    Ok(ContentId::from_bytes(algorithm, bytes))
146}
147
148fn node_fields<'a>(
149    datum: &'a Datum,
150    namespace: &str,
151    name: &str,
152    context: &str,
153) -> Result<&'a [(Symbol, Datum)]> {
154    match datum {
155        Datum::Node { tag, fields }
156            if tag.namespace.as_deref() == Some(namespace) && tag.name.as_ref() == name =>
157        {
158            Ok(fields)
159        }
160        _ => Err(Error::Eval(format!(
161            "{context} must be {namespace}/{name} node"
162        ))),
163    }
164}
165
166fn required_field<'a>(
167    fields: &'a [(Symbol, Datum)],
168    name: &str,
169    context: &str,
170) -> Result<&'a Datum> {
171    fields
172        .iter()
173        .find_map(|(field, value)| {
174            (field.namespace.is_none() && field.name.as_ref() == name).then_some(value)
175        })
176        .ok_or_else(|| Error::Eval(format!("{context} is missing {name}")))
177}
178
179fn symbol_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<Symbol> {
180    match required_field(fields, name, context)? {
181        Datum::Symbol(symbol) => Ok(symbol.clone()),
182        _ => Err(Error::Eval(format!(
183            "{context} field {name} must be a symbol"
184        ))),
185    }
186}
187
188fn string_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<String> {
189    match required_field(fields, name, context)? {
190        Datum::String(value) => Ok(value.clone()),
191        _ => Err(Error::Eval(format!(
192            "{context} field {name} must be a string"
193        ))),
194    }
195}
196
197fn string_or_nil(datum: &Datum, name: &str) -> Result<Option<String>> {
198    match datum {
199        Datum::Nil => Ok(None),
200        Datum::String(value) => Ok(Some(value.clone())),
201        _ => Err(Error::Eval(format!("field {name} must be a string or nil"))),
202    }
203}
204
205fn string_vec_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<Vec<String>> {
206    vector_field(required_field(fields, name, context)?, name)?
207        .iter()
208        .map(|datum| match datum {
209            Datum::String(value) => Ok(value.clone()),
210            _ => Err(Error::Eval(format!(
211                "{context} field {name} entries must be strings"
212            ))),
213        })
214        .collect()
215}
216
217fn symbol_vec_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<Vec<Symbol>> {
218    vector_field(required_field(fields, name, context)?, name)?
219        .iter()
220        .map(|datum| match datum {
221            Datum::Symbol(value) => Ok(value.clone()),
222            _ => Err(Error::Eval(format!(
223                "{context} field {name} entries must be symbols"
224            ))),
225        })
226        .collect()
227}
228
229fn content_id_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<ContentId> {
230    content_id_from_datum(required_field(fields, name, context)?, context)
231}
232
233fn expr_json_field(fields: &[(Symbol, Datum)], name: &str, context: &str) -> Result<Expr> {
234    let datum = required_field(fields, name, context)?;
235    let expr_fields = node_fields(datum, "bridge", "ExprJson", context)?;
236    let Datum::String(json) = required_field(expr_fields, "json", context)? else {
237        return Err(Error::Eval(format!(
238            "{context} expression payload json must be a string"
239        )));
240    };
241    let value = serde_json::from_str(json)
242        .map_err(|err| Error::Eval(format!("{context} expression json is invalid: {err}")))?;
243    let mut budget = DecodeBudget::new(DecodeLimits::default());
244    sim_codec_json::json_to_expr(CodecId(1), &value, &mut budget, 0)
245}
246
247fn vector_field<'a>(datum: &'a Datum, name: &str) -> Result<&'a [Datum]> {
248    match datum {
249        Datum::Vector(items) => Ok(items),
250        _ => Err(Error::Eval(format!("{name} must be a vector"))),
251    }
252}