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
8pub 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}