apollo_federation/connectors/json_selection/methods/public/
slice.rs1use std::iter::empty;
2
3use serde_json_bytes::Value as JSON;
4use shape::Shape;
5use shape::ShapeCase;
6
7use crate::connectors::json_selection::ApplyToError;
8use crate::connectors::json_selection::ApplyToInternal;
9use crate::connectors::json_selection::MethodArgs;
10use crate::connectors::json_selection::ShapeContext;
11use crate::connectors::json_selection::VarsWithPathsMap;
12use crate::connectors::json_selection::immutable::InputPath;
13use crate::connectors::json_selection::location::Ranged;
14use crate::connectors::json_selection::location::WithRange;
15use crate::connectors::spec::ConnectSpec;
16use crate::impl_arrow_method;
17
18impl_arrow_method!(SliceMethod, slice_method, slice_shape);
19fn slice_method(
26 method_name: &WithRange<String>,
27 method_args: Option<&MethodArgs>,
28 data: &JSON,
29 vars: &VarsWithPathsMap,
30 input_path: &InputPath<JSON>,
31 spec: ConnectSpec,
32) -> (Option<JSON>, Vec<ApplyToError>) {
33 let length = if let JSON::Array(array) = data {
34 array.len() as i64
35 } else if let JSON::String(s) = data {
36 s.as_str().len() as i64
37 } else {
38 return (
39 None,
40 vec![ApplyToError::new(
41 format!(
42 "Method ->{} requires an array or string input",
43 method_name.as_ref()
44 ),
45 input_path.to_vec(),
46 method_name.range(),
47 spec,
48 )],
49 );
50 };
51
52 if let Some(MethodArgs { args, .. }) = method_args {
53 let mut errors = Vec::new();
54
55 let start = args
56 .first()
57 .and_then(|arg| {
58 let (value_opt, apply_errors) = arg.apply_to_path(data, vars, input_path, spec);
59 errors.extend(apply_errors);
60 value_opt
61 })
62 .and_then(|n| n.as_i64())
63 .unwrap_or(0)
64 .max(0)
65 .min(length) as usize;
66
67 let end = args
68 .get(1)
69 .and_then(|arg| {
70 let (value_opt, apply_errors) = arg.apply_to_path(data, vars, input_path, spec);
71 errors.extend(apply_errors);
72 value_opt
73 })
74 .and_then(|n| n.as_i64())
75 .unwrap_or(length)
76 .max(0)
77 .min(length) as usize;
78
79 let array = match data {
80 JSON::Array(array) => {
81 if end - start > 0 {
82 JSON::Array(
83 array
84 .iter()
85 .skip(start)
86 .take(end - start)
87 .cloned()
88 .collect(),
89 )
90 } else {
91 JSON::Array(Vec::new())
92 }
93 }
94
95 JSON::String(s) => {
96 if end - start > 0 {
97 JSON::String(s.as_str()[start..end].to_string().into())
98 } else {
99 JSON::String("".to_string().into())
100 }
101 }
102
103 _ => unreachable!(),
104 };
105
106 (Some(array), errors)
107 } else {
108 (Some(data.clone()), Vec::new())
112 }
113}
114#[allow(dead_code)] fn slice_shape(
116 context: &ShapeContext,
117 method_name: &WithRange<String>,
118 _method_args: Option<&MethodArgs>,
119 input_shape: Shape,
120 _dollar_shape: Shape,
121) -> Shape {
122 match input_shape.case() {
128 ShapeCase::Array { prefix, tail } => {
129 let mut one_shapes = prefix.clone();
130 if !tail.is_none() {
131 one_shapes.push(tail.clone());
132 }
133 Shape::array(
134 [],
135 Shape::one(one_shapes, empty()),
136 input_shape.locations().cloned(),
137 )
138 }
139 ShapeCase::String(_) => Shape::string(input_shape.locations().cloned()),
140 ShapeCase::Name(_, _) => input_shape, ShapeCase::Unknown => Shape::unknown(input_shape.locations().cloned()),
142 _ => Shape::error(
143 format!(
144 "Method ->{} requires an array or string input",
145 method_name.as_ref()
146 ),
147 input_shape
148 .locations()
149 .cloned()
150 .chain(method_name.shape_location(context.source_id())),
151 ),
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use serde_json_bytes::json;
158
159 use crate::selection;
160
161 #[test]
162 fn slice_should_grab_parts_of_array_by_specified_indices() {
163 assert_eq!(
164 selection!("$->slice(1, 3)").apply_to(&json!([1, 2, 3, 4, 5])),
165 (Some(json!([2, 3])), vec![]),
166 );
167 }
168
169 #[test]
170 fn slice_should_stop_at_end_when_array_is_shorter_than_specified_end_index() {
171 assert_eq!(
172 selection!("$->slice(1, 3)").apply_to(&json!([1, 2])),
173 (Some(json!([2])), vec![]),
174 );
175 }
176
177 #[test]
178 fn slice_should_return_empty_array_when_array_is_shorter_than_specified_indices() {
179 assert_eq!(
180 selection!("$->slice(1, 3)").apply_to(&json!([1])),
181 (Some(json!([])), vec![]),
182 );
183 }
184
185 #[test]
186 fn slice_should_return_empty_array_when_provided_empty_array() {
187 assert_eq!(
188 selection!("$->slice(1, 3)").apply_to(&json!([])),
189 (Some(json!([])), vec![]),
190 );
191 }
192
193 #[test]
194 fn slice_should_return_blank_when_string_is_empty() {
195 assert_eq!(
196 selection!("$->slice(1, 3)").apply_to(&json!("")),
197 (Some(json!("")), vec![]),
198 );
199 }
200
201 #[test]
202 fn slice_should_return_part_of_string() {
203 assert_eq!(
204 selection!("$->slice(1, 3)").apply_to(&json!("hello")),
205 (Some(json!("el")), vec![]),
206 );
207 }
208
209 #[test]
210 fn slice_should_return_part_of_string_when_slice_indices_are_larger_than_string() {
211 assert_eq!(
212 selection!("$->slice(1, 3)").apply_to(&json!("he")),
213 (Some(json!("e")), vec![]),
214 );
215 }
216
217 #[test]
218 fn slice_should_return_empty_string_when_indices_are_completely_out_of_string_bounds() {
219 assert_eq!(
220 selection!("$->slice(1, 3)").apply_to(&json!("h")),
221 (Some(json!("")), vec![]),
222 );
223 }
224}