1#[derive(Debug, Clone)]
32pub struct ParsedPath<'a> {
33 pub raw: &'a str,
35 pub segments: Vec<PathSegment<'a>>,
37}
38
39#[derive(Debug, Clone, PartialEq)]
43pub struct List {
44 pub elements_nullable: bool,
47}
48
49#[derive(Debug, Clone)]
59pub struct PathSegment<'a> {
60 pub field: &'a str,
62 pub alias: Option<&'a str>,
64 pub is_nullable: bool,
67 pub list: Option<List>,
69}
70
71impl<'a> PathSegment<'a> {
72 pub fn json_key(&self) -> &str {
74 self.alias.unwrap_or(self.field)
75 }
76
77 pub fn is_list(&self) -> bool {
79 self.list.is_some()
80 }
81}
82
83impl<'a> ParsedPath<'a> {
84 pub fn parse(path: &'a str) -> Result<Self, PathParseError<'a>> {
103 if path.is_empty() {
104 return Err(PathParseError::Empty);
105 }
106
107 let raw_segments: Vec<&str> = path.split('.').collect();
108
109 let mut segments = Vec::with_capacity(raw_segments.len());
110 for segment in raw_segments {
111 if segment.is_empty() {
112 return Err(PathParseError::EmptySegment { path });
113 }
114
115 let suffix_start = segment.find(['?', '[']).unwrap_or(segment.len());
117 let (field_part, suffix) = segment.split_at(suffix_start);
118
119 let (is_nullable, list) = match suffix {
120 "" => (false, None),
121 "?" => (true, None),
122 "[]" => (
123 false,
124 Some(List {
125 elements_nullable: false,
126 }),
127 ),
128 "?[]" => (
129 true,
130 Some(List {
131 elements_nullable: false,
132 }),
133 ),
134 "[]?" => (
135 false,
136 Some(List {
137 elements_nullable: true,
138 }),
139 ),
140 "?[]?" => (
141 true,
142 Some(List {
143 elements_nullable: true,
144 }),
145 ),
146 _ => return Err(PathParseError::InvalidSuffix { path, suffix }),
147 };
148
149 let (field, alias) = if let Some(colon_pos) = field_part.find(':') {
151 let alias = &field_part[..colon_pos];
152 let field = &field_part[colon_pos + 1..];
153 (field, Some(alias))
154 } else {
155 (field_part, None)
156 };
157
158 if field.is_empty() {
159 return Err(PathParseError::EmptySegment { path });
160 }
161
162 segments.push(PathSegment {
163 field,
164 alias,
165 is_nullable,
166 list,
167 });
168 }
169
170 Ok(ParsedPath {
171 raw: path,
172 segments,
173 })
174 }
175}
176
177#[derive(Debug, Clone)]
179pub enum PathParseError<'a> {
180 Empty,
182 EmptySegment { path: &'a str },
184 InvalidSuffix { path: &'a str, suffix: &'a str },
186}
187
188impl<'a> std::fmt::Display for PathParseError<'a> {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 PathParseError::Empty => write!(f, "Field path cannot be empty"),
192 PathParseError::EmptySegment { path } => {
193 write!(f, "Empty segment in path '{}'", path)
194 }
195 PathParseError::InvalidSuffix { path, suffix } => {
196 write!(
197 f,
198 "Invalid suffix '{}' in path '{}'. Valid suffixes: ?, [], ?[], []?, ?[]?",
199 suffix, path
200 )
201 }
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_parse_simple_path() {
212 let path = ParsedPath::parse("object.address").unwrap();
213 assert_eq!(path.segments.len(), 2);
214 assert_eq!(path.segments[0].field, "object");
215 assert!(path.segments[0].alias.is_none());
216 assert!(!path.segments[0].is_nullable);
217 assert!(!path.segments[0].is_list());
218 assert_eq!(path.segments[1].field, "address");
219 assert!(!path.segments[1].is_list());
220 }
221
222 #[test]
223 fn test_parse_nested_path() {
224 let path = ParsedPath::parse("data.nodes.name").unwrap();
225 assert_eq!(path.segments.len(), 3);
226 assert_eq!(path.segments[0].field, "data");
227 assert!(!path.segments[0].is_list());
228 assert_eq!(path.segments[1].field, "nodes");
229 assert!(!path.segments[1].is_list());
230 assert_eq!(path.segments[2].field, "name");
231 assert!(!path.segments[2].is_list());
232 }
233
234 #[test]
235 fn test_parse_single_field() {
236 let path = ParsedPath::parse("chainIdentifier").unwrap();
237 assert_eq!(path.segments.len(), 1);
238 assert_eq!(path.segments[0].field, "chainIdentifier");
239 assert!(!path.segments[0].is_list());
240 }
241
242 #[test]
243 fn test_parse_with_alias() {
244 let path = ParsedPath::parse("epoch.firstCheckpoint:checkpoints.nodes[]").unwrap();
245 assert_eq!(path.segments.len(), 3);
246 assert_eq!(path.segments[0].field, "epoch");
247 assert!(path.segments[0].alias.is_none());
248 assert!(!path.segments[0].is_list());
249 assert_eq!(path.segments[1].field, "checkpoints");
250 assert_eq!(path.segments[1].alias, Some("firstCheckpoint"));
251 assert!(!path.segments[1].is_list());
252 assert_eq!(path.segments[2].field, "nodes");
253 assert!(path.segments[2].is_list());
254 }
255
256 #[test]
257 fn test_parse_array_with_alias() {
258 let path = ParsedPath::parse("myObjects:objects[]").unwrap();
259 assert_eq!(path.segments.len(), 1);
260 assert_eq!(path.segments[0].field, "objects");
261 assert_eq!(path.segments[0].alias, Some("myObjects"));
262 assert!(path.segments[0].is_list());
263 }
264
265 #[test]
266 fn test_json_key() {
267 let path = ParsedPath::parse("alias:field.normal").unwrap();
268 assert_eq!(path.segments[0].json_key(), "alias");
269 assert_eq!(path.segments[1].json_key(), "normal");
270 }
271
272 #[test]
273 fn test_parse_empty_error() {
274 let err = ParsedPath::parse("").unwrap_err();
275 assert!(matches!(err, PathParseError::Empty));
276 }
277
278 #[test]
279 fn test_parse_empty_segment_error() {
280 let err = ParsedPath::parse("foo..bar").unwrap_err();
281 assert!(matches!(err, PathParseError::EmptySegment { .. }));
282
283 let err = ParsedPath::parse(".foo").unwrap_err();
284 assert!(matches!(err, PathParseError::EmptySegment { .. }));
285 }
286
287 #[test]
288 fn test_parse_array_syntax() {
289 let path = ParsedPath::parse("items[].name").unwrap();
290 assert_eq!(path.segments.len(), 2);
291 assert_eq!(path.segments[0].field, "items");
292 assert!(path.segments[0].is_list());
293 assert_eq!(path.segments[1].field, "name");
294 assert!(!path.segments[1].is_list());
295 }
296
297 #[test]
298 fn test_parse_nested_arrays() {
299 let path = ParsedPath::parse("groups[].members[].name").unwrap();
300 assert_eq!(path.segments.len(), 3);
301 assert_eq!(path.segments[0].field, "groups");
302 assert!(path.segments[0].is_list());
303 assert_eq!(path.segments[1].field, "members");
304 assert!(path.segments[1].is_list());
305 assert_eq!(path.segments[2].field, "name");
306 assert!(!path.segments[2].is_list());
307 }
308
309 #[test]
310 fn test_parse_trailing_array() {
311 let path = ParsedPath::parse("items[].tags[]").unwrap();
312 assert_eq!(path.segments.len(), 2);
313 assert_eq!(path.segments[0].field, "items");
314 assert!(path.segments[0].is_list());
315 assert_eq!(path.segments[1].field, "tags");
316 assert!(path.segments[1].is_list());
317 }
318
319 #[test]
320 fn test_parse_empty_array_field_error() {
321 let err = ParsedPath::parse("[].name").unwrap_err();
322 assert!(matches!(err, PathParseError::EmptySegment { .. }));
323 }
324
325 #[test]
328 fn test_parse_nullable_field() {
329 let path = ParsedPath::parse("object?.address?").unwrap();
330 assert_eq!(path.segments.len(), 2);
331 assert!(path.segments[0].is_nullable);
332 assert!(!path.segments[0].is_list());
333 assert!(path.segments[1].is_nullable);
334 assert!(!path.segments[1].is_list());
335 }
336
337 #[test]
338 fn test_parse_nullable_array() {
339 let path = ParsedPath::parse("items?[].name").unwrap();
341 assert!(path.segments[0].is_nullable);
342 assert!(path.segments[0].is_list());
343 assert!(!path.segments[0].list.as_ref().unwrap().elements_nullable);
344 }
345
346 #[test]
347 fn test_parse_elements_nullable() {
348 let path = ParsedPath::parse("items[]?.name").unwrap();
350 assert!(!path.segments[0].is_nullable);
351 assert!(path.segments[0].is_list());
352 assert!(path.segments[0].list.as_ref().unwrap().elements_nullable);
353 }
354
355 #[test]
356 fn test_parse_both_nullable() {
357 let path = ParsedPath::parse("items?[]?.name").unwrap();
359 assert!(path.segments[0].is_nullable);
360 assert!(path.segments[0].is_list());
361 assert!(path.segments[0].list.as_ref().unwrap().elements_nullable);
362 }
363
364 #[test]
365 fn test_parse_nullable_with_alias() {
366 let path = ParsedPath::parse("myAddr:address?").unwrap();
367 assert_eq!(path.segments[0].field, "address");
368 assert_eq!(path.segments[0].alias, Some("myAddr"));
369 assert!(path.segments[0].is_nullable);
370 }
371
372 #[test]
373 fn test_parse_nullable_array_with_alias() {
374 let path = ParsedPath::parse("myItems:items?[]?.name").unwrap();
375 assert_eq!(path.segments[0].field, "items");
376 assert_eq!(path.segments[0].alias, Some("myItems"));
377 assert!(path.segments[0].is_nullable);
378 assert!(path.segments[0].list.as_ref().unwrap().elements_nullable);
379 }
380
381 #[test]
382 fn test_parse_mixed_nullable_path() {
383 let path = ParsedPath::parse("epoch?.checkpoints?.nodes?[].digest").unwrap();
384 assert_eq!(path.segments.len(), 4);
385 assert!(path.segments[0].is_nullable);
386 assert!(!path.segments[0].is_list());
387 assert!(path.segments[1].is_nullable);
388 assert!(!path.segments[1].is_list());
389 assert!(path.segments[2].is_nullable);
390 assert!(path.segments[2].is_list());
391 assert!(!path.segments[3].is_nullable);
392 assert!(!path.segments[3].is_list());
393 }
394
395 #[test]
396 fn test_parse_invalid_suffix_error() {
397 let err = ParsedPath::parse("foo[?].name").unwrap_err();
398 assert!(matches!(err, PathParseError::InvalidSuffix { .. }));
399 }
400}