1use std::hash::Hash;
4
5use apollo_compiler::collections::IndexMap;
6use apollo_compiler::collections::IndexSet;
7use serde_json_bytes::Map as JSONMap;
8use serde_json_bytes::Value as JSON;
9use serde_json_bytes::json;
10use shape::Shape;
11use shape::ShapeCase;
12use shape::location::Location;
13use shape::location::SourceId;
14
15use super::helpers::json_merge;
16use super::helpers::json_type_name;
17use super::immutable::InputPath;
18use super::known_var::KnownVariable;
19use super::lit_expr::LitExpr;
20use super::lit_expr::LitOp;
21use super::location::OffsetRange;
22use super::location::Ranged;
23use super::location::WithRange;
24use super::methods::ArrowMethod;
25use super::parser::*;
26use crate::connectors::spec::ConnectSpec;
27
28impl JSONSelection {
29 pub fn apply_to(&self, data: &JSON) -> (Option<JSON>, Vec<ApplyToError>) {
35 self.apply_with_vars(data, &IndexMap::default())
36 }
37
38 pub fn apply_with_vars(
39 &self,
40 data: &JSON,
41 vars: &IndexMap<String, JSON>,
42 ) -> (Option<JSON>, Vec<ApplyToError>) {
43 let mut errors = IndexSet::default();
45
46 let mut vars_with_paths: VarsWithPathsMap = IndexMap::default();
47 for (var_name, var_data) in vars {
48 vars_with_paths.insert(
49 KnownVariable::External(var_name.clone()),
50 (var_data, InputPath::empty().append(json!(var_name))),
51 );
52 }
53 vars_with_paths.insert(KnownVariable::Dollar, (data, InputPath::empty()));
57
58 let spec = self.spec();
59 let (value, apply_errors) =
60 self.apply_to_path(data, &vars_with_paths, &InputPath::empty(), spec);
61
62 errors.extend(apply_errors);
68
69 (value, errors.into_iter().collect())
70 }
71
72 pub fn shape(&self) -> Shape {
73 let context =
74 ShapeContext::new(SourceId::Other("JSONSelection".into())).with_spec(self.spec());
75
76 self.compute_output_shape(
77 &context,
80 Shape::name("$root", Vec::new()),
91 )
92 }
93
94 pub(crate) fn compute_output_shape(&self, context: &ShapeContext, input_shape: Shape) -> Shape {
95 debug_assert_eq!(context.spec(), self.spec());
96
97 let computable: &dyn ApplyToInternal = match &self.inner {
98 TopLevelSelection::Named(selection) => selection,
99 TopLevelSelection::Path(path_selection) => path_selection,
100 };
101
102 let dollar_shape = input_shape.clone();
103
104 if Some(&input_shape) == context.named_shapes().get("$root") {
105 computable.compute_output_shape(context, input_shape, dollar_shape)
108 } else {
109 let cloned_context = context
112 .clone()
113 .with_named_shapes([("$root".to_string(), input_shape.clone())]);
114 computable.compute_output_shape(&cloned_context, input_shape, dollar_shape)
115 }
116 }
117}
118
119pub(super) type VarsWithPathsMap<'a> = IndexMap<KnownVariable, (&'a JSON, InputPath<JSON>)>;
120
121fn lookup_variable<'a>(
122 vars: &'a VarsWithPathsMap,
123 var_name: &str,
124) -> Option<(&'a JSON, &'a InputPath<JSON>)> {
125 let entry = if var_name == "$" {
126 vars.get(&KnownVariable::Dollar)
127 } else {
128 vars.get(&KnownVariable::Local(var_name.to_string()))
132 .or_else(|| vars.get(&KnownVariable::External(var_name.to_string())))
133 };
134 entry.map(|(data, path)| (*data, path))
135}
136
137impl Ranged for JSONSelection {
138 fn range(&self) -> OffsetRange {
139 match &self.inner {
140 TopLevelSelection::Named(selection) => selection.range(),
141 TopLevelSelection::Path(path_selection) => path_selection.range(),
142 }
143 }
144
145 fn shape_location(&self, source_id: &SourceId) -> Option<Location> {
146 self.range().map(|range| source_id.location(range))
147 }
148}
149
150pub(super) trait ApplyToInternal {
151 fn apply_to_path(
154 &self,
155 data: &JSON,
156 vars: &VarsWithPathsMap,
157 input_path: &InputPath<JSON>,
158 spec: ConnectSpec,
159 ) -> (Option<JSON>, Vec<ApplyToError>);
160
161 fn apply_to_array(
164 &self,
165 data_array: &[JSON],
166 vars: &VarsWithPathsMap,
167 input_path: &InputPath<JSON>,
168 spec: ConnectSpec,
169 ) -> (Option<JSON>, Vec<ApplyToError>) {
170 let mut output = Vec::with_capacity(data_array.len());
171 let mut errors = Vec::new();
172
173 for (i, element) in data_array.iter().enumerate() {
174 let input_path_with_index = input_path.append(json!(i));
175 let (applied, apply_errors) =
176 self.apply_to_path(element, vars, &input_path_with_index, spec);
177 errors.extend(apply_errors);
178 output.push(applied.unwrap_or(JSON::Null));
182 }
183
184 (Some(JSON::Array(output)), errors)
185 }
186
187 fn compute_output_shape(
191 &self,
192 context: &ShapeContext,
193 input_shape: Shape,
196 dollar_shape: Shape,
200 ) -> Shape;
201}
202
203#[derive(Debug, Clone, Eq, PartialEq)]
204pub(crate) struct ShapeContext {
205 #[allow(dead_code)]
208 spec: ConnectSpec,
209
210 named_shapes: IndexMap<String, Shape>,
215
216 source_id: SourceId,
219}
220
221impl ShapeContext {
222 pub(crate) fn new(source_id: SourceId) -> Self {
223 Self {
224 spec: JSONSelection::default_connect_spec(),
225 named_shapes: IndexMap::default(),
226 source_id,
227 }
228 }
229
230 #[allow(dead_code)]
231 pub(crate) fn spec(&self) -> ConnectSpec {
232 self.spec
233 }
234
235 pub(crate) fn with_spec(mut self, spec: ConnectSpec) -> Self {
236 self.spec = spec;
237 self
238 }
239
240 pub(crate) fn named_shapes(&self) -> &IndexMap<String, Shape> {
241 &self.named_shapes
242 }
243
244 pub(crate) fn with_named_shapes(
245 mut self,
246 named_shapes: impl IntoIterator<Item = (String, Shape)>,
247 ) -> Self {
248 for (name, shape) in named_shapes {
249 self.named_shapes.insert(name.clone(), shape.clone());
250 }
251 self
252 }
253
254 pub(crate) fn source_id(&self) -> &SourceId {
255 &self.source_id
256 }
257}
258
259#[derive(Debug, Eq, PartialEq, Clone, Hash)]
260pub struct ApplyToError {
261 message: String,
262 path: Vec<JSON>,
263 range: OffsetRange,
264 spec: ConnectSpec,
265}
266
267impl ApplyToError {
268 pub(crate) const fn new(
269 message: String,
270 path: Vec<JSON>,
271 range: OffsetRange,
272 spec: ConnectSpec,
273 ) -> Self {
274 Self {
275 message,
276 path,
277 range,
278 spec,
279 }
280 }
281
282 #[cfg(test)]
285 pub(crate) fn from_json(json: &JSON) -> Self {
286 use crate::link::spec::Version;
287
288 let error = json.as_object().unwrap();
289 let message = error.get("message").unwrap().as_str().unwrap().to_string();
290 let path = error.get("path").unwrap().as_array().unwrap().clone();
291 let range = error.get("range").unwrap().as_array().unwrap();
292 let spec = error
293 .get("spec")
294 .and_then(|s| s.as_str())
295 .and_then(|s| match s.parse::<Version>() {
296 Ok(version) => ConnectSpec::try_from(&version).ok(),
297 Err(_) => None,
298 })
299 .unwrap_or_else(ConnectSpec::latest);
300
301 Self {
302 message,
303 path,
304 range: if range.len() == 2 {
305 let start = range[0].as_u64().unwrap() as usize;
306 let end = range[1].as_u64().unwrap() as usize;
307 Some(start..end)
308 } else {
309 None
310 },
311 spec,
312 }
313 }
314
315 pub fn message(&self) -> &str {
316 self.message.as_str()
317 }
318
319 pub fn path(&self) -> &[JSON] {
320 self.path.as_slice()
321 }
322
323 pub fn range(&self) -> OffsetRange {
324 self.range.clone()
325 }
326
327 pub fn spec(&self) -> ConnectSpec {
328 self.spec
329 }
330}
331
332pub(super) trait ApplyToResultMethods {
336 fn prepend_errors(self, errors: Vec<ApplyToError>) -> Self;
337
338 fn and_then_collecting_errors(
339 self,
340 f: impl FnOnce(&JSON) -> (Option<JSON>, Vec<ApplyToError>),
341 ) -> (Option<JSON>, Vec<ApplyToError>);
342}
343
344impl ApplyToResultMethods for (Option<JSON>, Vec<ApplyToError>) {
345 fn prepend_errors(self, mut errors: Vec<ApplyToError>) -> Self {
349 if errors.is_empty() {
350 self
351 } else {
352 let (value_opt, apply_errors) = self;
353 errors.extend(apply_errors);
354 (value_opt, errors)
355 }
356 }
357
358 fn and_then_collecting_errors(
361 self,
362 f: impl FnOnce(&JSON) -> (Option<JSON>, Vec<ApplyToError>),
363 ) -> (Option<JSON>, Vec<ApplyToError>) {
364 match self {
365 (Some(data), errors) => f(&data).prepend_errors(errors),
366 (None, errors) => (None, errors),
367 }
368 }
369}
370
371impl ApplyToInternal for JSONSelection {
372 fn apply_to_path(
373 &self,
374 data: &JSON,
375 vars: &VarsWithPathsMap,
376 input_path: &InputPath<JSON>,
377 _spec: ConnectSpec,
378 ) -> (Option<JSON>, Vec<ApplyToError>) {
379 match &self.inner {
380 TopLevelSelection::Named(named_selections) => {
387 named_selections.apply_to_path(data, vars, input_path, self.spec)
388 }
389 TopLevelSelection::Path(path_selection) => {
390 path_selection.apply_to_path(data, vars, input_path, self.spec)
391 }
392 }
393 }
394
395 fn compute_output_shape(
396 &self,
397 context: &ShapeContext,
398 input_shape: Shape,
399 dollar_shape: Shape,
400 ) -> Shape {
401 debug_assert_eq!(context.spec(), self.spec());
402
403 match &self.inner {
404 TopLevelSelection::Named(selection) => {
405 selection.compute_output_shape(context, input_shape, dollar_shape)
406 }
407 TopLevelSelection::Path(path_selection) => {
408 path_selection.compute_output_shape(context, input_shape, dollar_shape)
409 }
410 }
411 }
412}
413
414impl ApplyToInternal for NamedSelection {
415 fn apply_to_path(
416 &self,
417 data: &JSON,
418 vars: &VarsWithPathsMap,
419 input_path: &InputPath<JSON>,
420 spec: ConnectSpec,
421 ) -> (Option<JSON>, Vec<ApplyToError>) {
422 let mut output: Option<JSON> = None;
423 let mut errors = Vec::new();
424
425 let (value_opt, apply_errors) = self.path.apply_to_path(data, vars, input_path, spec);
426 errors.extend(apply_errors);
427
428 match &self.prefix {
429 NamingPrefix::Alias(alias) => {
430 if let Some(value) = value_opt {
431 output = Some(json!({ alias.name.as_str(): value }));
432 }
433 }
434
435 NamingPrefix::Spread(_spread_range) => {
436 match value_opt {
437 Some(JSON::Object(_) | JSON::Null) => {
438 output = value_opt;
441 }
442 Some(value) => {
443 errors.push(ApplyToError::new(
444 format!("Expected object or null, not {}", json_type_name(&value)),
445 input_path.to_vec(),
446 self.path.range(),
447 spec,
448 ));
449 }
450 None => {
451 errors.push(ApplyToError::new(
452 "Inlined path produced no value".to_string(),
453 input_path.to_vec(),
454 self.path.range(),
455 spec,
456 ));
457 }
458 };
459 }
460
461 NamingPrefix::None => {
462 if let Some(single_key) = self.path.get_single_key() {
467 if let Some(value) = value_opt {
468 output = Some(json!({ single_key.as_str(): value }));
469 }
470 } else {
471 output = value_opt;
472 }
473 }
474 }
475
476 (output, errors)
477 }
478
479 fn compute_output_shape(
480 &self,
481 context: &ShapeContext,
482 input_shape: Shape,
483 dollar_shape: Shape,
484 ) -> Shape {
485 let path_shape = self
486 .path
487 .compute_output_shape(context, input_shape, dollar_shape);
488
489 if let Some(single_output_key) = self.get_single_key() {
490 let mut map = Shape::empty_map();
491 map.insert(single_output_key.as_string(), path_shape);
492 Shape::record(map, self.shape_location(context.source_id()))
493 } else {
494 path_shape
495 }
496 }
497}
498
499impl ApplyToInternal for PathSelection {
500 fn apply_to_path(
501 &self,
502 data: &JSON,
503 vars: &VarsWithPathsMap,
504 input_path: &InputPath<JSON>,
505 spec: ConnectSpec,
506 ) -> (Option<JSON>, Vec<ApplyToError>) {
507 match (self.path.as_ref(), vars.get(&KnownVariable::Dollar)) {
508 (PathList::Key(_, _), Some((dollar_data, dollar_path))) => {
514 self.path
515 .apply_to_path(dollar_data, vars, dollar_path, spec)
516 }
517
518 _ => self.path.apply_to_path(data, vars, input_path, spec),
523 }
524 }
525
526 fn compute_output_shape(
527 &self,
528 context: &ShapeContext,
529 input_shape: Shape,
530 dollar_shape: Shape,
531 ) -> Shape {
532 match self.path.as_ref() {
533 PathList::Key(_, _) => {
534 self.path
538 .compute_output_shape(context, dollar_shape.clone(), dollar_shape)
539 }
540 _ => self
543 .path
544 .compute_output_shape(context, input_shape, dollar_shape),
545 }
546 }
547}
548
549impl ApplyToInternal for WithRange<PathList> {
550 fn apply_to_path(
551 &self,
552 data: &JSON,
553 vars: &VarsWithPathsMap,
554 input_path: &InputPath<JSON>,
555 spec: ConnectSpec,
556 ) -> (Option<JSON>, Vec<ApplyToError>) {
557 match self.as_ref() {
558 PathList::Var(ranged_var_name, tail) => {
559 let var_name = ranged_var_name.as_ref();
560 if var_name == &KnownVariable::AtSign {
561 tail.apply_to_path(data, vars, input_path, spec)
565 } else if let Some((var_data, var_path)) = lookup_variable(vars, var_name.as_str())
566 {
567 tail.apply_to_path(var_data, vars, var_path, spec)
572 } else {
573 (
574 None,
575 vec![ApplyToError::new(
576 format!("Variable {} not found", var_name.as_str()),
577 input_path.to_vec(),
578 ranged_var_name.range(),
579 spec,
580 )],
581 )
582 }
583 }
584 PathList::Key(key, tail) => {
585 let input_path_with_key = input_path.append(key.to_json());
586
587 if let JSON::Array(array) = data {
588 let empty_tail = WithRange::new(PathList::Empty, tail.range());
594 let self_with_empty_tail =
595 WithRange::new(PathList::Key(key.clone(), empty_tail), key.range());
596
597 self_with_empty_tail
598 .apply_to_array(array, vars, input_path, spec)
599 .and_then_collecting_errors(|shallow_mapped_array| {
600 tail.apply_to_path(
604 shallow_mapped_array,
605 vars,
606 &input_path_with_key,
607 spec,
608 )
609 })
610 } else {
611 let not_found = || {
612 (
613 None,
614 vec![ApplyToError::new(
615 format!(
616 "Property {} not found in {}",
617 key.dotted(),
618 json_type_name(data),
619 ),
620 input_path_with_key.to_vec(),
621 key.range(),
622 spec,
623 )],
624 )
625 };
626
627 if !matches!(data, JSON::Object(_)) {
628 return not_found();
629 }
630
631 if let Some(child) = data.get(key.as_str()) {
632 tail.apply_to_path(child, vars, &input_path_with_key, spec)
633 } else if tail.is_question() {
634 (None, vec![])
635 } else {
636 not_found()
637 }
638 }
639 }
640 PathList::Expr(expr, tail) => expr
641 .apply_to_path(data, vars, input_path, spec)
642 .and_then_collecting_errors(|value| {
643 tail.apply_to_path(value, vars, input_path, spec)
644 }),
645 PathList::Method(method_name, method_args, tail) => {
646 let method_path =
647 input_path.append(JSON::String(format!("->{}", method_name.as_ref()).into()));
648
649 ArrowMethod::lookup(method_name).map_or_else(
650 || {
651 (
652 None,
653 vec![ApplyToError::new(
654 format!("Method ->{} not found", method_name.as_ref()),
655 method_path.to_vec(),
656 method_name.range(),
657 spec,
658 )],
659 )
660 },
661 |method| {
662 let (result_opt, errors) = method.apply(
663 method_name,
664 method_args.as_ref(),
665 data,
666 vars,
667 &method_path,
668 spec,
669 );
670
671 if let (ArrowMethod::As, Some(JSON::Object(bindings))) =
678 (method, result_opt.as_ref())
679 {
680 let mut updated_vars = vars.clone();
681
682 for (var_name, var_value) in bindings {
683 updated_vars.insert(
684 KnownVariable::Local(var_name.as_str().to_string()),
685 (var_value, InputPath::empty().append(json!(var_name))),
687 );
688 }
689
690 return tail
691 .apply_to_path(data, &updated_vars, &method_path, spec)
694 .prepend_errors(errors);
695 }
696
697 if let Some(result) = result_opt.as_ref() {
698 tail.apply_to_path(result, vars, &method_path, spec)
699 .prepend_errors(errors)
700 } else {
701 (None, errors)
707 }
708 },
709 )
710 }
711 PathList::Selection(selection) => selection.apply_to_path(data, vars, input_path, spec),
712 PathList::Question(tail) => {
713 if data.is_null() {
715 (None, vec![])
716 } else {
717 tail.apply_to_path(data, vars, input_path, spec)
718 }
719 }
720 PathList::Empty => {
721 (Some(data.clone()), vec![])
724 }
725 }
726 }
727
728 fn compute_output_shape(
729 &self,
730 context: &ShapeContext,
731 input_shape: Shape,
732 dollar_shape: Shape,
733 ) -> Shape {
734 match input_shape.case() {
735 ShapeCase::One(shapes) => {
736 return Shape::one(
737 shapes.iter().map(|shape| {
738 self.compute_output_shape(context, shape.clone(), dollar_shape.clone())
739 }),
740 input_shape.locations.iter().cloned(),
741 );
742 }
743 ShapeCase::All(shapes) => {
744 return Shape::all(
745 shapes.iter().map(|shape| {
746 self.compute_output_shape(context, shape.clone(), dollar_shape.clone())
747 }),
748 input_shape.locations.iter().cloned(),
749 );
750 }
751 ShapeCase::Error(error) => {
752 return match error.partial.as_ref() {
753 Some(partial) => Shape::error_with_partial(
754 error.message.clone(),
755 self.compute_output_shape(context, partial.clone(), dollar_shape),
756 input_shape.locations.iter().cloned(),
757 ),
758 None => input_shape.clone(),
759 };
760 }
761 _ => {}
762 };
763
764 let mut extra_vars_opt: Option<Shape> = None;
768 let (current_shape, tail_opt) = match self.as_ref() {
769 PathList::Var(ranged_var_name, tail) => {
770 let var_name = ranged_var_name.as_ref();
771 let var_shape = if var_name == &KnownVariable::AtSign {
772 input_shape
773 } else if var_name == &KnownVariable::Dollar {
774 dollar_shape.clone()
775 } else if let Some(shape) = context.named_shapes().get(var_name.as_str()) {
776 shape.clone()
777 } else {
778 Shape::name(
779 var_name.as_str(),
780 ranged_var_name.shape_location(context.source_id()),
781 )
782 };
783 (var_shape, Some(tail))
784 }
785
786 PathList::Key(key, tail) => {
791 if input_shape.is_none() {
792 return input_shape;
804 }
805
806 let child_shape = field(&input_shape, key, context.source_id());
807
808 if child_shape.is_none() {
819 return Shape::error(
820 format!(
821 "Property {} not found in {}",
822 key.dotted(),
823 input_shape.pretty_print()
824 ),
825 key.shape_location(context.source_id()),
826 );
827 }
828
829 (child_shape, Some(tail))
830 }
831
832 PathList::Expr(expr, tail) => (
833 expr.compute_output_shape(context, input_shape, dollar_shape.clone()),
834 Some(tail),
835 ),
836
837 PathList::Method(method_name, method_args, tail) => {
838 if input_shape.is_none() {
839 return input_shape;
847 }
848
849 if let Some(method) = ArrowMethod::lookup(method_name) {
850 if context.spec() < ConnectSpec::V0_3 {
855 (
856 Shape::unknown(method_name.shape_location(context.source_id())),
857 None,
858 )
859 } else {
860 let result_shape = method.shape(
861 context,
862 method_name,
863 method_args.as_ref(),
864 input_shape.clone(),
865 dollar_shape.clone(),
866 );
867
868 if method == ArrowMethod::As {
871 extra_vars_opt = Some(result_shape);
876 (
877 input_shape,
881 Some(tail),
882 )
883 } else {
884 (result_shape, Some(tail))
885 }
886 }
887 } else {
888 (
889 Shape::error(
890 format!("Method ->{} not found", method_name.as_str()),
891 method_name.shape_location(context.source_id()),
892 ),
893 None,
894 )
895 }
896 }
897
898 PathList::Question(tail) => {
899 let q_shape = input_shape.question(self.shape_location(context.source_id()));
900 (
901 if tail.is_empty() {
902 q_shape
907 } else {
908 Shape::one([q_shape, Shape::none()], [])
915 },
916 Some(tail),
917 )
918 }
919
920 PathList::Selection(selection) => {
921 if input_shape.is_none() {
922 return input_shape;
928 }
929 (
930 selection.compute_output_shape(context, input_shape, dollar_shape.clone()),
931 None,
932 )
933 }
934
935 PathList::Empty => (input_shape, None),
936 };
937
938 if let Some(tail) = tail_opt {
939 fn compute_tail_shape(
943 tail: &WithRange<PathList>,
944 extra_vars_opt: &Option<Shape>,
945 context: &ShapeContext,
946 input_shape: Shape,
947 dollar_shape: Shape,
948 ) -> Shape {
949 match extra_vars_opt.as_ref().map(|s| s.case()) {
950 Some(ShapeCase::Object { fields, .. }) => {
951 let new_context = context.clone().with_named_shapes(
955 fields
956 .iter()
957 .map(|(name, shape)| (name.clone(), shape.clone())),
958 );
959 tail.compute_output_shape(&new_context, input_shape, dollar_shape)
960 }
961
962 Some(ShapeCase::Error(shape::Error { message, partial })) => {
963 if partial.is_some() {
964 let tail_shape = compute_tail_shape(
965 tail,
966 partial,
967 context,
968 input_shape,
969 dollar_shape,
970 );
971
972 Shape::error_with_partial(
973 message.clone(),
974 tail_shape,
975 tail.shape_location(context.source_id()),
976 )
977 } else {
978 Shape::error(message.clone(), tail.shape_location(context.source_id()))
979 }
980 }
981
982 _ => tail.compute_output_shape(context, input_shape, dollar_shape),
983 }
984 }
985
986 compute_tail_shape(
987 tail,
988 &extra_vars_opt,
989 context,
990 current_shape,
993 dollar_shape,
994 )
995 } else {
996 current_shape
997 }
998 }
999}
1000
1001impl ApplyToInternal for WithRange<LitExpr> {
1002 fn apply_to_path(
1003 &self,
1004 data: &JSON,
1005 vars: &VarsWithPathsMap,
1006 input_path: &InputPath<JSON>,
1007 spec: ConnectSpec,
1008 ) -> (Option<JSON>, Vec<ApplyToError>) {
1009 match self.as_ref() {
1010 LitExpr::String(s) => (Some(JSON::String(s.clone().into())), vec![]),
1011 LitExpr::Number(n) => (Some(JSON::Number(n.clone())), vec![]),
1012 LitExpr::Bool(b) => (Some(JSON::Bool(*b)), vec![]),
1013 LitExpr::Null => (Some(JSON::Null), vec![]),
1014 LitExpr::Object(map) => {
1015 let mut output = JSONMap::with_capacity(map.len());
1016 let mut errors = Vec::new();
1017 for (key, value) in map {
1018 let (value_opt, apply_errors) =
1019 value.apply_to_path(data, vars, input_path, spec);
1020 errors.extend(apply_errors);
1021 if let Some(value_json) = value_opt {
1022 output.insert(key.as_str(), value_json);
1023 }
1024 }
1025 (Some(JSON::Object(output)), errors)
1026 }
1027 LitExpr::Array(vec) => {
1028 let mut output = Vec::with_capacity(vec.len());
1029 let mut errors = Vec::new();
1030 for value in vec {
1031 let (value_opt, apply_errors) =
1032 value.apply_to_path(data, vars, input_path, spec);
1033 errors.extend(apply_errors);
1034 output.push(value_opt.unwrap_or(JSON::Null));
1035 }
1036 (Some(JSON::Array(output)), errors)
1037 }
1038 LitExpr::Path(path) => path.apply_to_path(data, vars, input_path, spec),
1039 LitExpr::LitPath(literal, subpath) => literal
1040 .apply_to_path(data, vars, input_path, spec)
1041 .and_then_collecting_errors(|value| {
1042 subpath.apply_to_path(value, vars, input_path, spec)
1043 }),
1044 LitExpr::OpChain(op, operands) => {
1045 match op.as_ref() {
1046 LitOp::NullishCoalescing => {
1047 let mut accumulated_errors = Vec::new();
1050 let mut last_value: Option<JSON> = None;
1051
1052 for operand in operands {
1053 let (value, errors) =
1054 operand.apply_to_path(data, vars, input_path, spec);
1055
1056 match value {
1057 Some(JSON::Null) | None => {
1059 accumulated_errors.extend(errors);
1061 last_value = value;
1062 continue;
1063 }
1064 Some(value) => {
1065 return (Some(value), errors);
1067 }
1068 }
1069 }
1070
1071 if last_value.is_none() {
1077 (None, accumulated_errors)
1080 } else {
1081 (last_value, Vec::new())
1086 }
1087 }
1088
1089 LitOp::NoneCoalescing => {
1090 let mut accumulated_errors = Vec::new();
1093
1094 for operand in operands {
1095 let (value, errors) =
1096 operand.apply_to_path(data, vars, input_path, spec);
1097
1098 match value {
1099 None => {
1101 accumulated_errors.extend(errors);
1102 continue;
1103 }
1104 Some(value) => {
1106 return (Some(value), errors);
1107 }
1108 }
1109 }
1110
1111 (None, accumulated_errors)
1113 }
1114 }
1115 }
1116 }
1117 }
1118
1119 fn compute_output_shape(
1120 &self,
1121 context: &ShapeContext,
1122 input_shape: Shape,
1123 dollar_shape: Shape,
1124 ) -> Shape {
1125 let locations = self.shape_location(context.source_id());
1126
1127 match self.as_ref() {
1128 LitExpr::Null => Shape::null(locations),
1129 LitExpr::Bool(value) => Shape::bool_value(*value, locations),
1130 LitExpr::String(value) => Shape::string_value(value.as_str(), locations),
1131
1132 LitExpr::Number(value) => {
1133 if let Some(n) = value.as_i64() {
1134 Shape::int_value(n, locations)
1135 } else if value.is_f64() {
1136 Shape::float(locations)
1137 } else {
1138 Shape::error("Number neither Int nor Float", locations)
1139 }
1140 }
1141
1142 LitExpr::Object(map) => {
1143 let mut fields = Shape::empty_map();
1144 for (key, value) in map {
1145 fields.insert(
1146 key.as_string(),
1147 value.compute_output_shape(
1148 context,
1149 input_shape.clone(),
1150 dollar_shape.clone(),
1151 ),
1152 );
1153 }
1154 Shape::object(fields, Shape::none(), locations)
1155 }
1156
1157 LitExpr::Array(vec) => {
1158 let mut shapes = Vec::with_capacity(vec.len());
1159 for value in vec {
1160 shapes.push(value.compute_output_shape(
1161 context,
1162 input_shape.clone(),
1163 dollar_shape.clone(),
1164 ));
1165 }
1166 Shape::array(shapes, Shape::none(), locations)
1167 }
1168
1169 LitExpr::Path(path) => path.compute_output_shape(context, input_shape, dollar_shape),
1170
1171 LitExpr::LitPath(literal, subpath) => {
1172 let literal_shape =
1173 literal.compute_output_shape(context, input_shape, dollar_shape.clone());
1174 subpath.compute_output_shape(context, literal_shape, dollar_shape)
1175 }
1176
1177 LitExpr::OpChain(op, operands) => {
1178 let mut shapes: Vec<Shape> = operands
1179 .iter()
1180 .map(|operand| {
1181 operand.compute_output_shape(
1182 context,
1183 input_shape.clone(),
1184 dollar_shape.clone(),
1185 )
1186 })
1187 .collect();
1188
1189 match op.as_ref() {
1190 LitOp::NullishCoalescing => {
1191 if let Some(last_shape) = shapes.pop() {
1192 let mut new_shapes = shapes
1193 .iter()
1194 .map(|shape| {
1195 shape
1196 .question(locations.clone())
1197 .not_none(locations.clone())
1198 })
1199 .collect::<Vec<_>>();
1200 new_shapes.push(last_shape);
1201 Shape::one(new_shapes, locations)
1202 } else {
1203 Shape::one(shapes, locations)
1204 }
1205 }
1206
1207 LitOp::NoneCoalescing => {
1209 if let Some(last_shape) = shapes.pop() {
1210 let mut new_shapes = shapes
1211 .iter()
1212 .map(|shape| shape.not_none(locations.clone()))
1213 .collect::<Vec<_>>();
1214 new_shapes.push(last_shape);
1215 Shape::one(new_shapes, locations)
1216 } else {
1217 Shape::one(shapes, locations)
1218 }
1219 }
1220 }
1221 }
1222 }
1223 }
1224}
1225
1226impl ApplyToInternal for SubSelection {
1227 fn apply_to_path(
1228 &self,
1229 data: &JSON,
1230 vars: &VarsWithPathsMap,
1231 input_path: &InputPath<JSON>,
1232 spec: ConnectSpec,
1233 ) -> (Option<JSON>, Vec<ApplyToError>) {
1234 if let JSON::Array(array) = data {
1235 return self.apply_to_array(array, vars, input_path, spec);
1236 }
1237
1238 let vars: VarsWithPathsMap = {
1239 let mut vars = vars.clone();
1240 vars.insert(KnownVariable::Dollar, (data, input_path.clone()));
1241 vars
1242 };
1243
1244 let mut output = JSON::Object(JSONMap::new());
1245 let mut errors = Vec::new();
1246
1247 for named_selection in self.selections.iter() {
1248 let (named_output_opt, apply_errors) =
1249 named_selection.apply_to_path(data, &vars, input_path, spec);
1250 errors.extend(apply_errors);
1251
1252 let (merged, merge_errors) = json_merge(Some(&output), named_output_opt.as_ref());
1253
1254 errors.extend(merge_errors.into_iter().map(|message| {
1255 ApplyToError::new(message, input_path.to_vec(), self.range(), spec)
1256 }));
1257
1258 if let Some(merged) = merged {
1259 output = merged;
1260 }
1261 }
1262
1263 if !matches!(data, JSON::Object(_)) {
1264 let output_is_empty = match &output {
1265 JSON::Object(map) => map.is_empty(),
1266 _ => false,
1267 };
1268 if output_is_empty {
1269 return (Some(data.clone()), errors);
1273 }
1274 }
1275
1276 (Some(output), errors)
1277 }
1278
1279 fn compute_output_shape(
1280 &self,
1281 context: &ShapeContext,
1282 input_shape: Shape,
1283 _previous_dollar_shape: Shape,
1284 ) -> Shape {
1285 if let ShapeCase::Array { prefix, tail } = input_shape.case() {
1289 let new_prefix = prefix
1290 .iter()
1291 .map(|shape| self.compute_output_shape(context, shape.clone(), shape.clone()))
1292 .collect::<Vec<_>>();
1293
1294 let new_tail = if tail.is_none() {
1295 tail.clone()
1296 } else {
1297 self.compute_output_shape(context, tail.clone(), tail.clone())
1298 };
1299
1300 return Shape::array(
1301 new_prefix,
1302 new_tail,
1303 self.shape_location(context.source_id()),
1304 );
1305 }
1306
1307 let input_shape = input_shape.any_item(Vec::new());
1311
1312 let dollar_shape = input_shape.clone();
1315
1316 let mut all_shape = Shape::none();
1319
1320 for named_selection in self.selections.iter() {
1321 all_shape = Shape::all(
1326 [
1327 all_shape,
1328 named_selection.compute_output_shape(
1329 context,
1330 input_shape.clone(),
1331 dollar_shape.clone(),
1332 ),
1333 ],
1334 self.shape_location(context.source_id()),
1335 );
1336
1337 if all_shape.is_null() {
1341 break;
1342 }
1343 }
1344
1345 if all_shape.is_none() {
1346 Shape::empty_object(self.shape_location(context.source_id()))
1347 } else {
1348 all_shape
1349 }
1350 }
1351}
1352
1353fn field(shape: &Shape, key: &WithRange<Key>, source_id: &SourceId) -> Shape {
1355 if let ShapeCase::One(inner) = shape.case() {
1356 let mut new_fields = Vec::new();
1357 for inner_field in inner {
1358 new_fields.push(field(inner_field, key, source_id));
1359 }
1360 return Shape::one(new_fields, shape.locations.iter().cloned());
1361 }
1362 if shape.is_none() || shape.is_null() {
1363 return Shape::none();
1364 }
1365 let field_shape = shape.field(key.as_str(), key.shape_location(source_id));
1366 if field_shape.is_none() {
1367 return Shape::error(
1368 format!("field `{field}` not found", field = key.as_str()),
1369 key.shape_location(source_id),
1370 );
1371 }
1372 field_shape
1373}
1374
1375#[cfg(test)]
1376mod tests {
1377 use rstest::rstest;
1378
1379 use super::*;
1380 use crate::assert_debug_snapshot;
1381 use crate::connectors::json_selection::PrettyPrintable;
1382 use crate::selection;
1383
1384 #[rstest]
1385 #[case::v0_2(ConnectSpec::V0_2)]
1386 #[case::v0_3(ConnectSpec::V0_3)]
1387 fn test_apply_to_selection(#[case] spec: ConnectSpec) {
1388 let data = json!({
1389 "hello": "world",
1390 "nested": {
1391 "hello": "world",
1392 "world": "hello",
1393 },
1394 "array": [
1395 { "hello": "world 0" },
1396 { "hello": "world 1" },
1397 { "hello": "world 2" },
1398 ],
1399 });
1400
1401 #[track_caller]
1402 fn check_ok(data: &JSON, selection: JSONSelection, expected_json: JSON) {
1403 let (actual_json, errors) = selection.apply_to(data);
1404 assert_eq!(actual_json, Some(expected_json));
1405 assert_eq!(errors, vec![]);
1406 }
1407
1408 check_ok(&data, selection!("hello", spec), json!({"hello": "world"}));
1409
1410 check_ok(
1411 &data,
1412 selection!("nested", spec),
1413 json!({
1414 "nested": {
1415 "hello": "world",
1416 "world": "hello",
1417 },
1418 }),
1419 );
1420
1421 check_ok(&data, selection!("nested.hello", spec), json!("world"));
1422 check_ok(&data, selection!("$.nested.hello", spec), json!("world"));
1423
1424 check_ok(&data, selection!("nested.world", spec), json!("hello"));
1425 check_ok(&data, selection!("$.nested.world", spec), json!("hello"));
1426
1427 check_ok(
1428 &data,
1429 selection!("nested hello", spec),
1430 json!({
1431 "hello": "world",
1432 "nested": {
1433 "hello": "world",
1434 "world": "hello",
1435 },
1436 }),
1437 );
1438
1439 check_ok(
1440 &data,
1441 selection!("array { hello }", spec),
1442 json!({
1443 "array": [
1444 { "hello": "world 0" },
1445 { "hello": "world 1" },
1446 { "hello": "world 2" },
1447 ],
1448 }),
1449 );
1450
1451 check_ok(
1452 &data,
1453 selection!("greetings: array { hello }", spec),
1454 json!({
1455 "greetings": [
1456 { "hello": "world 0" },
1457 { "hello": "world 1" },
1458 { "hello": "world 2" },
1459 ],
1460 }),
1461 );
1462
1463 check_ok(
1464 &data,
1465 selection!("$.array { hello }", spec),
1466 json!([
1467 { "hello": "world 0" },
1468 { "hello": "world 1" },
1469 { "hello": "world 2" },
1470 ]),
1471 );
1472
1473 check_ok(
1474 &data,
1475 selection!("worlds: array.hello", spec),
1476 json!({
1477 "worlds": [
1478 "world 0",
1479 "world 1",
1480 "world 2",
1481 ],
1482 }),
1483 );
1484
1485 check_ok(
1486 &data,
1487 selection!("worlds: $.array.hello", spec),
1488 json!({
1489 "worlds": [
1490 "world 0",
1491 "world 1",
1492 "world 2",
1493 ],
1494 }),
1495 );
1496
1497 check_ok(
1498 &data,
1499 selection!("array.hello", spec),
1500 json!(["world 0", "world 1", "world 2",]),
1501 );
1502
1503 check_ok(
1504 &data,
1505 selection!("$.array.hello", spec),
1506 json!(["world 0", "world 1", "world 2",]),
1507 );
1508
1509 check_ok(
1510 &data,
1511 selection!("nested grouped: { hello worlds: array.hello }", spec),
1512 json!({
1513 "nested": {
1514 "hello": "world",
1515 "world": "hello",
1516 },
1517 "grouped": {
1518 "hello": "world",
1519 "worlds": [
1520 "world 0",
1521 "world 1",
1522 "world 2",
1523 ],
1524 },
1525 }),
1526 );
1527
1528 check_ok(
1529 &data,
1530 selection!("nested grouped: { hello worlds: $.array.hello }", spec),
1531 json!({
1532 "nested": {
1533 "hello": "world",
1534 "world": "hello",
1535 },
1536 "grouped": {
1537 "hello": "world",
1538 "worlds": [
1539 "world 0",
1540 "world 1",
1541 "world 2",
1542 ],
1543 },
1544 }),
1545 );
1546 }
1547
1548 #[rstest]
1549 #[case::v0_2(ConnectSpec::V0_2)]
1550 #[case::v0_3(ConnectSpec::V0_3)]
1551 fn test_apply_to_errors(#[case] spec: ConnectSpec) {
1552 let data = json!({
1553 "hello": "world",
1554 "nested": {
1555 "hello": 123,
1556 "world": true,
1557 },
1558 "array": [
1559 { "hello": 1, "goodbye": "farewell" },
1560 { "hello": "two" },
1561 { "hello": 3.0, "smello": "yellow" },
1562 ],
1563 });
1564
1565 assert_eq!(
1566 selection!("hello", spec).apply_to(&data),
1567 (Some(json!({"hello": "world"})), vec![],)
1568 );
1569
1570 fn make_yellow_errors_expected(
1571 yellow_range: std::ops::Range<usize>,
1572 spec: ConnectSpec,
1573 ) -> Vec<ApplyToError> {
1574 vec![ApplyToError::new(
1575 "Property .yellow not found in object".to_string(),
1576 vec![json!("yellow")],
1577 Some(yellow_range),
1578 spec,
1579 )]
1580 }
1581 assert_eq!(
1582 selection!("yellow", spec).apply_to(&data),
1583 (Some(json!({})), make_yellow_errors_expected(0..6, spec)),
1584 );
1585 assert_eq!(
1586 selection!("$.yellow", spec).apply_to(&data),
1587 (None, make_yellow_errors_expected(2..8, spec)),
1588 );
1589
1590 assert_eq!(
1591 selection!("nested.hello", spec).apply_to(&data),
1592 (Some(json!(123)), vec![],)
1593 );
1594
1595 fn make_quoted_yellow_expected(
1596 yellow_range: std::ops::Range<usize>,
1597 spec: ConnectSpec,
1598 ) -> (Option<JSON>, Vec<ApplyToError>) {
1599 (
1600 None,
1601 vec![ApplyToError::new(
1602 "Property .\"yellow\" not found in object".to_string(),
1603 vec![json!("nested"), json!("yellow")],
1604 Some(yellow_range),
1605 spec,
1606 )],
1607 )
1608 }
1609 assert_eq!(
1610 selection!("nested.'yellow'", spec).apply_to(&data),
1611 make_quoted_yellow_expected(7..15, spec),
1612 );
1613 assert_eq!(
1614 selection!("nested.\"yellow\"", spec).apply_to(&data),
1615 make_quoted_yellow_expected(7..15, spec),
1616 );
1617 assert_eq!(
1618 selection!("$.nested.'yellow'", spec).apply_to(&data),
1619 make_quoted_yellow_expected(9..17, spec),
1620 );
1621
1622 fn make_nested_path_expected(
1623 hola_range: (usize, usize),
1624 yellow_range: (usize, usize),
1625 spec: ConnectSpec,
1626 ) -> (Option<JSON>, Vec<ApplyToError>) {
1627 (
1628 Some(json!({
1629 "world": true,
1630 })),
1631 vec![
1632 ApplyToError::from_json(&json!({
1633 "message": "Property .hola not found in object",
1634 "path": ["nested", "hola"],
1635 "range": hola_range,
1636 "spec": spec.to_string(),
1637 })),
1638 ApplyToError::from_json(&json!({
1639 "message": "Property .yellow not found in object",
1640 "path": ["nested", "yellow"],
1641 "range": yellow_range,
1642 "spec": spec.to_string(),
1643 })),
1644 ],
1645 )
1646 }
1647 assert_eq!(
1648 selection!("$.nested { hola yellow world }", spec).apply_to(&data),
1649 make_nested_path_expected((11, 15), (16, 22), spec),
1650 );
1651 assert_eq!(
1652 selection!(" $ . nested { hola yellow world } ", spec).apply_to(&data),
1653 make_nested_path_expected((14, 18), (19, 25), spec),
1654 );
1655
1656 fn make_partial_array_expected(
1657 goodbye_range: (usize, usize),
1658 spec: ConnectSpec,
1659 ) -> (Option<JSON>, Vec<ApplyToError>) {
1660 (
1661 Some(json!({
1662 "partial": [
1663 { "hello": 1, "goodbye": "farewell" },
1664 { "hello": "two" },
1665 { "hello": 3.0 },
1666 ],
1667 })),
1668 vec![
1669 ApplyToError::from_json(&json!({
1670 "message": "Property .goodbye not found in object",
1671 "path": ["array", 1, "goodbye"],
1672 "range": goodbye_range,
1673 "spec": spec.to_string(),
1674 })),
1675 ApplyToError::from_json(&json!({
1676 "message": "Property .goodbye not found in object",
1677 "path": ["array", 2, "goodbye"],
1678 "range": goodbye_range,
1679 "spec": spec.to_string(),
1680 })),
1681 ],
1682 )
1683 }
1684 assert_eq!(
1685 selection!("partial: $.array { hello goodbye }", spec).apply_to(&data),
1686 make_partial_array_expected((25, 32), spec),
1687 );
1688 assert_eq!(
1689 selection!(" partial : $ . array { hello goodbye } ", spec).apply_to(&data),
1690 make_partial_array_expected((29, 36), spec),
1691 );
1692
1693 assert_eq!(
1694 selection!("good: array.hello bad: array.smello", spec).apply_to(&data),
1695 (
1696 Some(json!({
1697 "good": [
1698 1,
1699 "two",
1700 3.0,
1701 ],
1702 "bad": [
1703 null,
1704 null,
1705 "yellow",
1706 ],
1707 })),
1708 vec![
1709 ApplyToError::from_json(&json!({
1710 "message": "Property .smello not found in object",
1711 "path": ["array", 0, "smello"],
1712 "range": [29, 35],
1713 "spec": spec.to_string(),
1714 })),
1715 ApplyToError::from_json(&json!({
1716 "message": "Property .smello not found in object",
1717 "path": ["array", 1, "smello"],
1718 "range": [29, 35],
1719 "spec": spec.to_string(),
1720 })),
1721 ],
1722 )
1723 );
1724
1725 assert_eq!(
1726 selection!("array { hello smello }", spec).apply_to(&data),
1727 (
1728 Some(json!({
1729 "array": [
1730 { "hello": 1 },
1731 { "hello": "two" },
1732 { "hello": 3.0, "smello": "yellow" },
1733 ],
1734 })),
1735 vec![
1736 ApplyToError::from_json(&json!({
1737 "message": "Property .smello not found in object",
1738 "path": ["array", 0, "smello"],
1739 "range": [14, 20],
1740 "spec": spec.to_string(),
1741 })),
1742 ApplyToError::from_json(&json!({
1743 "message": "Property .smello not found in object",
1744 "path": ["array", 1, "smello"],
1745 "range": [14, 20],
1746 "spec": spec.to_string(),
1747 })),
1748 ],
1749 )
1750 );
1751
1752 assert_eq!(
1753 selection!("$.nested { grouped: { hello smelly world } }", spec).apply_to(&data),
1754 (
1755 Some(json!({
1756 "grouped": {
1757 "hello": 123,
1758 "world": true,
1759 },
1760 })),
1761 vec![ApplyToError::from_json(&json!({
1762 "message": "Property .smelly not found in object",
1763 "path": ["nested", "smelly"],
1764 "range": [28, 34],
1765 "spec": spec.to_string(),
1766 }))],
1767 )
1768 );
1769
1770 assert_eq!(
1771 selection!("alias: $.nested { grouped: { hello smelly world } }", spec).apply_to(&data),
1772 (
1773 Some(json!({
1774 "alias": {
1775 "grouped": {
1776 "hello": 123,
1777 "world": true,
1778 },
1779 },
1780 })),
1781 vec![ApplyToError::from_json(&json!({
1782 "message": "Property .smelly not found in object",
1783 "path": ["nested", "smelly"],
1784 "range": [35, 41],
1785 "spec": spec.to_string(),
1786 }))],
1787 )
1788 );
1789 }
1790
1791 #[rstest]
1792 #[case::v0_2(ConnectSpec::V0_2)]
1793 #[case::v0_3(ConnectSpec::V0_3)]
1794 fn test_apply_to_nested_arrays(#[case] spec: ConnectSpec) {
1795 let data = json!({
1796 "arrayOfArrays": [
1797 [
1798 { "x": 0, "y": 0 },
1799 ],
1800 [
1801 { "x": 1, "y": 0 },
1802 { "x": 1, "y": 1 },
1803 { "x": 1, "y": 2 },
1804 ],
1805 [
1806 { "x": 2, "y": 0 },
1807 { "x": 2, "y": 1 },
1808 ],
1809 [],
1810 [
1811 null,
1812 { "x": 4, "y": 1 },
1813 { "x": 4, "why": 2 },
1814 null,
1815 { "x": 4, "y": 4 },
1816 ]
1817 ],
1818 });
1819
1820 fn make_array_of_arrays_x_expected(
1821 x_range: (usize, usize),
1822 spec: ConnectSpec,
1823 ) -> (Option<JSON>, Vec<ApplyToError>) {
1824 (
1825 Some(json!([[0], [1, 1, 1], [2, 2], [], [null, 4, 4, null, 4]])),
1826 vec![
1827 ApplyToError::from_json(&json!({
1828 "message": "Property .x not found in null",
1829 "path": ["arrayOfArrays", 4, 0, "x"],
1830 "range": x_range,
1831 "spec": spec.to_string(),
1832 })),
1833 ApplyToError::from_json(&json!({
1834 "message": "Property .x not found in null",
1835 "path": ["arrayOfArrays", 4, 3, "x"],
1836 "range": x_range,
1837 "spec": spec.to_string(),
1838 })),
1839 ],
1840 )
1841 }
1842 assert_eq!(
1843 selection!("arrayOfArrays.x", spec).apply_to(&data),
1844 make_array_of_arrays_x_expected((14, 15), spec),
1845 );
1846 assert_eq!(
1847 selection!("$.arrayOfArrays.x", spec).apply_to(&data),
1848 make_array_of_arrays_x_expected((16, 17), spec),
1849 );
1850
1851 fn make_array_of_arrays_y_expected(
1852 y_range: (usize, usize),
1853 spec: ConnectSpec,
1854 ) -> (Option<JSON>, Vec<ApplyToError>) {
1855 (
1856 Some(json!([
1857 [0],
1858 [0, 1, 2],
1859 [0, 1],
1860 [],
1861 [null, 1, null, null, 4],
1862 ])),
1863 vec![
1864 ApplyToError::from_json(&json!({
1865 "message": "Property .y not found in null",
1866 "path": ["arrayOfArrays", 4, 0, "y"],
1867 "range": y_range,
1868 "spec": spec.to_string(),
1869 })),
1870 ApplyToError::from_json(&json!({
1871 "message": "Property .y not found in object",
1872 "path": ["arrayOfArrays", 4, 2, "y"],
1873 "range": y_range,
1874 "spec": spec.to_string(),
1875 })),
1876 ApplyToError::from_json(&json!({
1877 "message": "Property .y not found in null",
1878 "path": ["arrayOfArrays", 4, 3, "y"],
1879 "range": y_range,
1880 "spec": spec.to_string(),
1881 })),
1882 ],
1883 )
1884 }
1885 assert_eq!(
1886 selection!("arrayOfArrays.y", spec).apply_to(&data),
1887 make_array_of_arrays_y_expected((14, 15), spec),
1888 );
1889 assert_eq!(
1890 selection!("$.arrayOfArrays.y", spec).apply_to(&data),
1891 make_array_of_arrays_y_expected((16, 17), spec),
1892 );
1893
1894 assert_eq!(
1895 selection!("alias: arrayOfArrays { x y }", spec).apply_to(&data),
1896 (
1897 Some(json!({
1898 "alias": [
1899 [
1900 { "x": 0, "y": 0 },
1901 ],
1902 [
1903 { "x": 1, "y": 0 },
1904 { "x": 1, "y": 1 },
1905 { "x": 1, "y": 2 },
1906 ],
1907 [
1908 { "x": 2, "y": 0 },
1909 { "x": 2, "y": 1 },
1910 ],
1911 [],
1912 [
1913 null,
1914 { "x": 4, "y": 1 },
1915 { "x": 4 },
1916 null,
1917 { "x": 4, "y": 4 },
1918 ]
1919 ],
1920 })),
1921 vec![
1922 ApplyToError::from_json(&json!({
1923 "message": "Property .x not found in null",
1924 "path": ["arrayOfArrays", 4, 0, "x"],
1925 "range": [23, 24],
1926 "spec": spec.to_string(),
1927 })),
1928 ApplyToError::from_json(&json!({
1929 "message": "Property .y not found in null",
1930 "path": ["arrayOfArrays", 4, 0, "y"],
1931 "range": [25, 26],
1932 "spec": spec.to_string(),
1933 })),
1934 ApplyToError::from_json(&json!({
1935 "message": "Property .y not found in object",
1936 "path": ["arrayOfArrays", 4, 2, "y"],
1937 "range": [25, 26],
1938 "spec": spec.to_string(),
1939 })),
1940 ApplyToError::from_json(&json!({
1941 "message": "Property .x not found in null",
1942 "path": ["arrayOfArrays", 4, 3, "x"],
1943 "range": [23, 24],
1944 "spec": spec.to_string(),
1945 })),
1946 ApplyToError::from_json(&json!({
1947 "message": "Property .y not found in null",
1948 "path": ["arrayOfArrays", 4, 3, "y"],
1949 "range": [25, 26],
1950 "spec": spec.to_string(),
1951 })),
1952 ],
1953 ),
1954 );
1955
1956 fn make_array_of_arrays_x_y_expected(
1957 x_range: (usize, usize),
1958 y_range: (usize, usize),
1959 spec: ConnectSpec,
1960 ) -> (Option<JSON>, Vec<ApplyToError>) {
1961 (
1962 Some(json!({
1963 "ys": [
1964 [0],
1965 [0, 1, 2],
1966 [0, 1],
1967 [],
1968 [null, 1, null, null, 4],
1969 ],
1970 "xs": [
1971 [0],
1972 [1, 1, 1],
1973 [2, 2],
1974 [],
1975 [null, 4, 4, null, 4],
1976 ],
1977 })),
1978 vec![
1979 ApplyToError::from_json(&json!({
1980 "message": "Property .y not found in null",
1981 "path": ["arrayOfArrays", 4, 0, "y"],
1982 "range": y_range,
1983 "spec": spec.to_string(),
1984 })),
1985 ApplyToError::from_json(&json!({
1986 "message": "Property .y not found in object",
1987 "path": ["arrayOfArrays", 4, 2, "y"],
1988 "range": y_range,
1989 "spec": spec.to_string(),
1990 })),
1991 ApplyToError::from_json(&json!({
1992 "path": ["arrayOfArrays", 4, 3, "y"],
1995 "message": "Property .y not found in null",
1996 "range": y_range,
1997 "spec": spec.to_string(),
1998 })),
1999 ApplyToError::from_json(&json!({
2000 "message": "Property .x not found in null",
2001 "path": ["arrayOfArrays", 4, 0, "x"],
2002 "range": x_range,
2003 "spec": spec.to_string(),
2004 })),
2005 ApplyToError::from_json(&json!({
2006 "message": "Property .x not found in null",
2007 "path": ["arrayOfArrays", 4, 3, "x"],
2008 "range": x_range,
2009 "spec": spec.to_string(),
2010 })),
2011 ],
2012 )
2013 }
2014 assert_eq!(
2015 selection!("ys: arrayOfArrays.y xs: arrayOfArrays.x", spec).apply_to(&data),
2016 make_array_of_arrays_x_y_expected((38, 39), (18, 19), spec),
2017 );
2018 assert_eq!(
2019 selection!("ys: $.arrayOfArrays.y xs: $.arrayOfArrays.x", spec).apply_to(&data),
2020 make_array_of_arrays_x_y_expected((42, 43), (20, 21), spec),
2021 );
2022 }
2023
2024 #[rstest]
2025 #[case::v0_2(ConnectSpec::V0_2)]
2026 #[case::v0_3(ConnectSpec::V0_3)]
2027 fn test_apply_to_variable_expressions(#[case] spec: ConnectSpec) {
2028 let id_object = selection!("id: $", spec).apply_to(&json!(123));
2029 assert_eq!(id_object, (Some(json!({"id": 123})), vec![]));
2030
2031 let data = json!({
2032 "id": 123,
2033 "name": "Ben",
2034 "friend_ids": [234, 345, 456]
2035 });
2036
2037 assert_eq!(
2038 selection!("id name friends: friend_ids { id: $ }", spec).apply_to(&data),
2039 (
2040 Some(json!({
2041 "id": 123,
2042 "name": "Ben",
2043 "friends": [
2044 { "id": 234 },
2045 { "id": 345 },
2046 { "id": 456 },
2047 ],
2048 })),
2049 vec![],
2050 ),
2051 );
2052
2053 let mut vars = IndexMap::default();
2054 vars.insert("$args".to_string(), json!({ "id": "id from args" }));
2055 assert_eq!(
2056 selection!("id: $args.id name", spec).apply_with_vars(&data, &vars),
2057 (
2058 Some(json!({
2059 "id": "id from args",
2060 "name": "Ben"
2061 })),
2062 vec![],
2063 ),
2064 );
2065 assert_eq!(
2066 selection!("nested.path { id: $args.id name }", spec).apply_to(&json!({
2067 "nested": {
2068 "path": data,
2069 },
2070 })),
2071 (
2072 Some(json!({
2073 "name": "Ben"
2074 })),
2075 vec![ApplyToError::from_json(&json!({
2076 "message": "Variable $args not found",
2077 "path": ["nested", "path"],
2078 "range": [18, 23],
2079 "spec": spec.to_string(),
2080 }))],
2081 ),
2082 );
2083 let mut vars_without_args_id = IndexMap::default();
2084 vars_without_args_id.insert("$args".to_string(), json!({ "unused": "ignored" }));
2085 assert_eq!(
2086 selection!("id: $args.id name", spec).apply_with_vars(&data, &vars_without_args_id),
2087 (
2088 Some(json!({
2089 "name": "Ben"
2090 })),
2091 vec![ApplyToError::from_json(&json!({
2092 "message": "Property .id not found in object",
2093 "path": ["$args", "id"],
2094 "range": [10, 12],
2095 "spec": spec.to_string(),
2096 }))],
2097 ),
2098 );
2099
2100 assert_eq!(
2102 selection!("$args.id", spec).apply_with_vars(&json!([1, 2, 3]), &vars),
2103 (Some(json!("id from args")), vec![]),
2104 );
2105 }
2106
2107 #[test]
2108 fn test_apply_to_variable_expressions_typename() {
2109 let typename_object =
2110 selection!("__typename: $->echo('Product') reviews { __typename: $->echo('Review') }")
2111 .apply_to(&json!({"reviews": [{}]}));
2112 assert_eq!(
2113 typename_object,
2114 (
2115 Some(json!({"__typename": "Product", "reviews": [{ "__typename": "Review" }] })),
2116 vec![]
2117 )
2118 );
2119 }
2120
2121 #[test]
2122 fn test_literal_expressions_in_parentheses() {
2123 assert_eq!(
2124 selection!("__typename: $('Product')").apply_to(&json!({})),
2125 (Some(json!({"__typename": "Product"})), vec![]),
2126 );
2127
2128 assert_eq!(
2129 selection!(" __typename : 'Product' ").apply_to(&json!({})),
2130 (
2131 Some(json!({})),
2132 vec![ApplyToError::new(
2133 "Property .\"Product\" not found in object".to_string(),
2134 vec![json!("Product")],
2135 Some(14..23),
2136 ConnectSpec::latest(),
2137 )],
2138 ),
2139 );
2140
2141 assert_eq!(
2142 selection!(
2143 r#"
2144 one: $(1)
2145 two: $(2)
2146 negativeThree: $(- 3)
2147 true: $(true )
2148 false: $( false)
2149 null: $(null)
2150 string: $("string")
2151 array: $( [ 1 , 2 , 3 ] )
2152 object: $( { "key" : "value" } )
2153 path: $(nested.path)
2154 "#
2155 )
2156 .apply_to(&json!({
2157 "nested": {
2158 "path": "nested path value"
2159 }
2160 })),
2161 (
2162 Some(json!({
2163 "one": 1,
2164 "two": 2,
2165 "negativeThree": -3,
2166 "true": true,
2167 "false": false,
2168 "null": null,
2169 "string": "string",
2170 "array": [1, 2, 3],
2171 "object": { "key": "value" },
2172 "path": "nested path value",
2173 })),
2174 vec![],
2175 ),
2176 );
2177
2178 assert_eq!(
2179 selection!(
2180 r#"
2181 one: $(1)->typeof
2182 two: $(2)->typeof
2183 negativeThree: $(-3)->typeof
2184 true: $(true)->typeof
2185 false: $(false)->typeof
2186 null: $(null)->typeof
2187 string: $("string")->typeof
2188 array: $([1, 2, 3])->typeof
2189 object: $({ "key": "value" })->typeof
2190 path: $(nested.path)->typeof
2191 "#
2192 )
2193 .apply_to(&json!({
2194 "nested": {
2195 "path": 12345
2196 }
2197 })),
2198 (
2199 Some(json!({
2200 "one": "number",
2201 "two": "number",
2202 "negativeThree": "number",
2203 "true": "boolean",
2204 "false": "boolean",
2205 "null": "null",
2206 "string": "string",
2207 "array": "array",
2208 "object": "object",
2209 "path": "number",
2210 })),
2211 vec![],
2212 ),
2213 );
2214
2215 assert_eq!(
2216 selection!(
2217 r#"
2218 items: $([
2219 1,
2220 -2.0,
2221 true,
2222 false,
2223 null,
2224 "string",
2225 [1, 2, 3],
2226 { "key": "value" },
2227 nested.path,
2228 ])->map(@->typeof)
2229 "#
2230 )
2231 .apply_to(&json!({
2232 "nested": {
2233 "path": { "deeply": "nested" }
2234 }
2235 })),
2236 (
2237 Some(json!({
2238 "items": [
2239 "number",
2240 "number",
2241 "boolean",
2242 "boolean",
2243 "null",
2244 "string",
2245 "array",
2246 "object",
2247 "object",
2248 ],
2249 })),
2250 vec![],
2251 ),
2252 );
2253
2254 assert_eq!(
2255 selection!(
2256 r#"
2257 $({
2258 one: 1,
2259 two: 2,
2260 negativeThree: -3,
2261 true: true,
2262 false: false,
2263 null: null,
2264 string: "string",
2265 array: [1, 2, 3],
2266 object: { "key": "value" },
2267 path: $ . nested . path ,
2268 })->entries
2269 "#
2270 )
2271 .apply_to(&json!({
2272 "nested": {
2273 "path": "nested path value"
2274 }
2275 })),
2276 (
2277 Some(json!([
2278 { "key": "one", "value": 1 },
2279 { "key": "two", "value": 2 },
2280 { "key": "negativeThree", "value": -3 },
2281 { "key": "true", "value": true },
2282 { "key": "false", "value": false },
2283 { "key": "null", "value": null },
2284 { "key": "string", "value": "string" },
2285 { "key": "array", "value": [1, 2, 3] },
2286 { "key": "object", "value": { "key": "value" } },
2287 { "key": "path", "value": "nested path value" },
2288 ])),
2289 vec![],
2290 ),
2291 );
2292
2293 assert_eq!(
2294 selection!(
2295 r#"
2296 $({
2297 string: $("string")->slice(1, 4),
2298 array: $([1, 2, 3])->map(@->add(10)),
2299 object: $({ "key": "value" })->get("key"),
2300 path: nested.path->slice($("nested ")->size),
2301 needlessParens: $("oyez"),
2302 withoutParens: "oyez",
2303 })
2304 "#
2305 )
2306 .apply_to(&json!({
2307 "nested": {
2308 "path": "nested path value"
2309 }
2310 })),
2311 (
2312 Some(json!({
2313 "string": "tri",
2314 "array": [11, 12, 13],
2315 "object": "value",
2316 "path": "path value",
2317 "needlessParens": "oyez",
2318 "withoutParens": "oyez",
2319 })),
2320 vec![],
2321 ),
2322 );
2323
2324 assert_eq!(
2325 selection!(
2326 r#"
2327 string: $("string")->slice(1, 4)
2328 array: $([1, 2, 3])->map(@->add(10))
2329 object: $({ "key": "value" })->get("key")
2330 path: nested.path->slice($("nested ")->size)
2331 "#
2332 )
2333 .apply_to(&json!({
2334 "nested": {
2335 "path": "nested path value"
2336 }
2337 })),
2338 (
2339 Some(json!({
2340 "string": "tri",
2341 "array": [11, 12, 13],
2342 "object": "value",
2343 "path": "path value",
2344 })),
2345 vec![],
2346 ),
2347 );
2348 }
2349
2350 #[rstest]
2351 #[case::v0_2(ConnectSpec::V0_2)]
2352 #[case::v0_3(ConnectSpec::V0_3)]
2353 fn test_inline_paths_with_subselections(#[case] spec: ConnectSpec) {
2354 let data = json!({
2355 "id": 123,
2356 "created": "2021-01-01T00:00:00Z",
2357 "model": "gpt-4o",
2358 "choices": [{
2359 "index": 0,
2360 "message": {
2361 "role": "assistant",
2362 "content": "The capital of Australia is Canberra.",
2363 },
2364 }, {
2365 "index": 1,
2366 "message": {
2367 "role": "assistant",
2368 "content": "The capital of Australia is Sydney.",
2369 },
2370 }],
2371 });
2372
2373 {
2374 let expected = (
2375 Some(json!({
2376 "id": 123,
2377 "created": "2021-01-01T00:00:00Z",
2378 "model": "gpt-4o",
2379 "role": "assistant",
2380 "content": "The capital of Australia is Canberra.",
2381 })),
2382 vec![],
2383 );
2384
2385 assert_eq!(
2386 selection!(
2387 r#"
2388 id
2389 created
2390 model
2391 role: choices->first.message.role
2392 content: choices->first.message.content
2393 "#,
2394 spec
2395 )
2396 .apply_to(&data),
2397 expected,
2398 );
2399
2400 assert_eq!(
2401 selection!(
2402 r#"
2403 id
2404 created
2405 model
2406 choices->first.message {
2407 role
2408 content
2409 }
2410 "#,
2411 spec
2412 )
2413 .apply_to(&data),
2414 expected,
2415 );
2416
2417 assert_eq!(
2418 selection!(
2419 r#"
2420 id
2421 choices->first.message {
2422 role
2423 content
2424 }
2425 created
2426 model
2427 "#,
2428 spec
2429 )
2430 .apply_to(&data),
2431 expected,
2432 );
2433 }
2434
2435 {
2436 let expected = (
2437 Some(json!({
2438 "id": 123,
2439 "created": "2021-01-01T00:00:00Z",
2440 "model": "gpt-4o",
2441 "role": "assistant",
2442 "message": "The capital of Australia is Sydney.",
2443 })),
2444 vec![],
2445 );
2446
2447 assert_eq!(
2448 selection!(
2449 r#"
2450 id
2451 created
2452 model
2453 role: choices->last.message.role
2454 message: choices->last.message.content
2455 "#,
2456 spec
2457 )
2458 .apply_to(&data),
2459 expected,
2460 );
2461
2462 assert_eq!(
2463 selection!(
2464 r#"
2465 id
2466 created
2467 model
2468 choices->last.message {
2469 role
2470 message: content
2471 }
2472 "#,
2473 spec
2474 )
2475 .apply_to(&data),
2476 expected,
2477 );
2478
2479 assert_eq!(
2480 selection!(
2481 r#"
2482 created
2483 choices->last.message {
2484 message: content
2485 role
2486 }
2487 model
2488 id
2489 "#,
2490 spec
2491 )
2492 .apply_to(&data),
2493 expected,
2494 );
2495 }
2496
2497 {
2498 let expected = (
2499 Some(json!({
2500 "id": 123,
2501 "created": "2021-01-01T00:00:00Z",
2502 "model": "gpt-4o",
2503 "role": "assistant",
2504 "correct": "The capital of Australia is Canberra.",
2505 "incorrect": "The capital of Australia is Sydney.",
2506 })),
2507 vec![],
2508 );
2509
2510 assert_eq!(
2511 selection!(
2512 r#"
2513 id
2514 created
2515 model
2516 role: choices->first.message.role
2517 correct: choices->first.message.content
2518 incorrect: choices->last.message.content
2519 "#,
2520 spec
2521 )
2522 .apply_to(&data),
2523 expected,
2524 );
2525
2526 assert_eq!(
2527 selection!(
2528 r#"
2529 id
2530 created
2531 model
2532 choices->first.message {
2533 role
2534 correct: content
2535 }
2536 choices->last.message {
2537 incorrect: content
2538 }
2539 "#,
2540 spec
2541 )
2542 .apply_to(&data),
2543 expected,
2544 );
2545
2546 assert_eq!(
2547 selection!(
2548 r#"
2549 id
2550 created
2551 model
2552 choices->first.message {
2553 role
2554 correct: content
2555 }
2556 incorrect: choices->last.message.content
2557 "#,
2558 spec
2559 )
2560 .apply_to(&data),
2561 expected,
2562 );
2563
2564 assert_eq!(
2565 selection!(
2566 r#"
2567 id
2568 created
2569 model
2570 choices->first.message {
2571 correct: content
2572 }
2573 choices->last.message {
2574 role
2575 incorrect: content
2576 }
2577 "#,
2578 spec
2579 )
2580 .apply_to(&data),
2581 expected,
2582 );
2583
2584 assert_eq!(
2585 selection!(
2586 r#"
2587 id
2588 created
2589 correct: choices->first.message.content
2590 choices->last.message {
2591 role
2592 incorrect: content
2593 }
2594 model
2595 "#,
2596 spec
2597 )
2598 .apply_to(&data),
2599 expected,
2600 );
2601 }
2602
2603 {
2604 let data = json!({
2605 "from": "data",
2606 });
2607
2608 let vars = {
2609 let mut vars = IndexMap::default();
2610 vars.insert(
2611 "$this".to_string(),
2612 json!({
2613 "id": 1234,
2614 }),
2615 );
2616 vars.insert(
2617 "$args".to_string(),
2618 json!({
2619 "input": {
2620 "title": "The capital of Australia",
2621 "body": "Canberra",
2622 },
2623 "extra": "extra",
2624 }),
2625 );
2626 vars
2627 };
2628
2629 let expected = (
2630 Some(json!({
2631 "id": 1234,
2632 "title": "The capital of Australia",
2633 "body": "Canberra",
2634 "from": "data",
2635 })),
2636 vec![],
2637 );
2638
2639 assert_eq!(
2640 selection!(
2641 r#"
2642 id: $this.id
2643 $args.input {
2644 title
2645 body
2646 }
2647 from
2648 "#,
2649 spec
2650 )
2651 .apply_with_vars(&data, &vars),
2652 expected,
2653 );
2654
2655 assert_eq!(
2656 selection!(
2657 r#"
2658 from
2659 $args.input { title body }
2660 id: $this.id
2661 "#,
2662 spec
2663 )
2664 .apply_with_vars(&data, &vars),
2665 expected,
2666 );
2667
2668 assert_eq!(
2669 selection!(
2670 r#"
2671 $args.input { body title }
2672 from
2673 id: $this.id
2674 "#,
2675 spec
2676 )
2677 .apply_with_vars(&data, &vars),
2678 expected,
2679 );
2680
2681 assert_eq!(
2682 selection!(
2683 r#"
2684 id: $this.id
2685 $args { $.input { title body } }
2686 from
2687 "#,
2688 spec
2689 )
2690 .apply_with_vars(&data, &vars),
2691 expected,
2692 );
2693
2694 assert_eq!(
2695 selection!(
2696 r#"
2697 id: $this.id
2698 $args { $.input { title body } extra }
2699 from: $.from
2700 "#,
2701 spec
2702 )
2703 .apply_with_vars(&data, &vars),
2704 (
2705 Some(json!({
2706 "id": 1234,
2707 "title": "The capital of Australia",
2708 "body": "Canberra",
2709 "extra": "extra",
2710 "from": "data",
2711 })),
2712 vec![],
2713 ),
2714 );
2715
2716 assert_eq!(
2717 selection!(
2718 r#"
2719 # Equivalent to id: $this.id
2720 $this { id }
2721
2722 $args {
2723 __typename: $("Args")
2724
2725 # Requiring $. instead of just . prevents .input from
2726 # parsing as a key applied to the $("Args") string.
2727 $.input { title body }
2728
2729 extra
2730 }
2731
2732 from: $.from
2733 "#,
2734 spec
2735 )
2736 .apply_with_vars(&data, &vars),
2737 (
2738 Some(json!({
2739 "id": 1234,
2740 "title": "The capital of Australia",
2741 "body": "Canberra",
2742 "__typename": "Args",
2743 "extra": "extra",
2744 "from": "data",
2745 })),
2746 vec![],
2747 ),
2748 );
2749 }
2750 }
2751
2752 #[test]
2753 fn test_inline_path_errors() {
2754 {
2755 let data = json!({
2756 "id": 123,
2757 "created": "2021-01-01T00:00:00Z",
2758 "model": "gpt-4o",
2759 "choices": [{
2760 "message": "The capital of Australia is Canberra.",
2761 }, {
2762 "message": "The capital of Australia is Sydney.",
2763 }],
2764 });
2765
2766 let expected = (
2767 Some(json!({
2768 "id": 123,
2769 "created": "2021-01-01T00:00:00Z",
2770 "model": "gpt-4o",
2771 })),
2772 vec![
2773 ApplyToError::new(
2774 "Property .role not found in string".to_string(),
2775 vec![
2776 json!("choices"),
2777 json!("->first"),
2778 json!("message"),
2779 json!("role"),
2780 ],
2781 Some(123..127),
2782 ConnectSpec::latest(),
2783 ),
2784 ApplyToError::new(
2785 "Property .content not found in string".to_string(),
2786 vec![
2787 json!("choices"),
2788 json!("->first"),
2789 json!("message"),
2790 json!("content"),
2791 ],
2792 Some(128..135),
2793 ConnectSpec::latest(),
2794 ),
2795 ApplyToError::new(
2796 "Expected object or null, not string".to_string(),
2797 vec![],
2798 Some(98..137),
2802 ConnectSpec::latest(),
2803 ),
2804 ],
2805 );
2806
2807 assert_eq!(
2808 selection!(
2809 r#"
2810 id
2811 created
2812 model
2813 choices->first.message { role content }
2814 "#
2815 )
2816 .apply_to(&data),
2817 expected,
2818 );
2819 }
2820
2821 assert_eq!(
2822 selection!("id nested.path.nonexistent { name }").apply_to(&json!({
2823 "id": 2345,
2824 "nested": {
2825 "path": "nested path value",
2826 },
2827 })),
2828 (
2829 Some(json!({
2830 "id": 2345,
2831 })),
2832 vec![
2833 ApplyToError::new(
2834 "Property .nonexistent not found in string".to_string(),
2835 vec![json!("nested"), json!("path"), json!("nonexistent")],
2836 Some(15..26),
2837 ConnectSpec::latest(),
2838 ),
2839 ApplyToError::new(
2840 "Inlined path produced no value".to_string(),
2841 vec![],
2842 Some(3..35),
2845 ConnectSpec::latest(),
2846 ),
2847 ],
2848 ),
2849 );
2850
2851 let valid_inline_path_selection = JSONSelection::named(SubSelection {
2852 selections: vec![NamedSelection {
2853 prefix: NamingPrefix::None,
2854 path: PathSelection {
2855 path: PathList::Key(
2856 Key::field("some").into_with_range(),
2857 PathList::Key(
2858 Key::field("object").into_with_range(),
2859 PathList::Empty.into_with_range(),
2860 )
2861 .into_with_range(),
2862 )
2863 .into_with_range(),
2864 },
2865 }],
2866 ..Default::default()
2867 });
2868
2869 assert_eq!(
2870 valid_inline_path_selection.apply_to(&json!({
2871 "some": {
2872 "object": {
2873 "key": "value",
2874 },
2875 },
2876 })),
2877 (
2878 Some(json!({
2879 "key": "value",
2880 })),
2881 vec![],
2882 ),
2883 );
2884 }
2885
2886 #[test]
2887 fn test_apply_to_non_identifier_properties() {
2888 let data = json!({
2889 "not an identifier": [
2890 { "also.not.an.identifier": 0 },
2891 { "also.not.an.identifier": 1 },
2892 { "also.not.an.identifier": 2 },
2893 ],
2894 "another": {
2895 "pesky string literal!": {
2896 "identifier": 123,
2897 "{ evil braces }": true,
2898 },
2899 },
2900 });
2901
2902 assert_eq!(
2903 selection!("alias: 'not an identifier' { safe: 'also.not.an.identifier' }")
2907 .apply_to(&data),
2908 (
2909 Some(json!({
2910 "alias": [
2911 { "safe": 0 },
2912 { "safe": 1 },
2913 { "safe": 2 },
2914 ],
2915 })),
2916 vec![],
2917 ),
2918 );
2919
2920 assert_eq!(
2921 selection!("'not an identifier'.'also.not.an.identifier'").apply_to(&data),
2922 (Some(json!([0, 1, 2])), vec![],),
2923 );
2924
2925 assert_eq!(
2926 selection!("$.'not an identifier'.'also.not.an.identifier'").apply_to(&data),
2927 (Some(json!([0, 1, 2])), vec![],),
2928 );
2929
2930 assert_eq!(
2931 selection!("$.\"not an identifier\" { safe: \"also.not.an.identifier\" }")
2932 .apply_to(&data),
2933 (
2934 Some(json!([
2935 { "safe": 0 },
2936 { "safe": 1 },
2937 { "safe": 2 },
2938 ])),
2939 vec![],
2940 ),
2941 );
2942
2943 assert_eq!(
2944 selection!(
2945 "another {
2946 pesky: 'pesky string literal!' {
2947 identifier
2948 evil: '{ evil braces }'
2949 }
2950 }"
2951 )
2952 .apply_to(&data),
2953 (
2954 Some(json!({
2955 "another": {
2956 "pesky": {
2957 "identifier": 123,
2958 "evil": true,
2959 },
2960 },
2961 })),
2962 vec![],
2963 ),
2964 );
2965
2966 assert_eq!(
2967 selection!("another.'pesky string literal!'.'{ evil braces }'").apply_to(&data),
2968 (Some(json!(true)), vec![],),
2969 );
2970
2971 assert_eq!(
2972 selection!("another.'pesky string literal!'.\"identifier\"").apply_to(&data),
2973 (Some(json!(123)), vec![],),
2974 );
2975
2976 assert_eq!(
2977 selection!("$.another.'pesky string literal!'.\"identifier\"").apply_to(&data),
2978 (Some(json!(123)), vec![],),
2979 );
2980 }
2981
2982 #[rstest]
2983 #[case::latest(ConnectSpec::V0_2)]
2984 #[case::next(ConnectSpec::V0_3)]
2985 fn test_left_associative_path_evaluation(#[case] spec: ConnectSpec) {
2986 assert_eq!(
2987 selection!("batch.id->first", spec).apply_to(&json!({
2988 "batch": [
2989 { "id": 1 },
2990 { "id": 2 },
2991 { "id": 3 },
2992 ],
2993 })),
2994 (Some(json!(1)), vec![]),
2995 );
2996
2997 assert_eq!(
2998 selection!("batch.id->last", spec).apply_to(&json!({
2999 "batch": [
3000 { "id": 1 },
3001 { "id": 2 },
3002 { "id": 3 },
3003 ],
3004 })),
3005 (Some(json!(3)), vec![]),
3006 );
3007
3008 assert_eq!(
3009 selection!("batch.id->size", spec).apply_to(&json!({
3010 "batch": [
3011 { "id": 1 },
3012 { "id": 2 },
3013 { "id": 3 },
3014 ],
3015 })),
3016 (Some(json!(3)), vec![]),
3017 );
3018
3019 assert_eq!(
3020 selection!("batch.id->slice(1)->first", spec).apply_to(&json!({
3021 "batch": [
3022 { "id": 1 },
3023 { "id": 2 },
3024 { "id": 3 },
3025 ],
3026 })),
3027 (Some(json!(2)), vec![]),
3028 );
3029
3030 assert_eq!(
3031 selection!("batch.id->map({ batchId: @ })", spec).apply_to(&json!({
3032 "batch": [
3033 { "id": 1 },
3034 { "id": 2 },
3035 { "id": 3 },
3036 ],
3037 })),
3038 (
3039 Some(json!([
3040 { "batchId": 1 },
3041 { "batchId": 2 },
3042 { "batchId": 3 },
3043 ])),
3044 vec![],
3045 ),
3046 );
3047
3048 let mut vars = IndexMap::default();
3049 vars.insert(
3050 "$batch".to_string(),
3051 json!([
3052 { "id": 4 },
3053 { "id": 5 },
3054 { "id": 6 },
3055 ]),
3056 );
3057 assert_eq!(
3058 selection!("$batch.id->map({ batchId: @ })", spec).apply_with_vars(
3059 &json!({
3060 "batch": "ignored",
3061 }),
3062 &vars
3063 ),
3064 (
3065 Some(json!([
3066 { "batchId": 4 },
3067 { "batchId": 5 },
3068 { "batchId": 6 },
3069 ])),
3070 vec![],
3071 ),
3072 );
3073
3074 assert_eq!(
3075 selection!("batch.id->map({ batchId: @ })->first", spec).apply_to(&json!({
3076 "batch": [
3077 { "id": 7 },
3078 { "id": 8 },
3079 { "id": 9 },
3080 ],
3081 })),
3082 (Some(json!({ "batchId": 7 })), vec![]),
3083 );
3084
3085 assert_eq!(
3086 selection!("batch.id->map({ batchId: @ })->last", spec).apply_to(&json!({
3087 "batch": [
3088 { "id": 7 },
3089 { "id": 8 },
3090 { "id": 9 },
3091 ],
3092 })),
3093 (Some(json!({ "batchId": 9 })), vec![]),
3094 );
3095
3096 assert_eq!(
3097 selection!("$batch.id->map({ batchId: @ })->first", spec).apply_with_vars(
3098 &json!({
3099 "batch": "ignored",
3100 }),
3101 &vars
3102 ),
3103 (Some(json!({ "batchId": 4 })), vec![]),
3104 );
3105
3106 assert_eq!(
3107 selection!("$batch.id->map({ batchId: @ })->last", spec).apply_with_vars(
3108 &json!({
3109 "batch": "ignored",
3110 }),
3111 &vars
3112 ),
3113 (Some(json!({ "batchId": 6 })), vec![]),
3114 );
3115
3116 assert_eq!(
3117 selection!("arrays.as.bs->echo({ echoed: @ })", spec).apply_to(&json!({
3118 "arrays": [
3119 { "as": { "bs": [10, 20, 30] } },
3120 { "as": { "bs": [40, 50, 60] } },
3121 { "as": { "bs": [70, 80, 90] } },
3122 ],
3123 })),
3124 (
3125 Some(json!({
3126 "echoed": [
3127 [10, 20, 30],
3128 [40, 50, 60],
3129 [70, 80, 90],
3130 ],
3131 })),
3132 vec![],
3133 ),
3134 );
3135
3136 assert_eq!(
3137 selection!("arrays.as.bs->echo({ echoed: @ })", spec).apply_to(&json!({
3138 "arrays": [
3139 { "as": { "bs": [10, 20, 30] } },
3140 { "as": [
3141 { "bs": [40, 50, 60] },
3142 { "bs": [70, 80, 90] },
3143 ] },
3144 { "as": { "bs": [100, 110, 120] } },
3145 ],
3146 })),
3147 (
3148 Some(json!({
3149 "echoed": [
3150 [10, 20, 30],
3151 [
3152 [40, 50, 60],
3153 [70, 80, 90],
3154 ],
3155 [100, 110, 120],
3156 ],
3157 })),
3158 vec![],
3159 ),
3160 );
3161
3162 assert_eq!(
3163 selection!("batch.id->jsonStringify", spec).apply_to(&json!({
3164 "batch": [
3165 { "id": 1 },
3166 { "id": 2 },
3167 { "id": 3 },
3168 ],
3169 })),
3170 (Some(json!("[1,2,3]")), vec![]),
3171 );
3172
3173 assert_eq!(
3174 selection!("batch.id->map([@])->echo([@])->jsonStringify", spec).apply_to(&json!({
3175 "batch": [
3176 { "id": 1 },
3177 { "id": 2 },
3178 { "id": 3 },
3179 ],
3180 })),
3181 (Some(json!("[[[1],[2],[3]]]")), vec![]),
3182 );
3183
3184 assert_eq!(
3185 selection!("batch.id->map([@])->echo([@])->jsonStringify->typeof", spec).apply_to(
3186 &json!({
3187 "batch": [
3188 { "id": 1 },
3189 { "id": 2 },
3190 { "id": 3 },
3191 ],
3192 })
3193 ),
3194 (Some(json!("string")), vec![]),
3195 );
3196 }
3197
3198 #[test]
3199 fn test_left_associative_output_shapes_v0_2() {
3200 let spec = ConnectSpec::V0_2;
3201
3202 assert_eq!(
3203 selection!("$batch.id", spec).shape().pretty_print(),
3204 "$batch.id"
3205 );
3206
3207 assert_eq!(
3208 selection!("$batch.id->first", spec).shape().pretty_print(),
3209 "Unknown",
3210 );
3211
3212 assert_eq!(
3213 selection!("$batch.id->last", spec).shape().pretty_print(),
3214 "Unknown",
3215 );
3216
3217 let mut named_shapes = IndexMap::default();
3218 named_shapes.insert(
3219 "$batch".to_string(),
3220 Shape::list(
3221 Shape::record(
3222 {
3223 let mut map = Shape::empty_map();
3224 map.insert("id".to_string(), Shape::int([]));
3225 map
3226 },
3227 [],
3228 ),
3229 [],
3230 ),
3231 );
3232
3233 let root_shape = Shape::name("$root", []);
3234 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
3235 .with_spec(spec)
3236 .with_named_shapes(named_shapes);
3237
3238 let computed_batch_id =
3239 selection!("$batch.id", spec).compute_output_shape(&shape_context, root_shape.clone());
3240 assert_eq!(computed_batch_id.pretty_print(), "List<Int>");
3241
3242 let computed_first = selection!("$batch.id->first", spec)
3243 .compute_output_shape(&shape_context, root_shape.clone());
3244 assert_eq!(computed_first.pretty_print(), "Unknown");
3245
3246 let computed_last = selection!("$batch.id->last", spec)
3247 .compute_output_shape(&shape_context, root_shape.clone());
3248 assert_eq!(computed_last.pretty_print(), "Unknown");
3249
3250 assert_eq!(
3251 selection!("$batch.id->jsonStringify", spec)
3252 .shape()
3253 .pretty_print(),
3254 "Unknown",
3255 );
3256
3257 assert_eq!(
3258 selection!("$batch.id->map([@])->echo([@])->jsonStringify", spec)
3259 .shape()
3260 .pretty_print(),
3261 "Unknown",
3262 );
3263
3264 assert_eq!(
3265 selection!("$batch.id->map(@)->echo(@)", spec)
3266 .shape()
3267 .pretty_print(),
3268 "Unknown",
3269 );
3270
3271 assert_eq!(
3272 selection!("$batch.id->map(@)->echo([@])", spec)
3273 .shape()
3274 .pretty_print(),
3275 "Unknown",
3276 );
3277
3278 assert_eq!(
3279 selection!("$batch.id->map([@])->echo(@)", spec)
3280 .shape()
3281 .pretty_print(),
3282 "Unknown",
3283 );
3284
3285 assert_eq!(
3286 selection!("$batch.id->map([@])->echo([@])", spec)
3287 .shape()
3288 .pretty_print(),
3289 "Unknown",
3290 );
3291
3292 assert_eq!(
3293 selection!("$batch.id->map([@])->echo([@])", spec)
3294 .compute_output_shape(&shape_context, root_shape,)
3295 .pretty_print(),
3296 "Unknown",
3297 );
3298 }
3299
3300 #[test]
3301 fn test_left_associative_output_shapes_v0_3() {
3302 let spec = ConnectSpec::V0_3;
3303
3304 assert_eq!(
3305 selection!("$batch.id", spec).shape().pretty_print(),
3306 "$batch.id"
3307 );
3308
3309 assert_eq!(
3310 selection!("$batch.id->first", spec).shape().pretty_print(),
3311 "$batch.id.0",
3312 );
3313
3314 assert_eq!(
3315 selection!("$batch.id->last", spec).shape().pretty_print(),
3316 "$batch.id.*",
3317 );
3318
3319 let mut named_shapes = IndexMap::default();
3320 named_shapes.insert(
3321 "$batch".to_string(),
3322 Shape::list(
3323 Shape::record(
3324 {
3325 let mut map = Shape::empty_map();
3326 map.insert("id".to_string(), Shape::int([]));
3327 map
3328 },
3329 [],
3330 ),
3331 [],
3332 ),
3333 );
3334
3335 let root_shape = Shape::name("$root", []);
3336 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
3337 .with_spec(spec)
3338 .with_named_shapes(named_shapes.clone());
3339
3340 let computed_batch_id =
3341 selection!("$batch.id", spec).compute_output_shape(&shape_context, root_shape.clone());
3342 assert_eq!(computed_batch_id.pretty_print(), "List<Int>");
3343
3344 let computed_first = selection!("$batch.id->first", spec)
3345 .compute_output_shape(&shape_context, root_shape.clone());
3346 assert_eq!(computed_first.pretty_print(), "One<Int, None>");
3347
3348 let computed_last = selection!("$batch.id->last", spec)
3349 .compute_output_shape(&shape_context, root_shape.clone());
3350 assert_eq!(computed_last.pretty_print(), "One<Int, None>");
3351
3352 assert_eq!(
3353 selection!("$batch.id->jsonStringify", spec)
3354 .shape()
3355 .pretty_print(),
3356 "String",
3357 );
3358
3359 assert_eq!(
3360 selection!("$batch.id->map([@])->echo([@])->jsonStringify", spec)
3361 .shape()
3362 .pretty_print(),
3363 "String",
3364 );
3365
3366 assert_eq!(
3367 selection!("$batch.id->map(@)->echo(@)", spec)
3368 .shape()
3369 .pretty_print(),
3370 "List<$batch.id.*>",
3371 );
3372
3373 assert_eq!(
3374 selection!("$batch.id->map(@)->echo([@])", spec)
3375 .shape()
3376 .pretty_print(),
3377 "[List<$batch.id.*>]",
3378 );
3379
3380 assert_eq!(
3381 selection!("$batch.id->map([@])->echo(@)", spec)
3382 .shape()
3383 .pretty_print(),
3384 "List<[$batch.id.*]>",
3385 );
3386
3387 assert_eq!(
3388 selection!("$batch.id->map([@])->echo([@])", spec)
3389 .shape()
3390 .pretty_print(),
3391 "[List<[$batch.id.*]>]",
3392 );
3393
3394 assert_eq!(
3395 selection!("$batch.id->map([@])->echo([@])", spec)
3396 .compute_output_shape(&shape_context, root_shape,)
3397 .pretty_print(),
3398 "[List<[Int]>]",
3399 );
3400 }
3401
3402 #[test]
3403 fn test_lit_paths() {
3404 let data = json!({
3405 "value": {
3406 "key": 123,
3407 },
3408 });
3409
3410 assert_eq!(
3411 selection!("$(\"a\")->first").apply_to(&data),
3412 (Some(json!("a")), vec![]),
3413 );
3414
3415 assert_eq!(
3416 selection!("$('asdf'->last)").apply_to(&data),
3417 (Some(json!("f")), vec![]),
3418 );
3419
3420 assert_eq!(
3421 selection!("$(1234)->add(1111)").apply_to(&data),
3422 (Some(json!(2345)), vec![]),
3423 );
3424
3425 assert_eq!(
3426 selection!("$(1234->add(1111))").apply_to(&data),
3427 (Some(json!(2345)), vec![]),
3428 );
3429
3430 assert_eq!(
3431 selection!("$(value.key->mul(10))").apply_to(&data),
3432 (Some(json!(1230)), vec![]),
3433 );
3434
3435 assert_eq!(
3436 selection!("$(value.key)->mul(10)").apply_to(&data),
3437 (Some(json!(1230)), vec![]),
3438 );
3439
3440 assert_eq!(
3441 selection!("$(value.key->typeof)").apply_to(&data),
3442 (Some(json!("number")), vec![]),
3443 );
3444
3445 assert_eq!(
3446 selection!("$(value.key)->typeof").apply_to(&data),
3447 (Some(json!("number")), vec![]),
3448 );
3449
3450 assert_eq!(
3451 selection!("$([1, 2, 3])->last").apply_to(&data),
3452 (Some(json!(3)), vec![]),
3453 );
3454
3455 assert_eq!(
3456 selection!("$([1, 2, 3]->first)").apply_to(&data),
3457 (Some(json!(1)), vec![]),
3458 );
3459
3460 assert_eq!(
3461 selection!("$({ a: 'ay', b: 1 }).a").apply_to(&data),
3462 (Some(json!("ay")), vec![]),
3463 );
3464
3465 assert_eq!(
3466 selection!("$({ a: 'ay', b: 2 }.a)").apply_to(&data),
3467 (Some(json!("ay")), vec![]),
3468 );
3469
3470 assert_eq!(
3471 selection!("$(-1->add(10))").apply_to(&data),
3475 (Some(json!(9)), vec![]),
3476 );
3477 }
3478
3479 #[test]
3480 fn test_compute_output_shape() {
3481 assert_eq!(selection!("").shape().pretty_print(), "{}");
3482
3483 assert_eq!(
3484 selection!("id name").shape().pretty_print(),
3485 "{ id: $root.*.id, name: $root.*.name }",
3486 );
3487
3488 assert_eq!(
3506 selection!(
3507 r#"
3508 id
3509 name
3510 friends: friend_ids { id: @ }
3511 alias: arrayOfArrays { x y }
3512 ys: arrayOfArrays.y xs: arrayOfArrays.x
3513 "#
3514 )
3515 .shape()
3516 .pretty_print(),
3517 "{ alias: { x: $root.*.arrayOfArrays.*.x, y: $root.*.arrayOfArrays.*.y }, friends: { id: $root.*.friend_ids.* }, id: $root.*.id, name: $root.*.name, xs: $root.*.arrayOfArrays.x, ys: $root.*.arrayOfArrays.y }",
3524 );
3525
3526 }
3573
3574 #[rstest]
3575 #[case::v0_3(ConnectSpec::V0_3)]
3576 fn test_optional_key_access_with_existing_property(#[case] spec: ConnectSpec) {
3577 use serde_json_bytes::json;
3578
3579 let data = json!({
3580 "user": {
3581 "profile": {
3582 "name": "Alice"
3583 }
3584 }
3585 });
3586
3587 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile.name", spec)
3588 .unwrap()
3589 .apply_to(&data);
3590 assert!(errors.is_empty());
3591 assert_eq!(result, Some(json!("Alice")));
3592 }
3593
3594 #[rstest]
3595 #[case::v0_3(ConnectSpec::V0_3)]
3596 fn test_optional_key_access_with_null_value(#[case] spec: ConnectSpec) {
3597 use serde_json_bytes::json;
3598
3599 let data_null = json!({
3600 "user": null
3601 });
3602
3603 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile.name", spec)
3604 .unwrap()
3605 .apply_to(&data_null);
3606 assert!(errors.is_empty());
3607 assert_eq!(result, None);
3608 }
3609
3610 #[rstest]
3611 #[case::v0_3(ConnectSpec::V0_3)]
3612 fn test_optional_key_access_on_non_object(#[case] spec: ConnectSpec) {
3613 use serde_json_bytes::json;
3614
3615 let data_non_obj = json!({
3616 "user": "not an object"
3617 });
3618
3619 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile.name", spec)
3620 .unwrap()
3621 .apply_to(&data_non_obj);
3622 assert_eq!(errors.len(), 1);
3623 assert!(
3624 errors[0]
3625 .message()
3626 .contains("Property .profile not found in string")
3627 );
3628 assert_eq!(result, None);
3629 }
3630
3631 #[rstest]
3632 #[case::v0_3(ConnectSpec::V0_3)]
3633 fn test_optional_key_access_with_missing_property(#[case] spec: ConnectSpec) {
3634 use serde_json_bytes::json;
3635
3636 let data = json!({
3637 "user": {
3638 "other": "value"
3639 }
3640 });
3641
3642 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile.name", spec)
3643 .unwrap()
3644 .apply_to(&data);
3645 assert_eq!(errors.len(), 1);
3646 assert!(
3647 errors[0]
3648 .message()
3649 .contains("Property .profile not found in object")
3650 );
3651 assert_eq!(result, None);
3652 }
3653
3654 #[rstest]
3655 #[case::v0_3(ConnectSpec::V0_3)]
3656 fn test_chained_optional_key_access(#[case] spec: ConnectSpec) {
3657 use serde_json_bytes::json;
3658
3659 let data = json!({
3660 "user": {
3661 "profile": {
3662 "name": "Alice"
3663 }
3664 }
3665 });
3666
3667 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile?.name", spec)
3668 .unwrap()
3669 .apply_to(&data);
3670 assert!(errors.is_empty());
3671 assert_eq!(result, Some(json!("Alice")));
3672 }
3673
3674 #[rstest]
3675 #[case::v0_3(ConnectSpec::V0_3)]
3676 fn test_chained_optional_access_with_null_in_middle(#[case] spec: ConnectSpec) {
3677 use serde_json_bytes::json;
3678
3679 let data_partial_null = json!({
3680 "user": {
3681 "profile": null
3682 }
3683 });
3684
3685 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile?.name", spec)
3686 .unwrap()
3687 .apply_to(&data_partial_null);
3688 assert!(errors.is_empty());
3689 assert_eq!(result, None);
3690 }
3691
3692 #[rstest]
3693 #[case::v0_3(ConnectSpec::V0_3)]
3694 fn test_optional_method_on_null(#[case] spec: ConnectSpec) {
3695 use serde_json_bytes::json;
3696
3697 let data = json!({
3698 "items": null
3699 });
3700
3701 let (result, errors) = JSONSelection::parse_with_spec("$.items?->first", spec)
3702 .unwrap()
3703 .apply_to(&data);
3704 assert!(errors.is_empty());
3705 assert_eq!(result, None);
3706 }
3707
3708 #[rstest]
3709 #[case::v0_3(ConnectSpec::V0_3)]
3710 fn test_optional_method_with_valid_method(#[case] spec: ConnectSpec) {
3711 use serde_json_bytes::json;
3712
3713 let data = json!({
3714 "values": [1, 2, 3]
3715 });
3716
3717 let (result, errors) = JSONSelection::parse_with_spec("$.values?->first", spec)
3718 .unwrap()
3719 .apply_to(&data);
3720 assert!(errors.is_empty());
3721 assert_eq!(result, Some(json!(1)));
3722 }
3723
3724 #[rstest]
3725 #[case::v0_3(ConnectSpec::V0_3)]
3726 fn test_optional_method_with_unknown_method(#[case] spec: ConnectSpec) {
3727 use serde_json_bytes::json;
3728
3729 let data = json!({
3730 "values": [1, 2, 3]
3731 });
3732
3733 let (result, errors) = JSONSelection::parse_with_spec("$.values?->length", spec)
3734 .unwrap()
3735 .apply_to(&data);
3736 assert_eq!(errors.len(), 1);
3737 assert!(errors[0].message().contains("Method ->length not found"));
3738 assert_eq!(result, None);
3739 }
3740
3741 #[rstest]
3742 #[case::v0_3(ConnectSpec::V0_3)]
3743 fn test_optional_chaining_with_subselection_on_valid_data(#[case] spec: ConnectSpec) {
3744 use serde_json_bytes::json;
3745
3746 let data = json!({
3747 "user": {
3748 "profile": {
3749 "name": "Alice",
3750 "age": 30,
3751 "email": "alice@example.com"
3752 }
3753 }
3754 });
3755
3756 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile { name age }", spec)
3757 .unwrap()
3758 .apply_to(&data);
3759 assert!(errors.is_empty());
3760 assert_eq!(
3761 result,
3762 Some(json!({
3763 "name": "Alice",
3764 "age": 30
3765 }))
3766 );
3767 }
3768
3769 #[rstest]
3770 #[case::v0_3(ConnectSpec::V0_3)]
3771 fn test_optional_chaining_with_subselection_on_null_data(#[case] spec: ConnectSpec) {
3772 use serde_json_bytes::json;
3773
3774 let data_null = json!({
3775 "user": null
3776 });
3777
3778 let (result, errors) = JSONSelection::parse_with_spec("$.user?.profile { name age }", spec)
3779 .unwrap()
3780 .apply_to(&data_null);
3781 assert!(errors.is_empty());
3782 assert_eq!(result, None);
3783 }
3784
3785 #[rstest]
3786 #[case::v0_3(ConnectSpec::V0_3)]
3787 fn test_mixed_regular_and_optional_chaining_working_case(#[case] spec: ConnectSpec) {
3788 use serde_json_bytes::json;
3789
3790 let data = json!({
3791 "response": {
3792 "data": {
3793 "user": {
3794 "profile": {
3795 "name": "Bob"
3796 }
3797 }
3798 }
3799 }
3800 });
3801
3802 let (result, errors) =
3803 JSONSelection::parse_with_spec("$.response.data?.user.profile.name", spec)
3804 .unwrap()
3805 .apply_to(&data);
3806 assert!(errors.is_empty());
3807 assert_eq!(result, Some(json!("Bob")));
3808 }
3809
3810 #[rstest]
3811 #[case::v0_3(ConnectSpec::V0_3)]
3812 fn test_mixed_regular_and_optional_chaining_with_null(#[case] spec: ConnectSpec) {
3813 use serde_json_bytes::json;
3814
3815 let data_null_data = json!({
3816 "response": {
3817 "data": null
3818 }
3819 });
3820
3821 let (result, errors) =
3822 JSONSelection::parse_with_spec("$.response.data?.user.profile.name", spec)
3823 .unwrap()
3824 .apply_to(&data_null_data);
3825 assert!(errors.is_empty());
3826 assert_eq!(result, None);
3827 }
3828
3829 #[rstest]
3830 #[case::v0_3(ConnectSpec::V0_3)]
3831 fn test_optional_selection_set_with_valid_data(#[case] spec: ConnectSpec) {
3832 use serde_json_bytes::json;
3833
3834 let data = json!({
3835 "user": {
3836 "id": 123,
3837 "name": "Alice"
3838 }
3839 });
3840
3841 let (result, errors) = JSONSelection::parse_with_spec("$.user ?{ id name }", spec)
3842 .unwrap()
3843 .apply_to(&data);
3844 assert_eq!(
3845 result,
3846 Some(json!({
3847 "id": 123,
3848 "name": "Alice"
3849 }))
3850 );
3851 assert_eq!(errors, vec![]);
3852 }
3853
3854 #[rstest]
3855 #[case::v0_3(ConnectSpec::V0_3)]
3856 fn test_optional_selection_set_with_null_data(#[case] spec: ConnectSpec) {
3857 use serde_json_bytes::json;
3858
3859 let data = json!({
3860 "user": null
3861 });
3862
3863 let (result, errors) = JSONSelection::parse_with_spec("$.user ?{ id name }", spec)
3864 .unwrap()
3865 .apply_to(&data);
3866 assert_eq!(result, None);
3867 assert_eq!(errors, vec![]);
3868 }
3869
3870 #[rstest]
3871 #[case::v0_3(ConnectSpec::V0_3)]
3872 fn test_optional_selection_set_with_missing_property(#[case] spec: ConnectSpec) {
3873 use serde_json_bytes::json;
3874
3875 let data = json!({
3876 "other": "value"
3877 });
3878
3879 let (result, errors) = JSONSelection::parse_with_spec("$.user ?{ id name }", spec)
3880 .unwrap()
3881 .apply_to(&data);
3882 assert_eq!(result, None);
3883 assert_eq!(errors.len(), 0);
3884 }
3885
3886 #[rstest]
3887 #[case::v0_3(ConnectSpec::V0_3)]
3888 fn test_optional_selection_set_with_non_object(#[case] spec: ConnectSpec) {
3889 use serde_json_bytes::json;
3890
3891 let data = json!({
3892 "user": "not an object"
3893 });
3894
3895 let (result, errors) = JSONSelection::parse_with_spec("$.user ?{ id name }", spec)
3896 .unwrap()
3897 .apply_to(&data);
3898 assert_eq!(result, Some(json!("not an object")));
3901 assert_eq!(errors.len(), 2);
3902 assert!(
3903 errors[0]
3904 .message()
3905 .contains("Property .id not found in string")
3906 );
3907 assert!(
3908 errors[1]
3909 .message()
3910 .contains("Property .name not found in string")
3911 );
3912 }
3913
3914 #[test]
3915 fn test_optional_field_selections() {
3916 let spec = ConnectSpec::V0_3;
3917 let author_selection = selection!("author? { age middleName? }", spec);
3918 assert_debug_snapshot!(author_selection);
3919 assert_eq!(
3920 author_selection.pretty_print(),
3921 "author? { age middleName? }",
3922 );
3923 assert_eq!(
3924 author_selection.shape().pretty_print(),
3925 "{ author: One<{ age: $root.*.author?.*.age, middleName: $root.*.author?.*.middleName? }, None> }",
3926 );
3927 }
3928
3929 #[test]
3930 fn test_optional_input_shape_with_selection() {
3931 let spec = ConnectSpec::V0_3;
3932 let optional_author_shape_selection =
3933 selection!("unreliableAuthor { age middleName? }", spec);
3934
3935 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
3936 .with_spec(spec)
3937 .with_named_shapes([(
3938 "$root".to_string(),
3939 Shape::record(
3940 {
3941 let mut map = Shape::empty_map();
3942 map.insert(
3943 "unreliableAuthor".to_string(),
3944 Shape::one(
3945 [
3946 Shape::record(
3947 {
3948 let mut map = Shape::empty_map();
3949 map.insert("age".to_string(), Shape::int([]));
3950 map.insert(
3951 "middleName".to_string(),
3952 Shape::one([Shape::string([]), Shape::none()], []),
3953 );
3954 map
3955 },
3956 [],
3957 ),
3958 Shape::none(),
3959 ],
3960 [],
3961 ),
3962 );
3963 map
3964 },
3965 [],
3966 ),
3967 )]);
3968
3969 assert_eq!(
3970 optional_author_shape_selection
3971 .compute_output_shape(
3972 &shape_context,
3973 shape_context.named_shapes().get("$root").unwrap().clone(),
3974 )
3975 .pretty_print(),
3976 "{ unreliableAuthor: One<{ age: Int, middleName: One<String, None> }, None> }",
3977 );
3978 }
3979
3980 #[rstest]
4046 #[case::v0_3(ConnectSpec::V0_3)]
4047 fn test_nested_optional_selection_sets(#[case] spec: ConnectSpec) {
4048 use serde_json_bytes::json;
4049
4050 let data = json!({
4051 "user": {
4052 "profile": {
4053 "name": "Alice",
4054 "email": "alice@example.com"
4055 }
4056 }
4057 });
4058
4059 let (result, errors) =
4060 JSONSelection::parse_with_spec("$.user.profile ?{ name email }", spec)
4061 .unwrap()
4062 .apply_to(&data);
4063 assert_eq!(
4064 result,
4065 Some(json!({
4066 "name": "Alice",
4067 "email": "alice@example.com"
4068 }))
4069 );
4070 assert_eq!(errors, vec![]);
4071
4072 let data_with_null_profile = json!({
4074 "user": {
4075 "profile": null
4076 }
4077 });
4078
4079 let (result, errors) =
4080 JSONSelection::parse_with_spec("$.user.profile ?{ name email }", spec)
4081 .unwrap()
4082 .apply_to(&data_with_null_profile);
4083 assert_eq!(result, None);
4084 assert_eq!(errors, vec![]);
4085 }
4086
4087 #[rstest]
4088 #[case::v0_3(ConnectSpec::V0_3)]
4089 fn test_mixed_optional_selection_and_optional_chaining(#[case] spec: ConnectSpec) {
4090 use serde_json_bytes::json;
4091
4092 let data = json!({
4093 "user": {
4094 "id": 123,
4095 "profile": null
4096 }
4097 });
4098
4099 let (result, errors) =
4100 JSONSelection::parse_with_spec("$.user ?{ id profileName: profile?.name }", spec)
4101 .unwrap()
4102 .apply_to(&data);
4103 assert_eq!(
4104 result,
4105 Some(json!({
4106 "id": 123
4107 }))
4108 );
4109 assert_eq!(errors, vec![]);
4110
4111 let data_no_user = json!({
4113 "other": "value"
4114 });
4115
4116 let (result, errors) =
4117 JSONSelection::parse_with_spec("$.user ?{ id profileName: profile?.name }", spec)
4118 .unwrap()
4119 .apply_to(&data_no_user);
4120 assert_eq!(result, None);
4121 assert_eq!(errors.len(), 0);
4122 }
4123
4124 #[rstest]
4125 #[case::v0_3(ConnectSpec::V0_3)]
4126 fn test_optional_selection_set_parsing(#[case] spec: ConnectSpec) {
4127 let selection = JSONSelection::parse_with_spec("$.user? { id name }", spec).unwrap();
4129 assert_eq!(selection.pretty_print(), "$.user? { id name }");
4130
4131 let selection = JSONSelection::parse_with_spec("$.user.profile? { name }", spec).unwrap();
4133 assert_eq!(selection.pretty_print(), "$.user.profile? { name }");
4134
4135 let selection =
4137 JSONSelection::parse_with_spec("$.user? { id profile { name } }", spec).unwrap();
4138 assert_eq!(selection.pretty_print(), "$.user? { id profile { name } }");
4139 }
4140
4141 #[rstest]
4142 #[case::v0_3(ConnectSpec::V0_3)]
4143 fn test_optional_selection_set_with_arrays(#[case] spec: ConnectSpec) {
4144 use serde_json_bytes::json;
4145
4146 let data = json!({
4147 "users": [
4148 {
4149 "id": 1,
4150 "name": "Alice"
4151 },
4152 null,
4153 {
4154 "id": 3,
4155 "name": "Charlie"
4156 }
4157 ]
4158 });
4159
4160 let (result, errors) = JSONSelection::parse_with_spec("$.users ?{ id name }", spec)
4161 .unwrap()
4162 .apply_to(&data);
4163 assert_eq!(
4164 result,
4165 Some(json!([
4166 {
4167 "id": 1,
4168 "name": "Alice"
4169 },
4170 null,
4171 {
4172 "id": 3,
4173 "name": "Charlie"
4174 }
4175 ]))
4176 );
4177
4178 assert_eq!(errors.len(), 2);
4179 assert!(
4180 errors[0]
4181 .message()
4182 .contains("Property .id not found in null")
4183 );
4184 assert!(
4185 errors[1]
4186 .message()
4187 .contains("Property .name not found in null")
4188 );
4189 }
4190
4191 #[test]
4696 fn null_coalescing_should_return_left_when_left_not_null() {
4697 let spec = ConnectSpec::V0_3;
4698 assert_eq!(
4699 selection!("$('Foo' ?? 'Bar')", spec).apply_to(&json!({})),
4700 (Some(json!("Foo")), vec![]),
4701 );
4702 }
4703
4704 #[test]
4705 fn null_coalescing_should_return_right_when_left_is_null() {
4706 let spec = ConnectSpec::V0_3;
4707 assert_eq!(
4708 selection!("$(null ?? 'Bar')", spec).apply_to(&json!({})),
4709 (Some(json!("Bar")), vec![]),
4710 );
4711 }
4712
4713 #[test]
4714 fn none_coalescing_should_return_left_when_left_not_none() {
4715 let spec = ConnectSpec::V0_3;
4716 assert_eq!(
4717 selection!("$('Foo' ?! 'Bar')", spec).apply_to(&json!({})),
4718 (Some(json!("Foo")), vec![]),
4719 );
4720 }
4721
4722 #[test]
4723 fn none_coalescing_should_preserve_null_when_left_is_null() {
4724 let spec = ConnectSpec::V0_3;
4725 assert_eq!(
4726 selection!("$(null ?! 'Bar')", spec).apply_to(&json!({})),
4727 (Some(json!(null)), vec![]),
4728 );
4729 }
4730
4731 #[test]
4732 fn nullish_coalescing_should_return_final_null() {
4733 let spec = ConnectSpec::V0_3;
4734 assert_eq!(
4735 selection!("$(missing ?? null)", spec).apply_to(&json!({})),
4736 (Some(json!(null)), vec![]),
4737 );
4738 assert_eq!(
4739 selection!("$(missing ?! null)", spec).apply_to(&json!({})),
4740 (Some(json!(null)), vec![]),
4741 );
4742 }
4743
4744 #[test]
4745 fn nullish_coalescing_should_return_final_none() {
4746 let spec = ConnectSpec::V0_3;
4747 assert_eq!(
4748 selection!("$(missing ?? also_missing)", spec).apply_to(&json!({})),
4749 (
4750 None,
4751 vec![
4752 ApplyToError::new(
4753 "Property .missing not found in object".to_string(),
4754 vec![json!("missing")],
4755 Some(2..9),
4756 spec,
4757 ),
4758 ApplyToError::new(
4759 "Property .also_missing not found in object".to_string(),
4760 vec![json!("also_missing")],
4761 Some(13..25),
4762 spec,
4763 ),
4764 ]
4765 ),
4766 );
4767 assert_eq!(
4768 selection!("maybe: $(missing ?! also_missing)", spec).apply_to(&json!({})),
4769 (
4770 Some(json!({})),
4771 vec![
4772 ApplyToError::new(
4773 "Property .missing not found in object".to_string(),
4774 vec![json!("missing")],
4775 Some(9..16),
4776 spec,
4777 ),
4778 ApplyToError::new(
4779 "Property .also_missing not found in object".to_string(),
4780 vec![json!("also_missing")],
4781 Some(20..32),
4782 spec,
4783 ),
4784 ]
4785 ),
4786 );
4787 }
4788
4789 #[test]
4790 fn coalescing_operators_should_return_earlier_values_if_later_missing() {
4791 let spec = ConnectSpec::V0_3;
4792 assert_eq!(
4793 selection!("$(1234 ?? missing)", spec).apply_to(&json!({})),
4794 (Some(json!(1234)), vec![]),
4795 );
4796 assert_eq!(
4797 selection!("$(item ?? missing)", spec).apply_to(&json!({ "item": 1234 })),
4798 (Some(json!(1234)), vec![]),
4799 );
4800 assert_eq!(
4801 selection!("$(item ?? missing)", spec).apply_to(&json!({ "item": null })),
4802 (
4803 None,
4804 vec![ApplyToError::new(
4805 "Property .missing not found in object".to_string(),
4806 vec![json!("missing")],
4807 Some(10..17),
4808 spec,
4809 )]
4810 ),
4811 );
4812 assert_eq!(
4813 selection!("$(null ?! missing)", spec).apply_to(&json!({})),
4814 (Some(json!(null)), vec![]),
4815 );
4816 assert_eq!(
4817 selection!("$(item ?! missing)", spec).apply_to(&json!({ "item": null })),
4818 (Some(json!(null)), vec![]),
4819 );
4820 }
4821
4822 #[test]
4823 fn null_coalescing_should_chain_left_to_right_when_multiple_nulls() {
4824 let spec = ConnectSpec::V0_3;
4826 assert_eq!(
4827 selection!("$(null ?? null ?? 'Bar')", spec).apply_to(&json!({})),
4828 (Some(json!("Bar")), vec![]),
4829 );
4830 }
4831
4832 #[test]
4833 fn null_coalescing_should_stop_at_first_non_null_when_chaining() {
4834 let spec = ConnectSpec::V0_3;
4835 assert_eq!(
4836 selection!("$('Foo' ?? null ?? 'Bar')", spec).apply_to(&json!({})),
4837 (Some(json!("Foo")), vec![]),
4838 );
4839 }
4840
4841 #[test]
4842 fn null_coalescing_should_fallback_when_field_is_null() {
4843 let spec = ConnectSpec::V0_3;
4844 let data = json!({"field1": null, "field2": "value2"});
4845 assert_eq!(
4846 selection!("$($.field1 ?? $.field2)", spec).apply_to(&data),
4847 (Some(json!("value2")), vec![]),
4848 );
4849 }
4850
4851 #[test]
4852 fn null_coalescing_should_use_literal_fallback_when_all_fields_null() {
4853 let spec = ConnectSpec::V0_3;
4854 let data = json!({"field1": null, "field3": null});
4855 assert_eq!(
4856 selection!("$($.field1 ?? $.field3 ?? 'fallback')", spec).apply_to(&data),
4857 (Some(json!("fallback")), vec![]),
4858 );
4859 }
4860
4861 #[test]
4862 fn none_coalescing_should_preserve_null_field() {
4863 let spec = ConnectSpec::V0_3;
4864 let data = json!({"nullField": null});
4865 assert_eq!(
4866 selection!("$($.nullField ?! 'fallback')", spec).apply_to(&data),
4867 (Some(json!(null)), vec![]),
4868 );
4869 }
4870
4871 #[test]
4872 fn none_coalescing_should_replace_missing_field() {
4873 let spec = ConnectSpec::V0_3;
4874 let data = json!({"nullField": null});
4875 assert_eq!(
4876 selection!("$($.missingField ?! 'fallback')", spec).apply_to(&data),
4877 (Some(json!("fallback")), vec![]),
4878 );
4879 }
4880
4881 #[test]
4882 fn null_coalescing_should_replace_null_field() {
4883 let spec = ConnectSpec::V0_3;
4884 let data = json!({"nullField": null});
4885 assert_eq!(
4886 selection!("$($.nullField ?? 'fallback')", spec).apply_to(&data),
4887 (Some(json!("fallback")), vec![]),
4888 );
4889 }
4890
4891 #[test]
4892 fn null_coalescing_should_replace_missing_field() {
4893 let spec = ConnectSpec::V0_3;
4894 let data = json!({"nullField": null});
4895 assert_eq!(
4896 selection!("$($.missingField ?? 'fallback')", spec).apply_to(&data),
4897 (Some(json!("fallback")), vec![]),
4898 );
4899 }
4900
4901 #[test]
4902 fn null_coalescing_should_preserve_number_type() {
4903 let spec = ConnectSpec::V0_3;
4904 assert_eq!(
4905 selection!("$(null ?? 42)", spec).apply_to(&json!({})),
4906 (Some(json!(42)), vec![]),
4907 );
4908 }
4909
4910 #[test]
4911 fn null_coalescing_should_preserve_boolean_type() {
4912 let spec = ConnectSpec::V0_3;
4913 assert_eq!(
4914 selection!("$(null ?? true)", spec).apply_to(&json!({})),
4915 (Some(json!(true)), vec![]),
4916 );
4917 }
4918
4919 #[test]
4920 fn null_coalescing_should_preserve_object_type() {
4921 let spec = ConnectSpec::V0_3;
4922 assert_eq!(
4923 selection!("$(null ?? {'key': 'value'})", spec).apply_to(&json!({})),
4924 (Some(json!({"key": "value"})), vec![]),
4925 );
4926 }
4927
4928 #[test]
4929 fn null_coalescing_should_preserve_array_type() {
4930 let spec = ConnectSpec::V0_3;
4931 assert_eq!(
4932 selection!("$(null ?? [1, 2, 3])", spec).apply_to(&json!({})),
4933 (Some(json!([1, 2, 3])), vec![]),
4934 );
4935 }
4936
4937 #[test]
4938 fn null_coalescing_should_fallback_when_null_used_as_method_arg() {
4939 let spec = ConnectSpec::V0_3;
4940 assert_eq!(
4941 selection!("$.a->add(b ?? c)", spec).apply_to(&json!({"a": 5, "b": null, "c": 5})),
4942 (Some(json!(10)), vec![]),
4943 );
4944 }
4945
4946 #[test]
4947 fn null_coalescing_should_fallback_when_none_used_as_method_arg() {
4948 let spec = ConnectSpec::V0_3;
4949 assert_eq!(
4950 selection!("$.a->add(missing ?? c)", spec)
4951 .apply_to(&json!({"a": 5, "b": null, "c": 5})),
4952 (Some(json!(10)), vec![]),
4953 );
4954 }
4955
4956 #[test]
4957 fn null_coalescing_should_not_fallback_when_not_null_used_as_method_arg() {
4958 let spec = ConnectSpec::V0_3;
4959 assert_eq!(
4960 selection!("$.a->add(b ?? c)", spec).apply_to(&json!({"a": 5, "b": 3, "c": 5})),
4961 (Some(json!(8)), vec![]),
4962 );
4963 }
4964
4965 #[test]
4966 fn null_coalescing_should_allow_multiple_method_args() {
4967 let spec = ConnectSpec::V0_3;
4968 let add_selection = selection!("a->add(b ?? c, missing ?! c)", spec);
4969 assert_eq!(
4970 add_selection.apply_to(&json!({ "a": 5, "b": 3, "c": 7 })),
4971 (Some(json!(15)), vec![]),
4972 );
4973 assert_eq!(
4974 add_selection.apply_to(&json!({ "a": 5, "b": null, "c": 7 })),
4975 (Some(json!(19)), vec![]),
4976 );
4977 }
4978
4979 #[test]
5065 fn wtf_operator_should_not_exclude_null_from_nullable_union_shape() {
5066 let spec = ConnectSpec::V0_3;
5067
5068 let nullish_selection = selection!("$($value ?? 'fallback')", spec);
5070 let wtf_selection = selection!("$($value ?! 'fallback')", spec);
5071
5072 let mut vars = IndexMap::default();
5073 vars.insert("$value".to_string(), json!(null));
5074
5075 assert_eq!(
5076 nullish_selection.apply_with_vars(&json!({}), &vars),
5077 (Some(json!("fallback")), vec![]),
5078 );
5079
5080 assert_eq!(
5081 wtf_selection.apply_with_vars(&json!({}), &vars),
5082 (Some(json!(null)), vec![]),
5083 );
5084
5085 let mut vars_with_string_value = IndexMap::default();
5086 vars_with_string_value.insert("$value".to_string(), json!("fine"));
5087
5088 assert_eq!(
5089 nullish_selection.apply_with_vars(&json!({}), &vars_with_string_value),
5090 (Some(json!("fine")), vec![]),
5091 );
5092
5093 assert_eq!(
5094 wtf_selection.apply_with_vars(&json!({}), &vars_with_string_value),
5095 (Some(json!("fine")), vec![]),
5096 );
5097
5098 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
5099 .with_spec(spec)
5100 .with_named_shapes([(
5101 "$value".to_string(),
5102 Shape::one([Shape::string([]), Shape::null([]), Shape::none()], []),
5103 )]);
5104
5105 assert_eq!(
5106 nullish_selection
5107 .compute_output_shape(&shape_context, Shape::none())
5110 .pretty_print(),
5111 "String",
5112 );
5113
5114 assert_eq!(
5115 wtf_selection
5116 .compute_output_shape(&shape_context, Shape::none())
5119 .pretty_print(),
5120 "One<String, null>",
5121 );
5122 }
5123
5124 #[test]
5125 fn question_operator_should_map_null_to_none() {
5126 let spec = ConnectSpec::V0_3;
5127
5128 let nullish_string_selection = selection!("$(stringOrNull?)", spec);
5129 assert_eq!(
5130 nullish_string_selection.apply_to(&json!({"stringOrNull": "a string"})),
5131 (Some(json!("a string")), vec![]),
5132 );
5133 assert_eq!(
5134 nullish_string_selection.apply_to(&json!({"stringOrNull": null})),
5135 (None, vec![]),
5136 );
5137 assert_eq!(
5138 nullish_string_selection.apply_to(&json!({})),
5139 (None, vec![]),
5140 );
5141
5142 let shape_context = {
5143 let mut named_shapes = IndexMap::default();
5144
5145 named_shapes.insert(
5146 "$root".to_string(),
5147 Shape::record(
5148 {
5149 let mut map = Shape::empty_map();
5150 map.insert(
5151 "stringOrNull".to_string(),
5152 Shape::one([Shape::string([]), Shape::null([])], []),
5153 );
5154 map
5155 },
5156 [],
5157 ),
5158 );
5159
5160 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5161 .with_spec(spec)
5162 .with_named_shapes(named_shapes)
5163 };
5164
5165 let root_shape = shape_context.named_shapes().get("$root").unwrap().clone();
5166
5167 assert_eq!(
5168 root_shape.pretty_print(),
5169 "{ stringOrNull: One<String, null> }",
5170 );
5171
5172 assert_eq!(
5173 nullish_string_selection
5174 .compute_output_shape(
5175 &shape_context,
5176 shape_context.named_shapes().get("$root").unwrap().clone(),
5177 )
5178 .pretty_print(),
5179 "One<String, None>",
5181 );
5182 }
5183
5184 #[test]
5185 fn question_operator_should_add_none_to_named_shapes() {
5186 let spec = ConnectSpec::V0_3;
5187
5188 let string_or_null_expr = selection!("$(stringOrNull?)", spec);
5189
5190 assert_eq!(
5191 string_or_null_expr.shape().pretty_print(),
5192 "$root.stringOrNull?",
5193 );
5194 }
5195
5196 #[test]
5197 fn question_operator_with_nested_objects() {
5198 let spec = ConnectSpec::V0_3;
5199
5200 let nested_selection = selection!("$(user?.profile?.name)", spec);
5201 assert_eq!(
5202 nested_selection.apply_to(&json!({"user": {"profile": {"name": "Alice"}}})),
5203 (Some(json!("Alice")), vec![]),
5204 );
5205 assert_eq!(
5206 nested_selection.apply_to(&json!({"user": null})),
5207 (None, vec![]),
5208 );
5209 assert_eq!(
5210 nested_selection.apply_to(&json!({"user": {"profile": null}})),
5211 (None, vec![]),
5212 );
5213 assert_eq!(nested_selection.apply_to(&json!({})), (None, vec![]));
5214 }
5215
5216 #[test]
5217 fn question_operator_with_array_access() {
5218 let spec = ConnectSpec::V0_3;
5219
5220 let array_selection = selection!("$(items?->first?.name)", spec);
5221 assert_eq!(
5222 array_selection.apply_to(&json!({"items": [{"name": "first"}]})),
5223 (Some(json!("first")), vec![]),
5224 );
5225 assert_eq!(
5226 array_selection.apply_to(&json!({"items": []})),
5227 (None, vec![]),
5228 );
5229 assert_eq!(
5230 array_selection.apply_to(&json!({"items": null})),
5231 (None, vec![]),
5232 );
5233 assert_eq!(array_selection.apply_to(&json!({})), (None, vec![]));
5234 }
5235
5236 #[test]
5237 fn question_operator_with_union_shapes() {
5238 let spec = ConnectSpec::V0_3;
5239
5240 let shape_context = {
5241 let mut named_shapes = IndexMap::default();
5242
5243 named_shapes.insert(
5244 "$root".to_string(),
5245 Shape::record(
5246 {
5247 let mut map = Shape::empty_map();
5248 map.insert(
5249 "unionField".to_string(),
5250 Shape::one([Shape::string([]), Shape::int([]), Shape::null([])], []),
5251 );
5252 map
5253 },
5254 [],
5255 ),
5256 );
5257
5258 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5259 .with_spec(spec)
5260 .with_named_shapes(named_shapes)
5261 };
5262
5263 let union_selection = selection!("$(unionField?)", spec);
5264
5265 assert_eq!(
5266 union_selection
5267 .compute_output_shape(
5268 &shape_context,
5269 shape_context.named_shapes().get("$root").unwrap().clone(),
5270 )
5271 .pretty_print(),
5272 "One<String, Int, None>",
5273 );
5274 }
5275
5276 #[test]
5277 fn question_operator_with_error_shapes() {
5278 let spec = ConnectSpec::V0_3;
5279
5280 let shape_context = {
5281 let mut named_shapes = IndexMap::default();
5282
5283 named_shapes.insert(
5284 "$root".to_string(),
5285 Shape::record(
5286 {
5287 let mut map = Shape::empty_map();
5288 map.insert(
5289 "errorField".to_string(),
5290 Shape::error_with_partial(
5291 "Test error".to_string(),
5292 Shape::one([Shape::string([]), Shape::null([])], []),
5293 [],
5294 ),
5295 );
5296 map
5297 },
5298 [],
5299 ),
5300 );
5301
5302 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5303 .with_spec(spec)
5304 .with_named_shapes(named_shapes)
5305 };
5306
5307 let error_selection = selection!("$(errorField?)", spec);
5308
5309 let result_shape = error_selection.compute_output_shape(
5310 &shape_context,
5311 shape_context.named_shapes().get("$root").unwrap().clone(),
5312 );
5313
5314 assert!(result_shape.pretty_print().contains("Error"));
5316 assert!(result_shape.pretty_print().contains("None"));
5317 }
5318
5319 #[test]
5320 fn question_operator_with_all_shapes() {
5321 let spec = ConnectSpec::V0_3;
5322
5323 let shape_context = {
5324 let mut named_shapes = IndexMap::default();
5325
5326 named_shapes.insert(
5327 "$root".to_string(),
5328 Shape::record(
5329 {
5330 let mut map = Shape::empty_map();
5331 map.insert(
5332 "allField".to_string(),
5333 Shape::all(
5334 [
5335 Shape::string([]),
5336 Shape::one([Shape::string([]), Shape::null([])], []),
5337 ],
5338 [],
5339 ),
5340 );
5341 map
5342 },
5343 [],
5344 ),
5345 );
5346
5347 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5348 .with_spec(spec)
5349 .with_named_shapes(named_shapes)
5350 };
5351
5352 let all_selection = selection!("$(allField?)", spec);
5353
5354 assert_eq!(
5355 all_selection
5356 .compute_output_shape(
5357 &shape_context,
5358 shape_context.named_shapes().get("$root").unwrap().clone(),
5359 )
5360 .pretty_print(),
5361 "One<String, None>",
5362 );
5363 }
5364
5365 #[test]
5366 fn question_operator_preserves_non_null_shapes() {
5367 let spec = ConnectSpec::V0_3;
5368
5369 let shape_context = {
5370 let mut named_shapes = IndexMap::default();
5371
5372 named_shapes.insert(
5373 "$root".to_string(),
5374 Shape::record(
5375 {
5376 let mut map = Shape::empty_map();
5377 map.insert("nonNullString".to_string(), Shape::string([]));
5378 map
5379 },
5380 [],
5381 ),
5382 );
5383
5384 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5385 .with_spec(spec)
5386 .with_named_shapes(named_shapes)
5387 };
5388
5389 let non_null_selection = selection!("$(nonNullString?)", spec);
5390
5391 assert_eq!(
5392 non_null_selection
5393 .compute_output_shape(
5394 &shape_context,
5395 shape_context.named_shapes().get("$root").unwrap().clone(),
5396 )
5397 .pretty_print(),
5398 "String",
5399 );
5400 }
5401
5402 #[test]
5403 fn question_operator_with_multiple_operators_in_chain() {
5404 let spec = ConnectSpec::V0_3;
5405
5406 let mixed_chain_selection = selection!("$(field? ?? 'default')", spec);
5408 assert_eq!(
5409 mixed_chain_selection.apply_to(&json!({"field": "value"})),
5410 (Some(json!("value")), vec![]),
5411 );
5412 assert_eq!(
5413 mixed_chain_selection.apply_to(&json!({"field": null})),
5414 (Some(json!("default")), vec![]),
5415 );
5416 assert_eq!(
5417 mixed_chain_selection.apply_to(&json!({})),
5418 (Some(json!("default")), vec![]),
5419 );
5420 }
5421
5422 #[test]
5423 fn question_operator_direct_null_input_shape() {
5424 let spec = ConnectSpec::V0_3;
5425
5426 let shape_context = {
5427 let mut named_shapes = IndexMap::default();
5428
5429 named_shapes.insert("$root".to_string(), Shape::null([]));
5430
5431 ShapeContext::new(SourceId::Other("JSONSelection".into()))
5432 .with_spec(spec)
5433 .with_named_shapes(named_shapes)
5434 };
5435
5436 let null_selection = selection!("$root?", spec);
5437
5438 assert_eq!(
5439 null_selection
5440 .compute_output_shape(
5441 &shape_context,
5442 shape_context.named_shapes().get("$root").unwrap().clone(),
5443 )
5444 .pretty_print(),
5445 "None",
5446 );
5447 }
5448
5449 #[test]
5450 fn test_unknown_name() {
5451 let spec = ConnectSpec::V0_3;
5452 let sel = selection!("book.author? { name age? }", spec);
5453 assert_eq!(
5454 sel.shape().pretty_print(),
5455 "One<{ age: $root.book.author?.*.age?, name: $root.book.author?.*.name }, None>",
5456 );
5457 }
5458
5459 #[test]
5460 fn test_nullish_coalescing_shape() {
5461 let spec = ConnectSpec::V0_3;
5462 let sel = selection!("$(a ?? b ?? c)", spec);
5463 assert_eq!(
5464 sel.shape().pretty_print(),
5465 "One<$root.a?!, $root.b?!, $root.c>",
5466 );
5467
5468 let mut named_shapes = IndexMap::default();
5469 named_shapes.insert(
5470 "$root".to_string(),
5471 Shape::record(
5472 {
5473 let mut map = Shape::empty_map();
5474 map.insert(
5475 "a".to_string(),
5476 Shape::one([Shape::string([]), Shape::null([])], []),
5477 );
5478 map.insert("b".to_string(), Shape::string([]));
5479 map.insert("c".to_string(), Shape::int([]));
5480 map
5481 },
5482 [],
5483 ),
5484 );
5485
5486 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
5487 .with_spec(spec)
5488 .with_named_shapes(named_shapes);
5489
5490 assert_eq!(
5491 sel.compute_output_shape(
5492 &shape_context,
5493 shape_context.named_shapes().get("$root").unwrap().clone(),
5494 )
5495 .pretty_print(),
5496 "One<String, Int>",
5497 );
5498 }
5499
5500 #[test]
5501 fn test_none_coalescing_shape() {
5502 let spec = ConnectSpec::V0_3;
5503 let sel = selection!("$(a ?! b ?! c)", spec);
5504 assert_eq!(
5505 sel.shape().pretty_print(),
5506 "One<$root.a!, $root.b!, $root.c>",
5507 );
5508
5509 let mut named_shapes = IndexMap::default();
5510 named_shapes.insert(
5511 "$root".to_string(),
5512 Shape::record(
5513 {
5514 let mut map = Shape::empty_map();
5515 map.insert(
5516 "a".to_string(),
5517 Shape::one([Shape::string([]), Shape::null([])], []),
5518 );
5519 map.insert(
5520 "b".to_string(),
5521 Shape::one([Shape::string([]), Shape::none()], []),
5522 );
5523 map.insert("c".to_string(), Shape::null([]));
5524 map
5525 },
5526 [],
5527 ),
5528 );
5529
5530 let shape_context = ShapeContext::new(SourceId::Other("JSONSelection".into()))
5531 .with_spec(spec)
5532 .with_named_shapes(named_shapes);
5533
5534 assert_eq!(
5535 sel.compute_output_shape(
5536 &shape_context,
5537 shape_context.named_shapes().get("$root").unwrap().clone(),
5538 )
5539 .pretty_print(),
5540 "One<String, null>",
5541 );
5542 }
5543}