stepflow_flow/values/
json_path.rs1use serde::de::{self, Visitor};
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15use std::fmt;
16
17#[derive(Debug, Clone, PartialEq, Hash, Eq, Ord, PartialOrd)]
19pub enum PathPart {
20 Field(String),
22 Index(usize),
24 IndexStr(String),
26}
27
28#[derive(Debug, Clone, PartialEq, Hash, Eq)]
32pub struct JsonPath(Vec<PathPart>);
33
34impl JsonPath {
35 pub fn new() -> Self {
37 Self(Vec::new())
38 }
39
40 pub fn from_parts(parts: Vec<PathPart>) -> Self {
42 Self(parts)
43 }
44
45 pub fn parts(&self) -> &[PathPart] {
47 &self.0
48 }
49
50 pub fn is_empty(&self) -> bool {
52 self.0.is_empty()
53 }
54
55 pub fn outer_field(&self) -> Option<&str> {
56 match self.0.first() {
57 Some(PathPart::Field(name)) => Some(name),
58 Some(PathPart::IndexStr(name)) => Some(name),
59 _ => None,
60 }
61 }
62
63 pub fn parse(s: &str) -> Result<Self, String> {
65 if s.is_empty() {
66 return Ok(Self::new());
67 }
68
69 let s = match s.trim().strip_prefix("$") {
70 Some(s) => s,
71 None => {
72 return Ok(Self::from_parts(vec![PathPart::Field(s.to_string())]));
74 }
75 };
76
77 let mut parts = Vec::new();
78 let mut chars = s.chars().peekable();
79
80 while let Some(ch) = chars.next() {
81 match ch {
82 '[' => {
83 let mut bracket_content = String::new();
84 let mut found_end = false;
85
86 for ch in chars.by_ref() {
87 if ch == ']' {
88 found_end = true;
89 break;
90 }
91 bracket_content.push(ch);
92 }
93
94 if !found_end {
95 return Err("Unclosed bracket '[' in path".to_string());
96 }
97
98 let bracket_content = bracket_content.trim();
99
100 if (bracket_content.starts_with('"') && bracket_content.ends_with('"'))
102 || (bracket_content.starts_with('\'') && bracket_content.ends_with('\''))
103 {
104 let field_name = &bracket_content[1..bracket_content.len() - 1];
105 parts.push(PathPart::Field(field_name.to_string()));
106 } else if let Ok(index) = bracket_content.parse::<usize>() {
107 parts.push(PathPart::Index(index));
108 } else {
109 return Err(format!(
110 "Invalid index '{bracket_content}' in JSON path. Expected a number or a quoted string."
111 ));
112 }
113 }
114 '.' => {
115 let mut field_name = String::new();
116
117 while let Some(&ch) = chars.peek() {
118 if ch == '[' || ch == '.' {
119 break;
120 }
121 field_name.push(chars.next().unwrap());
122 }
123
124 if !field_name.is_empty() {
125 parts.push(PathPart::Field(field_name));
126 }
127 }
128 _ => {
129 return Err(format!(
130 "Invalid character '{ch}' in JSON path after $ or ']'. Expected '.' or '['"
131 ));
132 }
133 }
134 }
135
136 Ok(Self::from_parts(parts))
137 }
138
139 fn as_string(&self) -> String {
141 let mut result = String::from("$");
142
143 for part in &self.0 {
144 match part {
145 PathPart::Field(name) => {
146 result.push_str(&format!(".{name}"));
147 }
148 PathPart::Index(idx) => {
149 result.push_str(&format!("[{idx}]"));
150 }
151 PathPart::IndexStr(s) => {
152 result.push_str(&format!("[\"{s}\"]"));
153 }
154 }
155 }
156
157 result
158 }
159}
160
161impl Default for JsonPath {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167impl fmt::Display for JsonPath {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "{}", self.as_string())
170 }
171}
172
173impl Serialize for JsonPath {
174 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175 where
176 S: Serializer,
177 {
178 self.to_string().serialize(serializer)
179 }
180}
181
182impl<'de> Deserialize<'de> for JsonPath {
183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184 where
185 D: Deserializer<'de>,
186 {
187 struct JsonPathVisitor;
188
189 impl<'de> Visitor<'de> for JsonPathVisitor {
190 type Value = JsonPath;
191
192 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
193 formatter.write_str("a JSON path string")
194 }
195
196 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
197 where
198 E: de::Error,
199 {
200 JsonPath::parse(value).map_err(de::Error::custom)
201 }
202
203 fn visit_none<E>(self) -> Result<Self::Value, E>
204 where
205 E: de::Error,
206 {
207 Ok(JsonPath::new())
208 }
209
210 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
211 where
212 D: Deserializer<'de>,
213 {
214 deserializer.deserialize_str(self)
215 }
216 }
217
218 deserializer.deserialize_option(JsonPathVisitor)
219 }
220}
221
222impl schemars::JsonSchema for JsonPath {
223 fn schema_name() -> std::borrow::Cow<'static, str> {
224 "JsonPath".into()
225 }
226
227 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
228 schemars::json_schema!({
229 "type": "string",
230 "description": "JSON path expression to apply to the referenced value."
231 })
232 }
233}
234
235impl From<String> for JsonPath {
236 fn from(s: String) -> Self {
237 Self::parse(&s).unwrap_or_default()
238 }
239}
240
241impl From<&str> for JsonPath {
242 fn from(s: &str) -> Self {
243 Self::parse(s).unwrap_or_default()
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_json_path_parsing() {
253 let path = JsonPath::parse("field").unwrap();
255 assert_eq!(path.parts(), &[PathPart::Field("field".to_string())]);
256 assert_eq!(path.to_string(), "$.field");
257
258 let path = JsonPath::parse("$[\"field\"]").unwrap();
260 assert_eq!(path.parts(), &[PathPart::Field("field".to_string())]);
261 assert_eq!(path.to_string(), "$.field");
262
263 let path = JsonPath::parse("$.field").unwrap();
265 assert_eq!(path.parts(), &[PathPart::Field("field".to_string())]);
266 assert_eq!(path.to_string(), "$.field");
267
268 let path = JsonPath::parse("$[0]").unwrap();
270 assert_eq!(path.parts(), &[PathPart::Index(0)]);
271 assert_eq!(path.to_string(), "$[0]");
272
273 let path = JsonPath::parse("$.field[0].nested").unwrap();
275 assert_eq!(
276 path.parts(),
277 &[
278 PathPart::Field("field".to_string()),
279 PathPart::Index(0),
280 PathPart::Field("nested".to_string())
281 ]
282 );
283 assert_eq!(path.to_string(), "$.field[0].nested");
284
285 let path = JsonPath::parse("").unwrap();
287 assert!(path.is_empty());
288 assert_eq!(path.to_string(), "$");
289 }
290 #[test]
291
292 fn test_json_path_invalid_syntax() {
293 assert_eq!(
295 JsonPath::parse("$field").unwrap_err(),
296 "Invalid character 'f' in JSON path after $ or ']'. Expected '.' or '['"
297 );
298
299 assert_eq!(
301 JsonPath::parse("$[0]field").unwrap_err(),
302 "Invalid character 'f' in JSON path after $ or ']'. Expected '.' or '['"
303 );
304
305 assert_eq!(
307 JsonPath::parse("$[\"hello").unwrap_err(),
308 "Unclosed bracket '[' in path"
309 );
310 assert_eq!(
312 JsonPath::parse("$[\"hello\"").unwrap_err(),
313 "Unclosed bracket '[' in path"
314 );
315 assert_eq!(
317 JsonPath::parse("$[5").unwrap_err(),
318 "Unclosed bracket '[' in path"
319 );
320 assert_eq!(
322 JsonPath::parse("$[hello]").unwrap_err(),
323 "Invalid index 'hello' in JSON path. Expected a number or a quoted string."
324 );
325 }
326
327 #[test]
328 fn test_json_path_serialization() {
329 let path = JsonPath::from("field");
331 let serialized = serde_json::to_string(&path).unwrap();
332 assert_eq!(serialized, "\"$.field\"");
333
334 let path = JsonPath::parse("$.field[0]").unwrap();
336 let serialized = serde_json::to_string(&path).unwrap();
337 assert_eq!(serialized, "\"$.field[0]\"");
338
339 let original = JsonPath::parse("$.items[0].name").unwrap();
341 let serialized = serde_json::to_string(&original).unwrap();
342 let deserialized: JsonPath = serde_json::from_str(&serialized).unwrap();
343 assert_eq!(original, deserialized);
344 }
345
346 #[test]
347 fn test_json_path_deserialization() {
348 let deserialized: JsonPath = serde_json::from_str(&"\"$\"").unwrap();
349 assert_eq!(deserialized, JsonPath::parse("$").unwrap());
350
351 let deserialized: JsonPath = serde_json::from_str(&"null").unwrap();
352 assert_eq!(deserialized, JsonPath::new());
353 }
354}