velesdb_core/velesql/
json_path.rs1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Clone, PartialEq)]
11#[non_exhaustive]
12pub enum JsonPathError {
13 EmptyPath,
15 InvalidArrayIndex(String),
17 UnclosedBracket,
19 EmptySegment,
21}
22
23impl std::fmt::Display for JsonPathError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Self::EmptyPath => write!(f, "Empty JSON path"),
27 Self::InvalidArrayIndex(s) => write!(f, "Invalid array index: '{s}'"),
28 Self::UnclosedBracket => write!(f, "Unclosed bracket in JSON path"),
29 Self::EmptySegment => write!(f, "Empty segment in JSON path (double dot)"),
30 }
31 }
32}
33
34impl std::error::Error for JsonPathError {}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
38#[non_exhaustive]
39pub enum PathSegment {
40 Property(String),
42 Index(usize),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub struct JsonPath {
61 pub segments: Vec<PathSegment>,
63}
64
65impl JsonPath {
66 #[must_use]
68 pub fn new() -> Self {
69 Self {
70 segments: Vec::new(),
71 }
72 }
73
74 #[must_use]
76 pub fn from_property(name: &str) -> Self {
77 Self {
78 segments: vec![PathSegment::Property(name.to_string())],
79 }
80 }
81
82 pub fn parse(input: &str) -> Result<Self, JsonPathError> {
88 let input = input.trim();
89 if input.is_empty() {
90 return Err(JsonPathError::EmptyPath);
91 }
92
93 let mut segments = Vec::new();
94 let mut current = String::new();
95 let mut chars = input.chars().peekable();
96 let mut last_was_index = false;
97
98 while let Some(c) = chars.next() {
99 match c {
100 '.' => Self::handle_dot(&mut segments, &mut current, &mut last_was_index)?,
101 '[' => {
102 Self::flush_property(&mut segments, &mut current);
103 let index = Self::parse_bracket_index(&mut chars)?;
104 segments.push(PathSegment::Index(index));
105 last_was_index = true;
106 }
107 _ => {
108 current.push(c);
109 last_was_index = false;
110 }
111 }
112 }
113
114 if !current.is_empty() {
115 segments.push(PathSegment::Property(current));
116 }
117
118 if segments.is_empty() {
119 return Err(JsonPathError::EmptyPath);
120 }
121
122 Ok(JsonPath { segments })
123 }
124
125 fn flush_property(segments: &mut Vec<PathSegment>, current: &mut String) {
127 if !current.is_empty() {
128 segments.push(PathSegment::Property(std::mem::take(current)));
129 }
130 }
131
132 fn handle_dot(
134 segments: &mut Vec<PathSegment>,
135 current: &mut String,
136 last_was_index: &mut bool,
137 ) -> Result<(), JsonPathError> {
138 if current.is_empty() && !*last_was_index && !segments.is_empty() {
140 return Err(JsonPathError::EmptySegment);
141 }
142 Self::flush_property(segments, current);
143 *last_was_index = false;
144 Ok(())
145 }
146
147 fn parse_bracket_index(
149 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
150 ) -> Result<usize, JsonPathError> {
151 let mut idx_str = String::new();
152 let mut closed = false;
153 for ch in chars.by_ref() {
154 if ch == ']' {
155 closed = true;
156 break;
157 }
158 idx_str.push(ch);
159 }
160 if !closed {
161 return Err(JsonPathError::UnclosedBracket);
162 }
163 idx_str
164 .trim()
165 .parse()
166 .map_err(|_| JsonPathError::InvalidArrayIndex(idx_str))
167 }
168
169 #[must_use]
171 pub fn is_simple(&self) -> bool {
172 self.segments.len() == 1 && matches!(self.segments.first(), Some(PathSegment::Property(_)))
173 }
174
175 #[must_use]
177 pub fn root_property(&self) -> Option<&str> {
178 match self.segments.first() {
179 Some(PathSegment::Property(name)) => Some(name),
180 _ => None,
181 }
182 }
183
184 #[must_use]
186 pub fn tail(&self) -> Self {
187 Self {
188 segments: self.segments.iter().skip(1).cloned().collect(),
189 }
190 }
191
192 #[must_use]
196 pub fn extract<'a>(&self, doc: &'a Value) -> Option<&'a Value> {
197 let mut current = doc;
198
199 for segment in &self.segments {
200 current = match segment {
201 PathSegment::Property(key) => current.get(key)?,
202 PathSegment::Index(idx) => current.get(idx)?,
203 };
204 }
205
206 Some(current)
207 }
208
209 #[must_use]
211 pub fn extract_or_null(&self, doc: &Value) -> Value {
212 self.extract(doc).cloned().unwrap_or(Value::Null)
213 }
214}
215
216impl Default for JsonPath {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222impl std::fmt::Display for JsonPath {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 let mut first = true;
225 for segment in &self.segments {
226 match segment {
227 PathSegment::Property(name) => {
228 if first {
229 write!(f, "{name}")?;
230 } else {
231 write!(f, ".{name}")?;
232 }
233 }
234 PathSegment::Index(idx) => {
235 write!(f, "[{idx}]")?;
236 }
237 }
238 first = false;
239 }
240 Ok(())
241 }
242}
243
244