1use std::fmt;
4
5pub const MAX_SELECTOR_BYTES: usize = 1024;
7pub const MAX_SELECTOR_SEGMENTS: usize = 64;
9const MAX_ARRAY_INDEX: u64 = 1_000_000;
10const MAX_ERROR_SELECTOR_PREVIEW_BYTES: usize = 160;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct SelectorError {
15 selector_preview: String,
16 selector_bytes: usize,
17 offset: usize,
18 reason: &'static str,
19}
20
21impl SelectorError {
22 fn new(selector: &str, offset: usize, reason: &'static str) -> Self {
23 let preview_end = if selector.len() <= MAX_ERROR_SELECTOR_PREVIEW_BYTES {
24 selector.len()
25 } else {
26 let mut boundary = MAX_ERROR_SELECTOR_PREVIEW_BYTES;
27 while !selector.is_char_boundary(boundary) {
28 boundary -= 1;
29 }
30 boundary
31 };
32 Self {
33 selector_preview: selector[..preview_end].to_string(),
34 selector_bytes: selector.len(),
35 offset,
36 reason,
37 }
38 }
39}
40
41impl fmt::Display for SelectorError {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 write!(
44 formatter,
45 "invalid response selector {:?}",
46 self.selector_preview
47 )?;
48 if self.selector_preview.len() < self.selector_bytes {
49 write!(formatter, " ({} bytes)", self.selector_bytes)?;
50 }
51 write!(
52 formatter,
53 " at byte {}: {}. Fix: use a `$`-rooted selector such as `$.data.account.email` or `$.orgs[0].name`",
54 self.offset, self.reason
55 )
56 }
57}
58
59impl std::error::Error for SelectorError {}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62enum Segment<'a> {
63 Key(&'a str),
64 Index(usize),
65}
66
67fn visit_segments<'a>(
68 selector: &'a str,
69 mut visit: impl FnMut(Segment<'a>),
70) -> Result<(), SelectorError> {
71 let bytes = selector.as_bytes();
72 if bytes.len() > MAX_SELECTOR_BYTES {
73 return Err(SelectorError::new(
74 selector,
75 MAX_SELECTOR_BYTES,
76 "the selector exceeds the 1024-byte limit",
77 ));
78 }
79 if bytes.first() != Some(&b'$') {
80 return Err(SelectorError::new(
81 selector,
82 0,
83 "the selector must start with `$`",
84 ));
85 }
86 let mut cursor = 1;
87 let mut segment_count = 0usize;
88
89 let mut visit_bounded = |segment| {
90 segment_count += 1;
91 if segment_count > MAX_SELECTOR_SEGMENTS {
92 return false;
93 }
94 visit(segment);
95 true
96 };
97
98 while cursor < bytes.len() {
99 if bytes[cursor] == b'.' {
100 cursor += 1;
101 let key_start = cursor;
102 while cursor < bytes.len() && !matches!(bytes[cursor], b'.' | b'[' | b']') {
103 if !bytes[cursor].is_ascii_alphanumeric() && !matches!(bytes[cursor], b'_' | b'-') {
104 return Err(SelectorError::new(
105 selector,
106 cursor,
107 "bare object keys may contain only ASCII letters, digits, `_`, and `-`",
108 ));
109 }
110 cursor += 1;
111 }
112 if cursor == key_start {
113 return Err(SelectorError::new(
114 selector,
115 cursor,
116 "an object key is missing after `.`",
117 ));
118 }
119 if !visit_bounded(Segment::Key(&selector[key_start..cursor])) {
120 return Err(SelectorError::new(
121 selector,
122 cursor,
123 "the selector exceeds the 64-segment limit",
124 ));
125 }
126 continue;
127 }
128 if bytes[cursor] != b'[' {
129 return Err(SelectorError::new(
130 selector,
131 cursor,
132 "expected `.` or `[` after the current segment",
133 ));
134 }
135 let bracket_start = cursor;
136 cursor += 1;
137 if cursor < bytes.len() && bytes[cursor] == b'"' {
138 cursor += 1;
139 let key_start = cursor;
140 while cursor < bytes.len() && bytes[cursor] != b'"' {
141 if bytes[cursor] == b'\\' || bytes[cursor].is_ascii_control() {
142 return Err(SelectorError::new(
143 selector,
144 cursor,
145 "quoted object keys cannot contain escapes or control characters",
146 ));
147 }
148 cursor += 1;
149 }
150 if cursor == key_start {
151 return Err(SelectorError::new(
152 selector,
153 cursor,
154 "a quoted object key cannot be empty",
155 ));
156 }
157 if cursor == bytes.len() || bytes[cursor] != b'"' {
158 return Err(SelectorError::new(
159 selector,
160 bracket_start,
161 "a quoted object key is missing its closing quote",
162 ));
163 }
164 if !visit_bounded(Segment::Key(&selector[key_start..cursor])) {
165 return Err(SelectorError::new(
166 selector,
167 cursor,
168 "the selector exceeds the 64-segment limit",
169 ));
170 }
171 cursor += 1;
172 if cursor == bytes.len() || bytes[cursor] != b']' {
173 return Err(SelectorError::new(
174 selector,
175 bracket_start,
176 "a quoted object key is missing its closing `]`",
177 ));
178 }
179 cursor += 1;
180 continue;
181 }
182 let index_start = cursor;
183 while cursor < bytes.len() && bytes[cursor].is_ascii_digit() {
184 cursor += 1;
185 }
186 if cursor == index_start {
187 return Err(SelectorError::new(
188 selector,
189 cursor,
190 "an array index must contain decimal digits",
191 ));
192 }
193 if cursor == bytes.len() || bytes[cursor] != b']' {
194 return Err(SelectorError::new(
195 selector,
196 bracket_start,
197 "an array index is missing its closing `]`",
198 ));
199 }
200 if cursor - index_start > 1 && bytes[index_start] == b'0' {
201 return Err(SelectorError::new(
202 selector,
203 index_start,
204 "array indexes cannot contain leading zeroes",
205 ));
206 }
207 let index = selector[index_start..cursor].parse::<u64>().map_err(|_| {
208 SelectorError::new(selector, index_start, "the array index is too large")
209 })?;
210 if index > MAX_ARRAY_INDEX {
211 return Err(SelectorError::new(
212 selector,
213 index_start,
214 "the array index exceeds the 1000000 limit",
215 ));
216 }
217 if !visit_bounded(Segment::Index(index as usize)) {
218 return Err(SelectorError::new(
219 selector,
220 cursor,
221 "the selector exceeds the 64-segment limit",
222 ));
223 }
224 cursor += 1;
225 }
226 Ok(())
227}
228
229pub fn validate(selector: &str) -> Result<(), SelectorError> {
231 visit_segments(selector, |_| {})
232}
233
234fn validate_at(errors: &mut Vec<String>, scope: std::fmt::Arguments<'_>, selector: Option<&str>) {
235 if let Some(selector) = selector {
236 if let Err(error) = validate(selector) {
237 errors.push(format!("{scope}: {error}"));
238 }
239 }
240}
241
242pub fn validate_detector_response_selectors(detector: &crate::DetectorSpec) -> Vec<String> {
244 let Some(verify) = &detector.verify else {
245 return Vec::new();
246 };
247 let mut errors = Vec::new();
248 if let Some(success) = &verify.success {
249 validate_at(
250 &mut errors,
251 format_args!("verify.success.json_path"),
252 success.json_path.as_deref(),
253 );
254 if success.equals.is_some() && success.json_path.is_none() {
255 errors.push(
256 "verify.success.equals requires verify.success.json_path so the expected value has an explicit response selector"
257 .to_string(),
258 );
259 }
260 }
261 for (metadata_index, metadata) in verify.metadata.iter().enumerate() {
262 validate_at(
263 &mut errors,
264 format_args!("verify.metadata[{metadata_index}].json_path"),
265 Some(&metadata.json_path),
266 );
267 }
268 for (step_index, step) in verify.steps.iter().enumerate() {
269 validate_at(
270 &mut errors,
271 format_args!("verify.steps[{step_index}].success.json_path"),
272 step.success.json_path.as_deref(),
273 );
274 if step.success.equals.is_some() && step.success.json_path.is_none() {
275 errors.push(format!(
276 "verify.steps[{step_index}].success.equals requires verify.steps[{step_index}].success.json_path so the expected value has an explicit response selector"
277 ));
278 }
279 for (extract_index, metadata) in step.extract.iter().enumerate() {
280 validate_at(
281 &mut errors,
282 format_args!("verify.steps[{step_index}].extract[{extract_index}].json_path"),
283 Some(&metadata.json_path),
284 );
285 }
286 }
287 errors
288}
289
290pub fn select<'a>(
296 value: &'a serde_json::Value,
297 selector: &str,
298) -> Result<Option<&'a serde_json::Value>, SelectorError> {
299 let mut selected = Some(value);
300 visit_segments(selector, |segment| {
301 selected = match (selected, segment) {
302 (Some(serde_json::Value::Object(object)), Segment::Key(key)) => object.get(key),
303 (Some(serde_json::Value::Array(array)), Segment::Index(index)) => array.get(index),
304 _ => None,
305 };
306 })?;
307 Ok(selected)
308}