1use std::collections::BTreeMap;
2
3use serde_json::Value as JsonValue;
4use sim_codec::{DecodeBudget, DecodeLimits};
5use sim_kernel::{CodecId, Error, Expr, Result, Symbol};
6
7use crate::identity::content_id_string;
8use crate::warrant::parse_content_id_string;
9use crate::{BridgeBook, BridgeHeader, BridgePacket, BridgePart, BridgeProvenance, BridgeWarrant};
10
11pub fn encode_bridge_text(packet: &BridgePacket, book: &BridgeBook) -> Result<String> {
13 for part in &packet.body {
14 book.parts.require_registered(&part.kind)?;
15 match &part.kind {
16 kind if *kind == Symbol::qualified("bridge", "Frame") => {
17 book.frames.validate_payload(&part.payload)?;
18 }
19 kind if *kind == Symbol::qualified("bridge", "Call") => {
20 crate::validate_call_payload(&part.payload)?;
21 }
22 kind if *kind == Symbol::qualified("bridge", "Weave") => {
23 crate::validate_weave_payload(&part.payload)?;
24 }
25 kind if collab_part(kind) => {
26 crate::validate_collab_payload(kind, &part.payload)?;
27 }
28 _ => {}
29 }
30 }
31 let mut lines = vec![
32 "BRIDGE/1".to_owned(),
33 format!("CID {}", packet.header.cid.as_deref().unwrap_or("nil")),
34 format!("MOVE {}", symbol_text(&packet.header.move_kind)),
35 format!("FROM {}", checked_token("FROM", &packet.header.from)?),
36 format!("TO {}", string_list(&packet.header.to)?),
37 format!("ROLE {}", symbol_text(&packet.header.role)),
38 format!("PARENTS {}", string_list(&packet.header.parents)?),
39 format!("TASK {}", symbol_text(&packet.header.task)),
40 format!("OUTPUT {}", symbol_text(&packet.header.output)),
41 format!("CEIL {}", symbol_list(&packet.header.ceiling)),
42 format!("CONTEXT {}", symbol_list(&packet.header.context)),
43 format!(
44 "PROV author={} card={}",
45 symbol_text(&packet.header.provenance.author),
46 packet.header.provenance.card.as_deref().unwrap_or("nil")
47 ),
48 ];
49 if let Some(warrant) = &packet.warrant {
50 lines.push(format!("WARRANT {}", warrant_text(warrant)));
51 }
52 lines.push("BODY".to_owned());
53 for part in &packet.body {
54 lines.push(format!(
55 "{} {} payload={}",
56 part_keyword(&part.kind),
57 symbol_text(&part.id),
58 payload_text(&part.payload)?
59 ));
60 }
61 lines.push("END".to_owned());
62 Ok(format!("{}\n", lines.join("\n")))
63}
64
65pub fn decode_bridge_text(text: &str, book: &BridgeBook) -> Result<BridgePacket> {
67 let mut lines = text.lines();
68 match lines.next() {
69 Some("BRIDGE/1") => {}
70 _ => {
71 return Err(Error::Eval(
72 "BRIDGE packet must start with BRIDGE/1".to_owned(),
73 ));
74 }
75 }
76
77 let mut headers = BTreeMap::new();
78 let mut body_lines = Vec::new();
79 let mut in_body = false;
80 let mut ended = false;
81 for line in lines {
82 if ended {
83 if !line.trim().is_empty() {
84 return Err(Error::Eval("BRIDGE packet has text after END".to_owned()));
85 }
86 continue;
87 }
88 if line == "BODY" {
89 if in_body {
90 return Err(Error::Eval("duplicate BRIDGE BODY marker".to_owned()));
91 }
92 in_body = true;
93 continue;
94 }
95 if line == "END" {
96 if !in_body {
97 return Err(Error::Eval("BRIDGE END before BODY".to_owned()));
98 }
99 ended = true;
100 continue;
101 }
102 if in_body {
103 body_lines.push(line.to_owned());
104 } else {
105 let (key, value) = split_header(line)?;
106 if !is_known_header(key) {
107 return Err(Error::Eval(format!("unknown BRIDGE header {key}")));
108 }
109 if headers.insert(key.to_owned(), value.to_owned()).is_some() {
110 return Err(Error::Eval(format!("duplicate BRIDGE header {key}")));
111 }
112 }
113 }
114 if !ended {
115 return Err(Error::Eval("BRIDGE packet is missing END".to_owned()));
116 }
117 let packet = BridgePacket {
118 header: BridgeHeader {
119 cid: header(&headers, "CID").and_then(parse_cid)?,
120 move_kind: parse_symbol(header(&headers, "MOVE")?),
121 from: header(&headers, "FROM")?.to_owned(),
122 to: parse_string_list(header(&headers, "TO")?)?,
123 role: parse_symbol(header(&headers, "ROLE")?),
124 parents: parse_string_list(header(&headers, "PARENTS")?)?,
125 task: parse_symbol(header(&headers, "TASK")?),
126 output: parse_symbol(header(&headers, "OUTPUT")?),
127 ceiling: parse_symbol_list(header(&headers, "CEIL")?)?,
128 context: parse_symbol_list(header(&headers, "CONTEXT")?)?,
129 provenance: parse_provenance(header(&headers, "PROV")?)?,
130 },
131 body: body_lines
132 .iter()
133 .map(|line| parse_part(line, book))
134 .collect::<Result<Vec<_>>>()?,
135 warrant: match headers.get("WARRANT") {
136 Some(value) => Some(parse_warrant(value)?),
137 None => None,
138 },
139 };
140 Ok(packet)
141}
142
143fn split_header(line: &str) -> Result<(&str, &str)> {
144 line.split_once(' ')
145 .ok_or_else(|| Error::Eval(format!("malformed BRIDGE header line {line:?}")))
146}
147
148fn is_known_header(header: &str) -> bool {
149 matches!(
150 header,
151 "CID"
152 | "MOVE"
153 | "FROM"
154 | "TO"
155 | "ROLE"
156 | "PARENTS"
157 | "TASK"
158 | "OUTPUT"
159 | "CEIL"
160 | "CONTEXT"
161 | "PROV"
162 | "WARRANT"
163 )
164}
165
166fn header<'a>(headers: &'a BTreeMap<String, String>, name: &str) -> Result<&'a str> {
167 headers
168 .get(name)
169 .map(String::as_str)
170 .ok_or_else(|| Error::Eval(format!("BRIDGE packet is missing {name} header")))
171}
172
173fn parse_cid(value: &str) -> Result<Option<String>> {
174 if value == "nil" {
175 Ok(None)
176 } else {
177 Ok(Some(value.to_owned()))
178 }
179}
180
181fn parse_provenance(value: &str) -> Result<BridgeProvenance> {
182 let mut author = None;
183 let mut card = None;
184 for item in value.split(' ') {
185 let Some((key, value)) = item.split_once('=') else {
186 return Err(Error::Eval(format!("malformed BRIDGE provenance {item}")));
187 };
188 match key {
189 "author" => author = Some(parse_symbol(value)),
190 "card" => {
191 card = Some(if value == "nil" {
192 None
193 } else {
194 Some(value.to_owned())
195 })
196 }
197 _ => {
198 return Err(Error::Eval(format!(
199 "unknown BRIDGE provenance field {key}"
200 )));
201 }
202 }
203 }
204 Ok(BridgeProvenance {
205 author: author.ok_or_else(|| Error::Eval("BRIDGE provenance missing author".to_owned()))?,
206 card: card.ok_or_else(|| Error::Eval("BRIDGE provenance missing card".to_owned()))?,
207 })
208}
209
210fn warrant_text(warrant: &BridgeWarrant) -> String {
211 format!(
212 "moves={} frames={} parts={}",
213 content_id_string(&warrant.moves),
214 content_id_string(&warrant.frames),
215 warrant_parts_text(&warrant.parts)
216 )
217}
218
219fn warrant_parts_text(parts: &[(Symbol, sim_kernel::ContentId)]) -> String {
220 format!(
221 "[{}]",
222 parts
223 .iter()
224 .map(|(kind, id)| format!("{}={}", symbol_text(kind), content_id_string(id)))
225 .collect::<Vec<_>>()
226 .join(",")
227 )
228}
229
230fn parse_warrant(value: &str) -> Result<BridgeWarrant> {
231 let mut moves = None;
232 let mut frames = None;
233 let mut parts = None;
234 for item in value.split(' ') {
235 let Some((key, value)) = item.split_once('=') else {
236 return Err(Error::Eval(format!("malformed BRIDGE warrant {item}")));
237 };
238 match key {
239 "moves" => moves = Some(parse_content_id_string(value)?),
240 "frames" => frames = Some(parse_content_id_string(value)?),
241 "parts" => parts = Some(parse_warrant_parts(value)?),
242 _ => return Err(Error::Eval(format!("unknown BRIDGE warrant field {key}"))),
243 }
244 }
245 Ok(BridgeWarrant {
246 moves: moves.ok_or_else(|| Error::Eval("BRIDGE warrant missing moves".to_owned()))?,
247 frames: frames.ok_or_else(|| Error::Eval("BRIDGE warrant missing frames".to_owned()))?,
248 parts: parts.ok_or_else(|| Error::Eval("BRIDGE warrant missing parts".to_owned()))?,
249 })
250}
251
252fn parse_warrant_parts(text: &str) -> Result<Vec<(Symbol, sim_kernel::ContentId)>> {
253 parse_list(text)?
254 .into_iter()
255 .map(|item| {
256 let (kind, cid) = item
257 .split_once('=')
258 .ok_or_else(|| Error::Eval(format!("malformed BRIDGE warrant part {item}")))?;
259 Ok((parse_symbol(kind), parse_content_id_string(cid)?))
260 })
261 .collect()
262}
263
264fn parse_part(line: &str, book: &BridgeBook) -> Result<BridgePart> {
265 let mut fields = line.splitn(3, ' ');
266 let keyword = fields
267 .next()
268 .ok_or_else(|| Error::Eval("empty BRIDGE part line".to_owned()))?;
269 let id = fields
270 .next()
271 .ok_or_else(|| Error::Eval(format!("BRIDGE part {keyword} missing id")))?;
272 let rest = fields
273 .next()
274 .ok_or_else(|| Error::Eval(format!("BRIDGE part {keyword} missing payload")))?;
275 let payload = rest
276 .strip_prefix("payload=")
277 .ok_or_else(|| Error::Eval(format!("BRIDGE part {keyword} has unknown field")))?;
278 let kind = kind_from_keyword(keyword);
279 book.parts.require_registered(&kind)?;
280 let payload = parse_payload(payload)?;
281 match &kind {
282 kind if *kind == Symbol::qualified("bridge", "Frame") => {
283 book.frames.validate_payload(&payload)?;
284 }
285 kind if *kind == Symbol::qualified("bridge", "Call") => {
286 crate::validate_call_payload(&payload)?;
287 }
288 kind if *kind == Symbol::qualified("bridge", "Weave") => {
289 crate::validate_weave_payload(&payload)?;
290 }
291 kind if collab_part(kind) => {
292 crate::validate_collab_payload(kind, &payload)?;
293 }
294 _ => {}
295 }
296 Ok(BridgePart {
297 id: parse_symbol(id),
298 kind,
299 payload,
300 })
301}
302
303fn payload_text(expr: &Expr) -> Result<String> {
304 serde_json::to_string(&sim_codec_json::expr_to_json(expr))
305 .map_err(|err| Error::Eval(format!("encode BRIDGE payload JSON: {err}")))
306}
307
308fn parse_payload(text: &str) -> Result<Expr> {
309 let value = serde_json::from_str::<JsonValue>(text)
310 .map_err(|err| Error::Eval(format!("parse BRIDGE payload JSON: {err}")))?;
311 let mut budget = DecodeBudget::new(DecodeLimits::default());
312 sim_codec_json::json_to_expr(CodecId(0), &value, &mut budget, 0)
313}
314
315fn symbol_text(symbol: &Symbol) -> String {
316 symbol.as_qualified_str()
317}
318
319fn parse_symbol(text: &str) -> Symbol {
320 match text.split_once('/') {
321 Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
322 Symbol::qualified(namespace.to_owned(), name.to_owned())
323 }
324 _ => Symbol::new(text.to_owned()),
325 }
326}
327
328fn string_list(items: &[String]) -> Result<String> {
329 let tokens = items
330 .iter()
331 .map(|item| checked_token("list item", item))
332 .collect::<Result<Vec<_>>>()?;
333 Ok(format!("[{}]", tokens.join(",")))
334}
335
336fn symbol_list(items: &[Symbol]) -> String {
337 format!(
338 "[{}]",
339 items.iter().map(symbol_text).collect::<Vec<_>>().join(",")
340 )
341}
342
343fn parse_string_list(text: &str) -> Result<Vec<String>> {
344 parse_list(text).map(|items| items.into_iter().map(str::to_owned).collect())
345}
346
347fn parse_symbol_list(text: &str) -> Result<Vec<Symbol>> {
348 parse_list(text).map(|items| items.into_iter().map(parse_symbol).collect())
349}
350
351fn parse_list(text: &str) -> Result<Vec<&str>> {
352 let inner = text
353 .strip_prefix('[')
354 .and_then(|text| text.strip_suffix(']'))
355 .ok_or_else(|| Error::Eval(format!("BRIDGE list must use brackets: {text}")))?;
356 if inner.is_empty() {
357 Ok(Vec::new())
358 } else {
359 Ok(inner.split(',').collect())
360 }
361}
362
363fn checked_token<'a>(label: &str, value: &'a str) -> Result<&'a str> {
364 if value.is_empty()
365 || value
366 .chars()
367 .any(|ch| ch.is_whitespace() || matches!(ch, '[' | ']' | ','))
368 {
369 Err(Error::Eval(format!(
370 "BRIDGE {label} must be a non-empty token"
371 )))
372 } else {
373 Ok(value)
374 }
375}
376
377fn part_keyword(kind: &Symbol) -> String {
378 kind.name.to_ascii_uppercase()
379}
380
381fn kind_from_keyword(keyword: &str) -> Symbol {
382 let mut chars = keyword.chars();
383 let name = match chars.next() {
384 Some(first) => format!(
385 "{}{}",
386 first.to_ascii_uppercase(),
387 chars.as_str().to_ascii_lowercase()
388 ),
389 None => String::new(),
390 };
391 Symbol::qualified("bridge", name)
392}
393
394fn collab_part(kind: &Symbol) -> bool {
395 matches!(
396 kind.name.as_ref(),
397 "Review" | "Vote" | "Patch" | "Evidence" | "Receipt" | "Attest"
398 )
399}