Skip to main content

sim_lib_web_core/
selector.rs

1use crate::{
2    DecodeLimits, WebRecordError, WebRepresentation,
3    wire::{cid, field, node, opt_text, read_cid, read_opt_text, read_u32, sym, u32d},
4};
5use sim_kernel::{ContentId, Datum};
6
7/// A quote anchored to Unicode scalar offsets and optional context/path.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct EvidenceSelector {
10    pub representation_id: ContentId,
11    pub start: u32,
12    pub end: u32,
13    pub exact: String,
14    pub prefix: Option<String>,
15    pub suffix: Option<String>,
16    pub structural_path: Option<Vec<String>>,
17}
18impl EvidenceSelector {
19    pub fn checked(
20        representation_id: ContentId,
21        start: u32,
22        end: u32,
23        exact: String,
24        text: &str,
25    ) -> Result<Self, WebRecordError> {
26        if start > end || end as usize > text.chars().count() {
27            return Err(WebRecordError::InvalidSelector);
28        }
29        let observed: String = text
30            .chars()
31            .skip(start as usize)
32            .take((end - start) as usize)
33            .collect();
34        if observed != exact {
35            return Err(WebRecordError::InvalidSelector);
36        }
37        Ok(Self {
38            representation_id,
39            start,
40            end,
41            exact,
42            prefix: None,
43            suffix: None,
44            structural_path: None,
45        })
46    }
47    pub fn with_context(
48        mut self,
49        prefix: Option<String>,
50        suffix: Option<String>,
51        structural_path: Option<Vec<String>>,
52    ) -> Self {
53        self.prefix = prefix;
54        self.suffix = suffix;
55        self.structural_path = structural_path;
56        self
57    }
58    pub fn verify(&self, rep: &WebRepresentation) -> Result<(), WebRecordError> {
59        if self.representation_id != rep.content_id {
60            return Err(WebRecordError::InvalidSelector);
61        }
62        Self::checked(
63            self.representation_id.clone(),
64            self.start,
65            self.end,
66            self.exact.clone(),
67            &rep.text,
68        )
69        .map(|_| ())
70    }
71    pub fn to_datum(&self) -> Datum {
72        node(
73            "selector",
74            vec![
75                field("representation", cid(&self.representation_id)),
76                field("start", u32d(self.start)),
77                field("end", u32d(self.end)),
78                field("exact", Datum::String(self.exact.clone())),
79                field("prefix", opt_text(&self.prefix)),
80                field("suffix", opt_text(&self.suffix)),
81                field(
82                    "path",
83                    Datum::Vector(
84                        self.structural_path
85                            .clone()
86                            .unwrap_or_default()
87                            .into_iter()
88                            .map(Datum::String)
89                            .collect(),
90                    ),
91                ),
92            ],
93        )
94    }
95    pub fn from_datum(
96        value: &Datum,
97        rep: &WebRepresentation,
98        limits: DecodeLimits,
99    ) -> Result<Self, WebRecordError> {
100        let Datum::Node { tag, fields } = value else {
101            return Err(WebRecordError::InvalidRecord("selector"));
102        };
103        if tag != &sym("selector") || fields.len() != 7 {
104            return Err(WebRecordError::InvalidRecord("selector"));
105        }
106        let get = |i: usize, name: &str| {
107            if fields[i].0 == sym(name) {
108                Ok(&fields[i].1)
109            } else {
110                Err(WebRecordError::InvalidRecord("selector ordering"))
111            }
112        };
113        let representation_id = read_cid(get(0, "representation")?)?;
114        let start = read_u32(get(1, "start")?)?;
115        let end = read_u32(get(2, "end")?)?;
116        let Datum::String(exact) = get(3, "exact")? else {
117            return Err(WebRecordError::InvalidRecord("exact"));
118        };
119        if exact.len() > limits.max_text_bytes {
120            return Err(WebRecordError::BoundExceeded("exact"));
121        }
122        let mut selector = Self::checked(representation_id, start, end, exact.clone(), &rep.text)?;
123        selector.prefix = read_opt_text(get(4, "prefix")?, limits)?;
124        selector.suffix = read_opt_text(get(5, "suffix")?, limits)?;
125        let Datum::Vector(path) = get(6, "path")? else {
126            return Err(WebRecordError::InvalidRecord("path"));
127        };
128        if path.len() > limits.max_items {
129            return Err(WebRecordError::BoundExceeded("path"));
130        }
131        selector.structural_path = if path.is_empty() {
132            None
133        } else {
134            Some(
135                path.iter()
136                    .map(|v| match v {
137                        Datum::String(s) => Ok(s.clone()),
138                        _ => Err(WebRecordError::InvalidRecord("path")),
139                    })
140                    .collect::<Result<_, _>>()?,
141            )
142        };
143        selector.verify(rep)?;
144        Ok(selector)
145    }
146}