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_scalar_preserving(content: &str, path: &str, value: &Value) -> DocumentResult<String> {
42 let segments = crate::document::parse_path(path)?;
43 let document = JsonDocument::parse(content)?;
44 let root = document.root();
45 let replacement =
46 serde_json::to_string(&serde_json::Value::from(value.clone())).map_err(|error| {
47 DocumentError::UnsupportedOperation {
48 format: "JSON".to_string(),
49 operation: "set".to_string(),
50 detail: error.to_string(),
51 }
52 })?;
53 let mut output = content.to_string();
54 match resolve(root, &segments, 0) {
55 Some(target) => {
56 if !matches!(target.kind, NodeKind::Scalar) {
57 return Err(DocumentError::UnsupportedOperation {
58 format: "JSON".to_string(),
59 operation: "set".to_string(),
60 detail: "only existing scalar JSON values are supported by the source editor"
61 .to_string(),
62 });
63 }
64 output.replace_range(target.start..target.end, &replacement);
65 }
66 None => {
67 let (last, parents) = segments.split_last().ok_or(DocumentError::EmptyPath)?;
71 let parent = resolve(root, parents, 0).ok_or_else(|| DocumentError::PathNotFound {
72 path: path.to_string(),
73 })?;
74 let NodeKind::Object(entries) = &parent.kind else {
75 return Err(DocumentError::UnsupportedOperation {
76 format: "JSON".to_string(),
77 operation: "set".to_string(),
78 detail: "cannot insert a new key into a non-object JSON value".to_string(),
79 });
80 };
81 let new_key =
82 serde_json::to_string(last).map_err(|error| DocumentError::ParseError {
83 format: "JSON".to_string(),
84 detail: error.to_string(),
85 })?;
86 let (position, fragment) = match entries.last() {
87 Some((_, last_node)) => {
88 let anchor = last_node.member_start.unwrap_or(last_node.start);
89 let (indent, multiline) = member_indent(content.as_bytes(), anchor);
90 let fragment = if multiline {
91 format!(",\n{indent}{new_key}: {replacement}")
92 } else {
93 format!(", {new_key}: {replacement}")
94 };
95 (last_node.end, fragment)
96 }
97 None => (parent.start + 1, format!("{new_key}: {replacement}")),
98 };
99 output.insert_str(position, &fragment);
100 }
101 }
102 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
103 DocumentError::ParseError {
104 format: "JSON".to_string(),
105 detail: error.to_string(),
106 }
107 })?;
108 Ok(output)
109}
110
111fn member_indent(bytes: &[u8], anchor: usize) -> (String, bool) {
114 match bytes[..anchor].iter().rposition(|byte| *byte == b'\n') {
115 Some(newline) => {
116 let indent = bytes[newline + 1..anchor]
117 .iter()
118 .take_while(|byte| matches!(byte, b' ' | b'\t'))
119 .map(|byte| *byte as char)
120 .collect();
121 (indent, true)
122 }
123 None => (String::new(), false),
124 }
125}
126
127pub fn unset_preserving(content: &str, path: &str) -> DocumentResult<String> {
130 let segments = crate::document::parse_path(path)?;
131 let document = JsonDocument::parse(content)?;
132 let root = document.root();
133 let target = resolve(root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
134 path: path.to_string(),
135 })?;
136 if target.member_start.is_none()
137 && matches!(target.kind, NodeKind::Scalar)
138 && segments.is_empty()
139 {
140 return Err(DocumentError::UnsupportedOperation {
141 format: "JSON".to_string(),
142 operation: "unset".to_string(),
143 detail: "cannot remove the JSON root value".to_string(),
144 });
145 }
146 let mut start = target.member_start.unwrap_or(target.start);
147 if target.member_start.is_some()
148 && let Some(newline) = content.as_bytes()[..start]
149 .iter()
150 .rposition(|byte| *byte == b'\n')
151 {
152 let candidate = newline + 1;
153 if content.as_bytes()[candidate..start]
154 .iter()
155 .all(|byte| byte.is_ascii_whitespace())
156 {
157 start = candidate;
158 }
159 }
160 let end = target.end;
161 let after = skip_ws_bytes(content.as_bytes(), end);
162 let (remove_start, mut remove_end, remove_following_line) =
163 if content.as_bytes().get(after) == Some(&b',') {
164 (start, after + 1, true)
165 } else if let Some(comma) = content.as_bytes()[..start]
166 .iter()
167 .rposition(|byte| *byte == b',')
168 {
169 (comma, end, false)
170 } else {
171 (start, end, false)
172 };
173 if remove_following_line {
174 if content.as_bytes().get(remove_end) == Some(&b'\r') {
175 remove_end += 1;
176 }
177 if content.as_bytes().get(remove_end) == Some(&b'\n') {
178 remove_end += 1;
179 }
180 }
181 let mut output = content.to_string();
182 output.replace_range(remove_start..remove_end, "");
183 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
184 DocumentError::ParseError {
185 format: "JSON".to_string(),
186 detail: error.to_string(),
187 }
188 })?;
189 Ok(output)
190}
191
192pub fn append_array_item_preserving(
195 content: &str,
196 path: &str,
197 item: &Value,
198) -> DocumentResult<String> {
199 let segments = crate::document::parse_path(path)?;
200 let mut parser = Parser::new(content);
201 let root = parser.parse_value(0)?;
202 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
203 path: path.to_string(),
204 })?;
205 let NodeKind::Array(items) = &target.kind else {
206 return Err(DocumentError::UnsupportedOperation {
207 format: "JSON".to_string(),
208 operation: "add".to_string(),
209 detail: "target is not an array".to_string(),
210 });
211 };
212 let fragment =
213 serde_json::to_string(&serde_json::Value::from(item.clone())).map_err(|error| {
214 DocumentError::UnsupportedOperation {
215 format: "JSON".to_string(),
216 operation: "add".to_string(),
217 detail: error.to_string(),
218 }
219 })?;
220 let close = target
221 .end
222 .checked_sub(1)
223 .ok_or_else(|| DocumentError::ParseError {
224 format: "JSON".to_string(),
225 detail: "invalid array span".to_string(),
226 })?;
227 let whitespace_start = content.as_bytes()[target.start + 1..close]
228 .iter()
229 .rposition(|byte| !byte.is_ascii_whitespace())
230 .map(|index| target.start + 2 + index)
231 .unwrap_or(target.start + 1);
232 let insertion = if items.is_empty() {
233 fragment
234 } else {
235 format!(", {fragment}")
236 };
237 let mut output = content.to_string();
238 output.insert_str(whitespace_start, &insertion);
239 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
240 DocumentError::ParseError {
241 format: "JSON".to_string(),
242 detail: error.to_string(),
243 }
244 })?;
245 Ok(output)
246}
247
248pub fn remove_array_item_preserving(
250 content: &str,
251 path: &str,
252 slug: &str,
253 slug_field: &str,
254) -> DocumentResult<String> {
255 let segments = crate::document::parse_path(path)?;
256 let mut parser = Parser::new(content);
257 let root = parser.parse_value(0)?;
258 let target = resolve(&root, &segments, 0).ok_or_else(|| DocumentError::PathNotFound {
259 path: path.to_string(),
260 })?;
261 let NodeKind::Array(items) = &target.kind else {
262 return Err(DocumentError::UnsupportedOperation {
263 format: "JSON".to_string(),
264 operation: "remove".to_string(),
265 detail: "target is not an array".to_string(),
266 });
267 };
268 let item = items
269 .iter()
270 .find(|item| {
271 let Ok(value) =
272 serde_json::from_str::<serde_json::Value>(&content[item.start..item.end])
273 else {
274 return false;
275 };
276 value.get(slug_field).and_then(serde_json::Value::as_str) == Some(slug)
277 })
278 .ok_or_else(|| DocumentError::SlugNotFound {
279 prefix: path.to_string(),
280 slug: slug.to_string(),
281 })?;
282 let mut start = item.start;
283 if let Some(newline) = content.as_bytes()[..start]
284 .iter()
285 .rposition(|byte| *byte == b'\n')
286 {
287 let candidate = newline + 1;
288 if content.as_bytes()[candidate..start]
289 .iter()
290 .all(|byte| byte.is_ascii_whitespace())
291 {
292 start = candidate;
293 }
294 }
295 let after = skip_ws_bytes(content.as_bytes(), item.end);
296 let (mut remove_start, mut remove_end, remove_following_line) =
297 if content.as_bytes().get(after) == Some(&b',') {
298 (start, after + 1, true)
299 } else if let Some(comma) = content.as_bytes()[target.start..start]
300 .iter()
301 .rposition(|byte| *byte == b',')
302 .map(|offset| target.start + offset)
303 {
304 (comma, item.end, false)
311 } else {
312 (start, item.end, false)
313 };
314 if remove_following_line {
315 if content.as_bytes().get(remove_end) == Some(&b'\r') {
316 remove_end += 1;
317 }
318 if content.as_bytes().get(remove_end) == Some(&b'\n') {
319 remove_end += 1;
320 }
321 }
322 if remove_start > remove_end {
323 std::mem::swap(&mut remove_start, &mut remove_end);
324 }
325 let mut output = content.to_string();
326 output.replace_range(remove_start..remove_end, "");
327 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
328 DocumentError::ParseError {
329 format: "JSON".to_string(),
330 detail: error.to_string(),
331 }
332 })?;
333 Ok(output)
334}
335
336fn skip_ws_bytes(source: &[u8], mut position: usize) -> usize {
337 while source
338 .get(position)
339 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
340 {
341 position += 1;
342 }
343 position
344}
345
346#[derive(Debug, Clone)]
347pub(crate) struct Node {
348 start: usize,
349 end: usize,
350 member_start: Option<usize>,
351 kind: NodeKind,
352}
353
354#[derive(Debug, Clone)]
355enum NodeKind {
356 Scalar,
357 Object(Vec<(String, Node)>),
358 Array(Vec<Node>),
359}
360
361fn resolve<'a>(node: &'a Node, segments: &[String], index: usize) -> Option<&'a Node> {
362 if index == segments.len() {
363 return Some(node);
364 }
365 match &node.kind {
366 NodeKind::Object(entries) => entries
367 .iter()
368 .rev()
369 .find(|(key, _)| key == &segments[index])
370 .and_then(|(_, child)| resolve(child, segments, index + 1)),
371 NodeKind::Array(items) => segments[index]
372 .parse::<usize>()
373 .ok()
374 .and_then(|item| items.get(item))
375 .and_then(|child| resolve(child, segments, index + 1)),
376 NodeKind::Scalar => None,
377 }
378}
379
380struct Parser<'a> {
381 source: &'a [u8],
382}
383
384impl<'a> Parser<'a> {
385 fn new(source: &'a str) -> Self {
386 Self {
387 source: source.as_bytes(),
388 }
389 }
390
391 fn parse_value(&mut self, mut position: usize) -> DocumentResult<Node> {
392 position = self.skip_ws(position);
393 let start = position;
394 let Some(byte) = self.source.get(position).copied() else {
395 return self.error(position, "expected JSON value");
396 };
397 let kind = match byte {
398 b'{' => self.parse_object(&mut position)?,
399 b'[' => self.parse_array(&mut position)?,
400 b'"' => {
401 position = self.parse_string(position)?;
402 NodeKind::Scalar
403 }
404 _ => {
405 position = self.parse_scalar(position)?;
406 NodeKind::Scalar
407 }
408 };
409 Ok(Node {
410 start,
411 end: position,
412 member_start: None,
413 kind,
414 })
415 }
416
417 fn parse_object(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
418 *position += 1;
419 let mut entries = Vec::new();
420 loop {
421 *position = self.skip_ws(*position);
422 if self.source.get(*position) == Some(&b'}') {
423 *position += 1;
424 return Ok(NodeKind::Object(entries));
425 }
426 let key_start = *position;
427 let key_end = self.parse_string(*position)?;
428 let key = serde_json::from_slice::<String>(&self.source[key_start..key_end]).map_err(
429 |error| DocumentError::ParseError {
430 format: "JSON".to_string(),
431 detail: error.to_string(),
432 },
433 )?;
434 *position = self.skip_ws(key_end);
435 if self.source.get(*position) != Some(&b':') {
436 return self.error(*position, "expected `:` after object key");
437 }
438 *position += 1;
439 let child = self.parse_value(*position)?;
440 *position = child.end;
441 let mut child = child;
442 child.member_start = Some(key_start);
443 entries.push((key, child));
444 *position = self.skip_ws(*position);
445 match self.source.get(*position) {
446 Some(b',') => *position += 1,
447 Some(b'}') => {
448 *position += 1;
449 return Ok(NodeKind::Object(entries));
450 }
451 _ => return self.error(*position, "expected `,` or `}` in object"),
452 }
453 }
454 }
455
456 fn parse_array(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
457 *position += 1;
458 let mut items = Vec::new();
459 loop {
460 *position = self.skip_ws(*position);
461 if self.source.get(*position) == Some(&b']') {
462 *position += 1;
463 return Ok(NodeKind::Array(items));
464 }
465 let child = self.parse_value(*position)?;
466 *position = child.end;
467 items.push(child);
468 *position = self.skip_ws(*position);
469 match self.source.get(*position) {
470 Some(b',') => *position += 1,
471 Some(b']') => {
472 *position += 1;
473 return Ok(NodeKind::Array(items));
474 }
475 _ => return self.error(*position, "expected `,` or `]` in array"),
476 }
477 }
478 }
479
480 fn parse_string(&self, mut position: usize) -> DocumentResult<usize> {
481 if self.source.get(position) != Some(&b'"') {
482 return self.error(position, "expected JSON string");
483 }
484 position += 1;
485 let mut escaped = false;
486 while let Some(byte) = self.source.get(position).copied() {
487 position += 1;
488 if escaped {
489 escaped = false;
490 } else if byte == b'\\' {
491 escaped = true;
492 } else if byte == b'"' {
493 return Ok(position);
494 }
495 }
496 self.error(position, "unterminated JSON string")
497 }
498
499 fn parse_scalar(&self, mut position: usize) -> DocumentResult<usize> {
500 let start = position;
501 while let Some(byte) = self.source.get(position).copied() {
502 if matches!(byte, b',' | b']' | b'}' | b' ' | b'\n' | b'\r' | b'\t') {
503 break;
504 }
505 position += 1;
506 }
507 if position == start {
508 return self.error(position, "empty JSON scalar");
509 }
510 serde_json::from_slice::<serde_json::Value>(&self.source[start..position])
511 .map_err(|error| DocumentError::ParseError {
512 format: "JSON".to_string(),
513 detail: error.to_string(),
514 })
515 .map(|_| position)
516 }
517
518 fn skip_ws(&self, mut position: usize) -> usize {
519 while self
520 .source
521 .get(position)
522 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
523 {
524 position += 1;
525 }
526 position
527 }
528
529 fn error<T>(&self, position: usize, detail: &str) -> DocumentResult<T> {
530 Err(DocumentError::ParseError {
531 format: "JSON".to_string(),
532 detail: format!("at byte {position}: {detail}"),
533 })
534 }
535}
536
537pub fn load(content: &str) -> DocumentResult<Value> {
538 serde_json::from_str::<serde_json::Value>(content)
539 .map(Value::from)
540 .map_err(|e| DocumentError::ParseError {
541 format: "JSON".to_string(),
542 detail: e.to_string(),
543 })
544}
545
546pub fn save(value: &Value) -> DocumentResult<String> {
547 let json_val: serde_json::Value = value.clone().into();
548 serde_json::to_string_pretty(&json_val).map_err(|e| DocumentError::ParseError {
549 format: "JSON".to_string(),
550 detail: e.to_string(),
551 })
552}