1use crate::document::{DocumentError, DocumentResult, Value};
4
5#[derive(Debug)]
9pub struct JsonDocument<'a> {
10 source: &'a str,
11 root: Node,
12}
13
14impl<'a> JsonDocument<'a> {
15 pub fn parse(source: &'a str) -> DocumentResult<Self> {
16 let mut parser = Parser::new(source);
17 let root = parser.parse_value(0)?;
18 if parser.skip_ws(root.end) != source.len() {
19 return Err(DocumentError::ParseError {
20 format: "JSON".to_string(),
21 detail: "trailing bytes after root value".to_string(),
22 });
23 }
24 Ok(Self { source, root })
25 }
26
27 #[must_use]
28 pub fn source(&self) -> &'a str {
29 self.source
30 }
31
32 #[must_use]
33 pub(crate) fn root(&self) -> &Node {
34 &self.root
35 }
36}
37
38pub fn set_preserving(content: &str, path: &str, value: &Value) -> DocumentResult<String> {
47 let segments = crate::document::parse_path(path)?;
48 let document = JsonDocument::parse(content)?;
49 let root = document.root();
50 let mut output = content.to_string();
51 match resolve(root, &segments, 0) {
52 Some(target) => {
53 let replacement = compact_json(value)?;
57 output.replace_range(target.start..target.end, &replacement);
58 }
59 None => {
60 let (insert_at, tail) = deepest_existing_object(root, &segments).ok_or_else(|| {
65 DocumentError::UnsupportedOperation {
66 format: "JSON".to_string(),
67 operation: "set".to_string(),
68 detail: "cannot insert a new key into a non-object JSON value".to_string(),
69 }
70 })?;
71 let NodeKind::Object(entries) = &insert_at.kind else {
72 unreachable!("deepest_existing_object only returns object nodes");
73 };
74 let (member_key, rest) = tail.split_first().ok_or(DocumentError::EmptyPath)?;
75 let new_key =
76 serde_json::to_string(member_key).map_err(|error| DocumentError::ParseError {
77 format: "JSON".to_string(),
78 detail: error.to_string(),
79 })?;
80 let (position, fragment) = match entries.last() {
81 Some((_, last_node)) => {
82 let anchor = last_node.member_start.unwrap_or(last_node.start);
83 let (indent, multiline) = member_indent(content.as_bytes(), anchor);
84 let member_value = nested_member_value(rest, value, multiline, &indent)?;
85 let fragment = if multiline {
86 format!(",\n{indent}{new_key}: {member_value}")
87 } else {
88 format!(", {new_key}: {member_value}")
89 };
90 (last_node.end, fragment)
91 }
92 None => {
93 let member_value = nested_member_value(rest, value, false, "")?;
95 (insert_at.start + 1, format!("{new_key}: {member_value}"))
96 }
97 };
98 output.insert_str(position, &fragment);
99 }
100 }
101 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
102 DocumentError::ParseError {
103 format: "JSON".to_string(),
104 detail: error.to_string(),
105 }
106 })?;
107 Ok(output)
108}
109
110fn compact_json(value: &Value) -> DocumentResult<String> {
112 serde_json::to_string(&serde_json::Value::from(value.clone())).map_err(|error| {
113 DocumentError::UnsupportedOperation {
114 format: "JSON".to_string(),
115 operation: "set".to_string(),
116 detail: error.to_string(),
117 }
118 })
119}
120
121fn deepest_existing_object<'a>(
125 root: &'a Node,
126 segments: &'a [String],
127) -> Option<(&'a Node, &'a [String])> {
128 for split in (0..segments.len()).rev() {
131 if let Some(node) = resolve(root, &segments[..split], 0) {
132 return match node.kind {
133 NodeKind::Object(_) => Some((node, &segments[split..])),
134 _ => None,
135 };
136 }
137 }
138 None
139}
140
141fn nested_member_value(
145 tail: &[String],
146 value: &Value,
147 multiline: bool,
148 base_indent: &str,
149) -> DocumentResult<String> {
150 let mut nested = serde_json::Value::from(value.clone());
151 for segment in tail.iter().rev() {
152 let mut object = serde_json::Map::new();
153 object.insert(segment.clone(), nested);
154 nested = serde_json::Value::Object(object);
155 }
156 if multiline {
157 let pretty =
158 serde_json::to_string_pretty(&nested).map_err(|error| DocumentError::ParseError {
159 format: "JSON".to_string(),
160 detail: error.to_string(),
161 })?;
162 Ok(pretty.replace('\n', &format!("\n{base_indent}")))
163 } else {
164 serde_json::to_string(&nested).map_err(|error| DocumentError::ParseError {
165 format: "JSON".to_string(),
166 detail: error.to_string(),
167 })
168 }
169}
170
171fn member_indent(bytes: &[u8], anchor: usize) -> (String, bool) {
174 match bytes[..anchor].iter().rposition(|byte| *byte == b'\n') {
175 Some(newline) => {
176 let indent = bytes[newline + 1..anchor]
177 .iter()
178 .take_while(|byte| matches!(byte, b' ' | b'\t'))
179 .map(|byte| *byte as char)
180 .collect();
181 (indent, true)
182 }
183 None => (String::new(), false),
184 }
185}
186
187pub fn unset_preserving(content: &str, path: &str) -> DocumentResult<String> {
190 let segments = crate::document::parse_path(path)?;
191 let document = JsonDocument::parse(content)?;
192 let root = document.root();
193 let target = resolve(root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
194 path: path.to_string(),
195 })?;
196 if target.member_start.is_none()
197 && matches!(target.kind, NodeKind::Scalar)
198 && segments.is_empty()
199 {
200 return Err(DocumentError::UnsupportedOperation {
201 format: "JSON".to_string(),
202 operation: "unset".to_string(),
203 detail: "cannot remove the JSON root value".to_string(),
204 });
205 }
206 let mut start = target.member_start.unwrap_or(target.start);
207 if target.member_start.is_some()
208 && let Some(newline) = content.as_bytes()[..start]
209 .iter()
210 .rposition(|byte| *byte == b'\n')
211 {
212 let candidate = newline + 1;
213 if content.as_bytes()[candidate..start]
214 .iter()
215 .all(|byte| byte.is_ascii_whitespace())
216 {
217 start = candidate;
218 }
219 }
220 let end = target.end;
221 let after = skip_ws_bytes(content.as_bytes(), end);
222 let parent_body_start =
227 resolve(root, &segments[..segments.len() - 1], 0).map_or(0, |parent| parent.start + 1);
228 let (remove_start, mut remove_end, remove_following_line) =
229 if content.as_bytes().get(after) == Some(&b',') {
230 (start, after + 1, true)
231 } else if let Some(offset) = content.as_bytes()[parent_body_start..start]
232 .iter()
233 .rposition(|byte| *byte == b',')
234 {
235 (parent_body_start + offset, end, false)
236 } else {
237 (start, end, false)
238 };
239 if remove_following_line {
240 if content.as_bytes().get(remove_end) == Some(&b'\r') {
241 remove_end += 1;
242 }
243 if content.as_bytes().get(remove_end) == Some(&b'\n') {
244 remove_end += 1;
245 }
246 }
247 let mut output = content.to_string();
248 output.replace_range(remove_start..remove_end, "");
249 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
250 DocumentError::ParseError {
251 format: "JSON".to_string(),
252 detail: error.to_string(),
253 }
254 })?;
255 Ok(output)
256}
257
258pub fn append_array_item_preserving(
261 content: &str,
262 path: &str,
263 item: &Value,
264) -> DocumentResult<String> {
265 let segments = crate::document::parse_path(path)?;
266 let mut parser = Parser::new(content);
267 let root = parser.parse_value(0)?;
268 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
269 path: path.to_string(),
270 })?;
271 let NodeKind::Array(items) = &target.kind else {
272 return Err(DocumentError::UnsupportedOperation {
273 format: "JSON".to_string(),
274 operation: "add".to_string(),
275 detail: "target is not an array".to_string(),
276 });
277 };
278 let fragment =
279 serde_json::to_string(&serde_json::Value::from(item.clone())).map_err(|error| {
280 DocumentError::UnsupportedOperation {
281 format: "JSON".to_string(),
282 operation: "add".to_string(),
283 detail: error.to_string(),
284 }
285 })?;
286 let close = target
287 .end
288 .checked_sub(1)
289 .ok_or_else(|| DocumentError::ParseError {
290 format: "JSON".to_string(),
291 detail: "invalid array span".to_string(),
292 })?;
293 let whitespace_start = content.as_bytes()[target.start + 1..close]
294 .iter()
295 .rposition(|byte| !byte.is_ascii_whitespace())
296 .map(|index| target.start + 2 + index)
297 .unwrap_or(target.start + 1);
298 let insertion = if items.is_empty() {
299 fragment
300 } else {
301 format!(", {fragment}")
302 };
303 let mut output = content.to_string();
304 output.insert_str(whitespace_start, &insertion);
305 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
306 DocumentError::ParseError {
307 format: "JSON".to_string(),
308 detail: error.to_string(),
309 }
310 })?;
311 Ok(output)
312}
313
314pub fn remove_array_item_preserving(
316 content: &str,
317 path: &str,
318 slug: &str,
319 slug_field: &str,
320) -> DocumentResult<String> {
321 let segments = crate::document::parse_path(path)?;
322 let mut parser = Parser::new(content);
323 let root = parser.parse_value(0)?;
324 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
325 path: path.to_string(),
326 })?;
327 let NodeKind::Array(items) = &target.kind else {
328 return Err(DocumentError::UnsupportedOperation {
329 format: "JSON".to_string(),
330 operation: "remove".to_string(),
331 detail: "target is not an array".to_string(),
332 });
333 };
334 let item = items
335 .iter()
336 .find(|item| {
337 let Ok(value) =
338 serde_json::from_str::<serde_json::Value>(&content[item.start..item.end])
339 else {
340 return false;
341 };
342 value.get(slug_field).and_then(serde_json::Value::as_str) == Some(slug)
343 })
344 .ok_or_else(|| DocumentError::SlugNotFound {
345 prefix: path.to_string(),
346 slug: slug.to_string(),
347 })?;
348 let mut start = item.start;
349 if let Some(newline) = content.as_bytes()[..start]
350 .iter()
351 .rposition(|byte| *byte == b'\n')
352 {
353 let candidate = newline + 1;
354 if content.as_bytes()[candidate..start]
355 .iter()
356 .all(|byte| byte.is_ascii_whitespace())
357 {
358 start = candidate;
359 }
360 }
361 let after = skip_ws_bytes(content.as_bytes(), item.end);
362 let (mut remove_start, mut remove_end, remove_following_line) =
363 if content.as_bytes().get(after) == Some(&b',') {
364 (start, after + 1, true)
365 } else if let Some(comma) = content.as_bytes()[target.start..start]
366 .iter()
367 .rposition(|byte| *byte == b',')
368 .map(|offset| target.start + offset)
369 {
370 (comma, item.end, false)
377 } else {
378 (start, item.end, false)
379 };
380 if remove_following_line {
381 if content.as_bytes().get(remove_end) == Some(&b'\r') {
382 remove_end += 1;
383 }
384 if content.as_bytes().get(remove_end) == Some(&b'\n') {
385 remove_end += 1;
386 }
387 }
388 if remove_start > remove_end {
389 std::mem::swap(&mut remove_start, &mut remove_end);
390 }
391 let mut output = content.to_string();
392 output.replace_range(remove_start..remove_end, "");
393 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
394 DocumentError::ParseError {
395 format: "JSON".to_string(),
396 detail: error.to_string(),
397 }
398 })?;
399 Ok(output)
400}
401
402fn skip_ws_bytes(source: &[u8], mut position: usize) -> usize {
403 while source
404 .get(position)
405 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
406 {
407 position += 1;
408 }
409 position
410}
411
412#[derive(Debug, Clone)]
413pub(crate) struct Node {
414 start: usize,
415 end: usize,
416 member_start: Option<usize>,
417 kind: NodeKind,
418}
419
420#[derive(Debug, Clone)]
421enum NodeKind {
422 Scalar,
423 Object(Vec<(String, Node)>),
424 Array(Vec<Node>),
425}
426
427fn resolve<'a>(node: &'a Node, segments: &[String], index: usize) -> Option<&'a Node> {
428 if index == segments.len() {
429 return Some(node);
430 }
431 match &node.kind {
432 NodeKind::Object(entries) => entries
433 .iter()
434 .rev()
435 .find(|(key, _)| key == &segments[index])
436 .and_then(|(_, child)| resolve(child, segments, index + 1)),
437 NodeKind::Array(items) => segments[index]
438 .parse::<usize>()
439 .ok()
440 .and_then(|item| items.get(item))
441 .and_then(|child| resolve(child, segments, index + 1)),
442 NodeKind::Scalar => None,
443 }
444}
445
446struct Parser<'a> {
447 source: &'a [u8],
448}
449
450impl<'a> Parser<'a> {
451 fn new(source: &'a str) -> Self {
452 Self {
453 source: source.as_bytes(),
454 }
455 }
456
457 fn parse_value(&mut self, mut position: usize) -> DocumentResult<Node> {
458 position = self.skip_ws(position);
459 let start = position;
460 let Some(byte) = self.source.get(position).copied() else {
461 return self.error(position, "expected JSON value");
462 };
463 let kind = match byte {
464 b'{' => self.parse_object(&mut position)?,
465 b'[' => self.parse_array(&mut position)?,
466 b'"' => {
467 position = self.parse_string(position)?;
468 NodeKind::Scalar
469 }
470 _ => {
471 position = self.parse_scalar(position)?;
472 NodeKind::Scalar
473 }
474 };
475 Ok(Node {
476 start,
477 end: position,
478 member_start: None,
479 kind,
480 })
481 }
482
483 fn parse_object(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
484 *position += 1;
485 let mut entries = Vec::new();
486 loop {
487 *position = self.skip_ws(*position);
488 if self.source.get(*position) == Some(&b'}') {
489 *position += 1;
490 return Ok(NodeKind::Object(entries));
491 }
492 let key_start = *position;
493 let key_end = self.parse_string(*position)?;
494 let key = serde_json::from_slice::<String>(&self.source[key_start..key_end]).map_err(
495 |error| DocumentError::ParseError {
496 format: "JSON".to_string(),
497 detail: error.to_string(),
498 },
499 )?;
500 *position = self.skip_ws(key_end);
501 if self.source.get(*position) != Some(&b':') {
502 return self.error(*position, "expected `:` after object key");
503 }
504 *position += 1;
505 let child = self.parse_value(*position)?;
506 *position = child.end;
507 let mut child = child;
508 child.member_start = Some(key_start);
509 entries.push((key, child));
510 *position = self.skip_ws(*position);
511 match self.source.get(*position) {
512 Some(b',') => *position += 1,
513 Some(b'}') => {
514 *position += 1;
515 return Ok(NodeKind::Object(entries));
516 }
517 _ => return self.error(*position, "expected `,` or `}` in object"),
518 }
519 }
520 }
521
522 fn parse_array(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
523 *position += 1;
524 let mut items = Vec::new();
525 loop {
526 *position = self.skip_ws(*position);
527 if self.source.get(*position) == Some(&b']') {
528 *position += 1;
529 return Ok(NodeKind::Array(items));
530 }
531 let child = self.parse_value(*position)?;
532 *position = child.end;
533 items.push(child);
534 *position = self.skip_ws(*position);
535 match self.source.get(*position) {
536 Some(b',') => *position += 1,
537 Some(b']') => {
538 *position += 1;
539 return Ok(NodeKind::Array(items));
540 }
541 _ => return self.error(*position, "expected `,` or `]` in array"),
542 }
543 }
544 }
545
546 fn parse_string(&self, mut position: usize) -> DocumentResult<usize> {
547 if self.source.get(position) != Some(&b'"') {
548 return self.error(position, "expected JSON string");
549 }
550 position += 1;
551 let mut escaped = false;
552 while let Some(byte) = self.source.get(position).copied() {
553 position += 1;
554 if escaped {
555 escaped = false;
556 } else if byte == b'\\' {
557 escaped = true;
558 } else if byte == b'"' {
559 return Ok(position);
560 }
561 }
562 self.error(position, "unterminated JSON string")
563 }
564
565 fn parse_scalar(&self, mut position: usize) -> DocumentResult<usize> {
566 let start = position;
567 while let Some(byte) = self.source.get(position).copied() {
568 if matches!(byte, b',' | b']' | b'}' | b' ' | b'\n' | b'\r' | b'\t') {
569 break;
570 }
571 position += 1;
572 }
573 if position == start {
574 return self.error(position, "empty JSON scalar");
575 }
576 serde_json::from_slice::<serde_json::Value>(&self.source[start..position])
577 .map_err(|error| DocumentError::ParseError {
578 format: "JSON".to_string(),
579 detail: error.to_string(),
580 })
581 .map(|_| position)
582 }
583
584 fn skip_ws(&self, mut position: usize) -> usize {
585 while self
586 .source
587 .get(position)
588 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
589 {
590 position += 1;
591 }
592 position
593 }
594
595 fn error<T>(&self, position: usize, detail: &str) -> DocumentResult<T> {
596 Err(DocumentError::ParseError {
597 format: "JSON".to_string(),
598 detail: format!("at byte {position}: {detail}"),
599 })
600 }
601}
602
603pub fn load(content: &str) -> DocumentResult<Value> {
604 serde_json::from_str::<serde_json::Value>(content)
605 .map(Value::from)
606 .map_err(|e| DocumentError::ParseError {
607 format: "JSON".to_string(),
608 detail: e.to_string(),
609 })
610}
611
612pub fn save(value: &Value) -> DocumentResult<String> {
613 let json_val: serde_json::Value = value.clone().into();
614 serde_json::to_string_pretty(&json_val).map_err(|e| DocumentError::ParseError {
615 format: "JSON".to_string(),
616 detail: e.to_string(),
617 })
618}