1use crate::{DecodeLimits, RepresentationMetadata, WebRecordError};
2use sim_kernel::{ContentId, Datum, NumberLiteral, Symbol};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct RecordDescriptor {
7 pub symbol: &'static str,
8 pub version: u32,
9}
10pub const RECORD_DESCRIPTORS: &[RecordDescriptor] = &[
11 RecordDescriptor {
12 symbol: "web/Capture",
13 version: 1,
14 },
15 RecordDescriptor {
16 symbol: "web/Exchange",
17 version: 1,
18 },
19 RecordDescriptor {
20 symbol: "web/Representation",
21 version: 1,
22 },
23 RecordDescriptor {
24 symbol: "web/EvidenceSelector",
25 version: 1,
26 },
27 RecordDescriptor {
28 symbol: "web/PolicyReceipt",
29 version: 1,
30 },
31];
32
33pub(crate) fn sym(s: &str) -> Symbol {
34 Symbol::qualified("web", s)
35}
36pub(crate) fn field(n: &str, v: Datum) -> (Symbol, Datum) {
37 (sym(n), v)
38}
39pub(crate) fn node(n: &str, f: Vec<(Symbol, Datum)>) -> Datum {
40 Datum::Node {
41 tag: sym(n),
42 fields: f,
43 }
44}
45pub(crate) fn u32d(v: u32) -> Datum {
46 Datum::Number(NumberLiteral {
47 domain: Symbol::qualified("numbers", "u32"),
48 canonical: v.to_string(),
49 })
50}
51pub(crate) fn cid(v: &ContentId) -> Datum {
52 node(
53 "content-id",
54 vec![
55 field("algorithm", Datum::Symbol(v.algorithm.clone())),
56 field("digest", Datum::Bytes(v.bytes.to_vec())),
57 ],
58 )
59}
60pub(crate) fn read_cid(v: &Datum) -> Result<ContentId, WebRecordError> {
61 let Datum::Node { tag, fields } = v else {
62 return Err(WebRecordError::InvalidRecord("content id"));
63 };
64 if tag != &sym("content-id") || fields.len() != 2 {
65 return Err(WebRecordError::InvalidRecord("content id"));
66 }
67 let (Datum::Symbol(a), Datum::Bytes(b)) = (&fields[0].1, &fields[1].1) else {
68 return Err(WebRecordError::InvalidRecord("content id"));
69 };
70 let bytes: [u8; 32] = b
71 .as_slice()
72 .try_into()
73 .map_err(|_| WebRecordError::InvalidRecord("digest"))?;
74 Ok(ContentId::from_bytes(a.clone(), bytes))
75}
76pub(crate) fn read_u32(v: &Datum) -> Result<u32, WebRecordError> {
77 match v {
78 Datum::Number(n) if n.domain == Symbol::qualified("numbers", "u32") => n
79 .canonical
80 .parse()
81 .map_err(|_| WebRecordError::InvalidRecord("u32")),
82 _ => Err(WebRecordError::InvalidRecord("u32")),
83 }
84}
85pub(crate) fn opt_text(v: &Option<String>) -> Datum {
86 v.clone().map_or(Datum::Nil, Datum::String)
87}
88pub(crate) fn read_opt_text(v: &Datum, l: DecodeLimits) -> Result<Option<String>, WebRecordError> {
89 match v {
90 Datum::Nil => Ok(None),
91 Datum::String(s) if s.len() <= l.max_text_bytes => Ok(Some(s.clone())),
92 Datum::String(_) => Err(WebRecordError::BoundExceeded("context")),
93 _ => Err(WebRecordError::InvalidRecord("context")),
94 }
95}
96pub(crate) fn representation_identity(
97 raw: &ContentId,
98 text: &str,
99 metadata: &RepresentationMetadata,
100) -> Datum {
101 node(
102 "representation",
103 vec![
104 field("raw", cid(raw)),
105 field("text", Datum::String(text.into())),
106 field("codec", Datum::String(metadata.codec.clone())),
107 field(
108 "codec-version",
109 Datum::String(metadata.codec_version.clone()),
110 ),
111 field("media-type", Datum::String(metadata.media_type.clone())),
112 field(
113 "charset",
114 metadata.charset.clone().map_or(Datum::Nil, Datum::String),
115 ),
116 field(
117 "language",
118 metadata.language.clone().map_or(Datum::Nil, Datum::String),
119 ),
120 field(
121 "warnings",
122 Datum::Vector(
123 metadata
124 .fidelity_warnings
125 .iter()
126 .cloned()
127 .map(Datum::String)
128 .collect(),
129 ),
130 ),
131 ],
132 )
133}