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#[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}