1use std::collections::BTreeSet;
2
3use sim_kernel::{ContentId, Datum, Error, Expr, NumberLiteral, Result, Symbol};
4
5use crate::{
6 AuthorityClass, BridgeBook, BridgeFrameBook, BridgeMoveBook, BridgePacket, BridgePartSpec,
7 FrameHoleKind, FrameKind, FrameSpec, RenderClass, ReplyRule, UnknownPolicy,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum BridgeWarrantPolicy {
13 SharedTrust,
15 Verify,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct BridgeWarrant {
22 pub moves: ContentId,
24 pub frames: ContentId,
26 pub parts: Vec<(Symbol, ContentId)>,
28}
29
30pub fn warrant_for_packet(book: &BridgeBook, packet: &BridgePacket) -> Result<BridgeWarrant> {
32 let mut used_parts = BTreeSet::new();
33 for part in &packet.body {
34 used_parts.insert(part.kind.clone());
35 }
36 let mut parts = Vec::new();
37 for kind in used_parts {
38 let spec = book
39 .parts
40 .spec(&kind)
41 .ok_or_else(|| Error::Eval(format!("unknown BRIDGE part kind {kind}")))?;
42 parts.push((kind, part_spec_content_id(spec)?));
43 }
44 Ok(BridgeWarrant {
45 moves: move_book_content_id(&book.moves)?,
46 frames: frame_book_content_id(&book.frames)?,
47 parts,
48 })
49}
50
51pub fn move_book_content_id(book: &BridgeMoveBook) -> Result<ContentId> {
53 Datum::Node {
54 tag: Symbol::qualified("bridge", "MoveBook"),
55 fields: vec![(
56 Symbol::new("moves"),
57 Datum::Vector(book.specs().map(move_spec_datum).collect()),
58 )],
59 }
60 .content_id()
61}
62
63pub fn frame_book_content_id(book: &BridgeFrameBook) -> Result<ContentId> {
65 Datum::Node {
66 tag: Symbol::qualified("bridge", "FrameBook"),
67 fields: vec![(
68 Symbol::new("frames"),
69 Datum::Vector(book.specs().map(frame_spec_datum).collect()),
70 )],
71 }
72 .content_id()
73}
74
75pub fn part_spec_content_id(spec: &BridgePartSpec) -> Result<ContentId> {
77 part_spec_datum(spec).content_id()
78}
79
80pub(crate) fn content_id_datum(id: &ContentId) -> Datum {
81 Datum::Node {
82 tag: Symbol::qualified("core", "ContentId"),
83 fields: vec![
84 (
85 Symbol::new("algorithm"),
86 Datum::Symbol(id.algorithm.clone()),
87 ),
88 (Symbol::new("bytes"), Datum::Bytes(id.bytes.to_vec())),
89 ],
90 }
91}
92
93pub(crate) fn parse_content_id_string(text: &str) -> Result<ContentId> {
94 let (algorithm, hex) = text
95 .split_once(':')
96 .ok_or_else(|| Error::Eval(format!("content id must contain algorithm:hex: {text}")))?;
97 if hex.len() != 64 {
98 return Err(Error::Eval(format!(
99 "content id digest must be 64 hex digits: {text}"
100 )));
101 }
102 let mut bytes = [0u8; 32];
103 for (index, pair) in hex.as_bytes().chunks_exact(2).enumerate() {
104 let pair = std::str::from_utf8(pair)
105 .map_err(|err| Error::Eval(format!("content id digest is not UTF-8: {err}")))?;
106 bytes[index] = u8::from_str_radix(pair, 16)
107 .map_err(|err| Error::Eval(format!("content id digest is not hex: {err}")))?;
108 }
109 Ok(ContentId::from_bytes(parse_symbol(algorithm), bytes))
110}
111
112fn move_spec_datum(spec: &crate::BridgeMoveSpec) -> Datum {
113 Datum::Node {
114 tag: Symbol::qualified("bridge", "MoveSpec"),
115 fields: vec![
116 (Symbol::new("intent"), Datum::Symbol(spec.intent.clone())),
117 (
118 Symbol::new("replies-to"),
119 reply_rule_datum(&spec.replies_to),
120 ),
121 (
122 Symbol::new("requires-parts"),
123 Datum::Vector(
124 spec.requires_parts
125 .iter()
126 .cloned()
127 .map(Datum::Symbol)
128 .collect(),
129 ),
130 ),
131 (
132 Symbol::new("requires-any-parts"),
133 Datum::Vector(
134 spec.requires_any_parts
135 .iter()
136 .map(|group| {
137 Datum::Vector(group.iter().cloned().map(Datum::Symbol).collect())
138 })
139 .collect(),
140 ),
141 ),
142 (Symbol::new("terminal"), Datum::Bool(spec.terminal)),
143 ],
144 }
145}
146
147fn reply_rule_datum(rule: &ReplyRule) -> Datum {
148 match rule {
149 ReplyRule::Opens => rule_node("Opens", Vec::new()),
150 ReplyRule::Any => rule_node("Any", Vec::new()),
151 ReplyRule::AnyNonReceipt => rule_node("AnyNonReceipt", Vec::new()),
152 ReplyRule::Only(intents) => rule_node(
153 "Only",
154 vec![(
155 Symbol::new("intents"),
156 Datum::Vector(intents.iter().cloned().map(Datum::Symbol).collect()),
157 )],
158 ),
159 }
160}
161
162fn rule_node(name: &str, fields: Vec<(Symbol, Datum)>) -> Datum {
163 let mut fields = fields;
164 fields.insert(
165 0,
166 (
167 Symbol::new("rule"),
168 Datum::Symbol(Symbol::qualified("bridge", name)),
169 ),
170 );
171 Datum::Node {
172 tag: Symbol::qualified("bridge", "ReplyRule"),
173 fields,
174 }
175}
176
177fn frame_spec_datum(spec: &FrameSpec) -> Datum {
178 Datum::Node {
179 tag: Symbol::qualified("bridge", "FrameSpec"),
180 fields: vec![
181 (Symbol::new("id"), Datum::Symbol(spec.id.clone())),
182 (
183 Symbol::new("kind"),
184 Datum::Symbol(frame_kind_symbol(spec.kind)),
185 ),
186 (Symbol::new("prefix"), Datum::String(spec.prefix.to_owned())),
187 (
188 Symbol::new("template"),
189 Datum::String(spec.template.to_owned()),
190 ),
191 (
192 Symbol::new("holes"),
193 Datum::Vector(
194 spec.holes
195 .iter()
196 .map(|hole| Datum::Node {
197 tag: Symbol::qualified("bridge", "FrameHoleSpec"),
198 fields: vec![
199 (Symbol::new("name"), Datum::Symbol(hole.name.clone())),
200 (
201 Symbol::new("kind"),
202 Datum::Symbol(frame_hole_kind_symbol(hole.kind)),
203 ),
204 ],
205 })
206 .collect(),
207 ),
208 ),
209 (
210 Symbol::new("grammar-priority"),
211 Datum::Number(NumberLiteral {
212 domain: Symbol::qualified("numbers", "u8"),
213 canonical: spec.grammar_priority.to_string(),
214 }),
215 ),
216 ],
217 }
218}
219
220fn part_spec_datum(spec: &BridgePartSpec) -> Datum {
221 Datum::Node {
222 tag: Symbol::qualified("bridge", "PartSpec"),
223 fields: vec![
224 (Symbol::new("kind"), Datum::Symbol(spec.kind.clone())),
225 (Symbol::new("shape"), expr_datum(&spec.shape_expr)),
226 (
227 Symbol::new("render-class"),
228 Datum::Symbol(render_class_symbol(&spec.render_class)),
229 ),
230 (
231 Symbol::new("authority-class"),
232 Datum::Symbol(authority_class_symbol(&spec.authority_class)),
233 ),
234 (
235 Symbol::new("unknown-policy"),
236 Datum::Symbol(unknown_policy_symbol(&spec.unknown_policy)),
237 ),
238 ],
239 }
240}
241
242fn expr_datum(expr: &Expr) -> Datum {
243 Datum::Node {
244 tag: Symbol::qualified("bridge", "ExprJson"),
245 fields: vec![(
246 Symbol::new("json"),
247 Datum::String(
248 serde_json::to_string(&sim_codec_json::expr_to_json(expr))
249 .expect("JSON value encodes"),
250 ),
251 )],
252 }
253}
254
255fn frame_kind_symbol(kind: FrameKind) -> Symbol {
256 let name = match kind {
257 FrameKind::Use => "Use",
258 FrameKind::Inform => "Inform",
259 FrameKind::Task => "Task",
260 FrameKind::Require => "Require",
261 FrameKind::Forbid => "Forbid",
262 FrameKind::Prefer => "Prefer",
263 FrameKind::Return => "Return",
264 FrameKind::Check => "Check",
265 };
266 Symbol::qualified("bridge", name)
267}
268
269fn frame_hole_kind_symbol(kind: FrameHoleKind) -> Symbol {
270 let name = match kind {
271 FrameHoleKind::Ref => "Ref",
272 FrameHoleKind::Term => "Term",
273 FrameHoleKind::Choice => "Choice",
274 FrameHoleKind::Path => "Path",
275 FrameHoleKind::Number => "Number",
276 FrameHoleKind::Prose => "Prose",
277 };
278 Symbol::qualified("bridge", name)
279}
280
281fn render_class_symbol(class: &RenderClass) -> Symbol {
282 let name = match class {
283 RenderClass::Structural => "Structural",
284 RenderClass::Frame => "Frame",
285 RenderClass::Data => "Data",
286 RenderClass::Evidence => "Evidence",
287 RenderClass::Review => "Review",
288 RenderClass::Vote => "Vote",
289 RenderClass::Patch => "Patch",
290 RenderClass::Fetch => "Fetch",
291 RenderClass::Return => "Return",
292 RenderClass::Receipt => "Receipt",
293 RenderClass::Extension => "Extension",
294 };
295 Symbol::qualified("bridge", name)
296}
297
298fn authority_class_symbol(class: &AuthorityClass) -> Symbol {
299 let name = match class {
300 AuthorityClass::Data => "Data",
301 AuthorityClass::Normative => "Normative",
302 AuthorityClass::Callable => "Callable",
303 AuthorityClass::Evidence => "Evidence",
304 };
305 Symbol::qualified("bridge", name)
306}
307
308fn unknown_policy_symbol(policy: &UnknownPolicy) -> Symbol {
309 let name = match policy {
310 UnknownPolicy::Reject => "Reject",
311 UnknownPolicy::PreserveDataOnly => "PreserveDataOnly",
312 };
313 Symbol::qualified("bridge", name)
314}
315
316fn parse_symbol(text: &str) -> Symbol {
317 match text.split_once('/') {
318 Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
319 Symbol::qualified(namespace.to_owned(), name.to_owned())
320 }
321 _ => Symbol::new(text.to_owned()),
322 }
323}