1use std::collections::HashMap;
7
8use crate::types::JsonLocation;
9
10#[derive(Debug, Clone, Default)]
15pub struct JsonSourceLocator {
16 locations: HashMap<String, JsonLocation>,
18}
19
20impl JsonSourceLocator {
21 #[must_use]
23 pub fn new(json_text: &str) -> Self {
24 let mut locator = Self {
25 locations: HashMap::new(),
26 };
27 locator.parse(json_text);
28 locator
29 }
30
31 fn parse(&mut self, text: &str) {
33 let mut line = 1usize;
34 let mut column = 1usize;
35 let mut path_stack: Vec<PathSegment> = Vec::new();
36 let chars: Vec<char> = text.chars().collect();
37 let len = chars.len();
38 let mut i = 0;
39
40 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
42 if i < len {
43 self.locations.insert(String::new(), JsonLocation::new(line, column));
44 }
45
46 while i < len {
47 let ch = chars[i];
48
49 match ch {
50 '{' => {
51 i += 1;
53 column += 1;
54 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
55
56 while i < len && chars[i] != '}' {
58 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
59 if i >= len || chars[i] == '}' {
60 break;
61 }
62
63 if chars[i] == '\"' {
65 let _key_line = line;
66 let _key_column = column;
67 let key = self.parse_string(&chars, &mut i, &mut line, &mut column);
68
69 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
71 if i < len && chars[i] == ':' {
72 i += 1;
73 column += 1;
74 }
75
76 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
78 path_stack.push(PathSegment::Property(key.clone()));
79 let path = self.build_path(&path_stack);
80 self.locations.insert(path, JsonLocation::new(line, column));
81
82 self.skip_value(&chars, &mut i, &mut line, &mut column, &mut path_stack);
84 path_stack.pop();
85
86 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
88 if i < len && chars[i] == ',' {
89 i += 1;
90 column += 1;
91 }
92 } else {
93 i += 1;
95 column += 1;
96 }
97 }
98
99 if i < len && chars[i] == '}' {
101 i += 1;
102 column += 1;
103 }
104 }
105 '[' => {
106 i += 1;
108 column += 1;
109 let mut index = 0usize;
110
111 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
112
113 while i < len && chars[i] != ']' {
114 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
115 if i >= len || chars[i] == ']' {
116 break;
117 }
118
119 path_stack.push(PathSegment::Index(index));
121 let path = self.build_path(&path_stack);
122 self.locations.insert(path, JsonLocation::new(line, column));
123
124 self.skip_value(&chars, &mut i, &mut line, &mut column, &mut path_stack);
126 path_stack.pop();
127
128 index += 1;
129
130 self.skip_whitespace(&chars, &mut i, &mut line, &mut column);
132 if i < len && chars[i] == ',' {
133 i += 1;
134 column += 1;
135 }
136 }
137
138 if i < len && chars[i] == ']' {
140 i += 1;
141 column += 1;
142 }
143 }
144 '"' => {
145 self.parse_string(&chars, &mut i, &mut line, &mut column);
146 }
147 '\n' => {
148 i += 1;
149 line += 1;
150 column = 1;
151 }
152 _ => {
153 i += 1;
155 column += 1;
156 }
157 }
158 }
159 }
160
161 fn skip_whitespace(&self, chars: &[char], i: &mut usize, line: &mut usize, column: &mut usize) {
163 while *i < chars.len() {
164 match chars[*i] {
165 ' ' | '\t' | '\r' => {
166 *i += 1;
167 *column += 1;
168 }
169 '\n' => {
170 *i += 1;
171 *line += 1;
172 *column = 1;
173 }
174 _ => break,
175 }
176 }
177 }
178
179 fn parse_string(&self, chars: &[char], i: &mut usize, line: &mut usize, column: &mut usize) -> String {
181 let mut result = String::new();
182
183 if *i < chars.len() && chars[*i] == '"' {
185 *i += 1;
186 *column += 1;
187 }
188
189 while *i < chars.len() {
190 let ch = chars[*i];
191
192 if ch == '"' {
193 *i += 1;
195 *column += 1;
196 break;
197 } else if ch == '\\' && *i + 1 < chars.len() {
198 *i += 1;
200 *column += 1;
201 let escaped = chars[*i];
202 *i += 1;
203 *column += 1;
204
205 match escaped {
206 'n' => result.push('\n'),
207 'r' => result.push('\r'),
208 't' => result.push('\t'),
209 '\\' => result.push('\\'),
210 '"' => result.push('"'),
211 '/' => result.push('/'),
212 'u' => {
213 for _ in 0..4 {
215 if *i < chars.len() {
216 *i += 1;
217 *column += 1;
218 }
219 }
220 }
221 _ => result.push(escaped),
222 }
223 } else if ch == '\n' {
224 *i += 1;
225 *line += 1;
226 *column = 1;
227 } else {
228 result.push(ch);
229 *i += 1;
230 *column += 1;
231 }
232 }
233
234 result
235 }
236
237 fn skip_value(
239 &mut self,
240 chars: &[char],
241 i: &mut usize,
242 line: &mut usize,
243 column: &mut usize,
244 path_stack: &mut Vec<PathSegment>,
245 ) {
246 self.skip_whitespace(chars, i, line, column);
247
248 if *i >= chars.len() {
249 return;
250 }
251
252 match chars[*i] {
253 '{' => {
254 *i += 1;
255 *column += 1;
256 self.skip_whitespace(chars, i, line, column);
257
258 while *i < chars.len() && chars[*i] != '}' {
259 self.skip_whitespace(chars, i, line, column);
260 if *i >= chars.len() || chars[*i] == '}' {
261 break;
262 }
263
264 if chars[*i] == '"' {
266 let key = self.parse_string(chars, i, line, column);
267
268 self.skip_whitespace(chars, i, line, column);
270 if *i < chars.len() && chars[*i] == ':' {
271 *i += 1;
272 *column += 1;
273 }
274
275 self.skip_whitespace(chars, i, line, column);
277 path_stack.push(PathSegment::Property(key.clone()));
278 let path = self.build_path(path_stack);
279 self.locations.insert(path, JsonLocation::new(*line, *column));
280 self.skip_value(chars, i, line, column, path_stack);
281 path_stack.pop();
282
283 self.skip_whitespace(chars, i, line, column);
285 if *i < chars.len() && chars[*i] == ',' {
286 *i += 1;
287 *column += 1;
288 }
289 } else {
290 *i += 1;
291 *column += 1;
292 }
293 }
294
295 if *i < chars.len() && chars[*i] == '}' {
296 *i += 1;
297 *column += 1;
298 }
299 }
300 '[' => {
301 *i += 1;
302 *column += 1;
303 let mut index = 0usize;
304
305 self.skip_whitespace(chars, i, line, column);
306
307 while *i < chars.len() && chars[*i] != ']' {
308 self.skip_whitespace(chars, i, line, column);
309 if *i >= chars.len() || chars[*i] == ']' {
310 break;
311 }
312
313 path_stack.push(PathSegment::Index(index));
315 let path = self.build_path(path_stack);
316 self.locations.insert(path, JsonLocation::new(*line, *column));
317 self.skip_value(chars, i, line, column, path_stack);
318 path_stack.pop();
319
320 index += 1;
321
322 self.skip_whitespace(chars, i, line, column);
324 if *i < chars.len() && chars[*i] == ',' {
325 *i += 1;
326 *column += 1;
327 }
328 }
329
330 if *i < chars.len() && chars[*i] == ']' {
331 *i += 1;
332 *column += 1;
333 }
334 }
335 '"' => {
336 self.parse_string(chars, i, line, column);
337 }
338 _ => {
339 while *i < chars.len() {
341 let ch = chars[*i];
342 if ch == ',' || ch == '}' || ch == ']' || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
343 break;
344 }
345 if ch == '\n' {
346 *line += 1;
347 *column = 1;
348 } else {
349 *column += 1;
350 }
351 *i += 1;
352 }
353 }
354 }
355 }
356
357 fn build_path(&self, stack: &[PathSegment]) -> String {
359 if stack.is_empty() {
360 return String::new();
361 }
362
363 let mut path = String::new();
364 for segment in stack {
365 path.push('/');
366 match segment {
367 PathSegment::Property(key) => {
368 for ch in key.chars() {
370 match ch {
371 '~' => path.push_str("~0"),
372 '/' => path.push_str("~1"),
373 _ => path.push(ch),
374 }
375 }
376 }
377 PathSegment::Index(idx) => {
378 path.push_str(&idx.to_string());
379 }
380 }
381 }
382 path
383 }
384
385 pub fn get_location(&self, path: impl AsRef<str>) -> JsonLocation {
387 self.locations
388 .get(path.as_ref())
389 .copied()
390 .unwrap_or_else(JsonLocation::unknown)
391 }
392
393 pub fn has_location(&self, path: &str) -> bool {
395 self.locations.contains_key(path)
396 }
397}
398
399#[derive(Debug, Clone)]
401enum PathSegment {
402 Property(String),
403 Index(usize),
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_simple_object() {
412 let json = r#"{"name": "test", "value": 42}"#;
413 let locator = JsonSourceLocator::new(json);
414
415 let root = locator.get_location("");
416 assert_eq!(root.line, 1);
417 assert_eq!(root.column, 1);
418
419 let name = locator.get_location("/name");
420 assert!(!name.is_unknown());
421
422 let value = locator.get_location("/value");
423 assert!(!value.is_unknown());
424 }
425
426 #[test]
427 fn test_nested_object() {
428 let json = r#"{
429 "outer": {
430 "inner": "value"
431 }
432}"#;
433 let locator = JsonSourceLocator::new(json);
434
435 let inner = locator.get_location("/outer/inner");
436 assert!(!inner.is_unknown());
437 assert_eq!(inner.line, 3);
438 }
439
440 #[test]
441 fn test_array() {
442 let json = r#"[1, 2, 3]"#;
443 let locator = JsonSourceLocator::new(json);
444
445 let first = locator.get_location("/0");
446 assert!(!first.is_unknown());
447
448 let second = locator.get_location("/1");
449 assert!(!second.is_unknown());
450 }
451
452 #[test]
453 fn test_unknown_path() {
454 let json = r#"{"name": "test"}"#;
455 let locator = JsonSourceLocator::new(json);
456
457 let unknown = locator.get_location("/nonexistent");
458 assert!(unknown.is_unknown());
459 }
460}