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#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct BridgePacket {
12 pub header: BridgeHeader,
14 pub body: Vec<BridgePart>,
16 pub warrant: Option<BridgeWarrant>,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct BridgeHeader {
23 pub cid: Option<String>,
25 pub move_kind: Symbol,
27 pub from: String,
29 pub to: Vec<String>,
31 pub role: Symbol,
33 pub parents: Vec<String>,
35 pub task: Symbol,
37 pub output: Symbol,
39 pub ceiling: Vec<Symbol>,
41 pub context: Vec<Symbol>,
43 pub provenance: BridgeProvenance,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct BridgeProvenance {
50 pub author: Symbol,
52 pub card: Option<String>,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct BridgePart {
59 pub id: Symbol,
61 pub kind: Symbol,
63 pub payload: Expr,
65}
66
67impl BridgePacket {
68 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
85pub 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
105pub 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;