Skip to main content

sim_lib_web_core/
wire.rs

1use crate::{DecodeLimits, RepresentationMetadata, WebRecordError};
2use sim_kernel::{ContentId, Datum, NumberLiteral, Symbol};
3
4/// Stable runtime descriptor shared by Shape/Citizen installers.
5#[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}