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()[..start]
300 .iter()
301 .rposition(|byte| *byte == b',')
302 {
303 (comma, item.end, false)
304 } else {
305 (start, item.end, false)
306 };
307 if remove_following_line {
308 if content.as_bytes().get(remove_end) == Some(&b'\r') {
309 remove_end += 1;
310 }
311 if content.as_bytes().get(remove_end) == Some(&b'\n') {
312 remove_end += 1;
313 }
314 }
315 if remove_start > remove_end {
316 std::mem::swap(&mut remove_start, &mut remove_end);
317 }
318 let mut output = content.to_string();
319 output.replace_range(remove_start..remove_end, "");
320 serde_json::from_str::<serde_json::Value>(&output).map_err(|error| {
321 DocumentError::ParseError {
322 format: "JSON".to_string(),
323 detail: error.to_string(),
324 }
325 })?;
326 Ok(output)
327}
328
329fn skip_ws_bytes(source: &[u8], mut position: usize) -> usize {
330 while source
331 .get(position)
332 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
333 {
334 position += 1;
335 }
336 position
337}
338
339#[derive(Debug, Clone)]
340pub(crate) struct Node {
341 start: usize,
342 end: usize,
343 member_start: Option<usize>,
344 kind: NodeKind,
345}
346
347#[derive(Debug, Clone)]
348enum NodeKind {
349 Scalar,
350 Object(Vec<(String, Node)>),
351 Array(Vec<Node>),
352}
353
354fn resolve<'a>(node: &'a Node, segments: &[String], index: usize) -> Option<&'a Node> {
355 if index == segments.len() {
356 return Some(node);
357 }
358 match &node.kind {
359 NodeKind::Object(entries) => entries
360 .iter()
361 .rev()
362 .find(|(key, _)| key == &segments[index])
363 .and_then(|(_, child)| resolve(child, segments, index + 1)),
364 NodeKind::Array(items) => segments[index]
365 .parse::<usize>()
366 .ok()
367 .and_then(|item| items.get(item))
368 .and_then(|child| resolve(child, segments, index + 1)),
369 NodeKind::Scalar => None,
370 }
371}
372
373struct Parser<'a> {
374 source: &'a [u8],
375}
376
377impl<'a> Parser<'a> {
378 fn new(source: &'a str) -> Self {
379 Self {
380 source: source.as_bytes(),
381 }
382 }
383
384 fn parse_value(&mut self, mut position: usize) -> DocumentResult<Node> {
385 position = self.skip_ws(position);
386 let start = position;
387 let Some(byte) = self.source.get(position).copied() else {
388 return self.error(position, "expected JSON value");
389 };
390 let kind = match byte {
391 b'{' => self.parse_object(&mut position)?,
392 b'[' => self.parse_array(&mut position)?,
393 b'"' => {
394 position = self.parse_string(position)?;
395 NodeKind::Scalar
396 }
397 _ => {
398 position = self.parse_scalar(position)?;
399 NodeKind::Scalar
400 }
401 };
402 Ok(Node {
403 start,
404 end: position,
405 member_start: None,
406 kind,
407 })
408 }
409
410 fn parse_object(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
411 *position += 1;
412 let mut entries = Vec::new();
413 loop {
414 *position = self.skip_ws(*position);
415 if self.source.get(*position) == Some(&b'}') {
416 *position += 1;
417 return Ok(NodeKind::Object(entries));
418 }
419 let key_start = *position;
420 let key_end = self.parse_string(*position)?;
421 let key = serde_json::from_slice::<String>(&self.source[key_start..key_end]).map_err(
422 |error| DocumentError::ParseError {
423 format: "JSON".to_string(),
424 detail: error.to_string(),
425 },
426 )?;
427 *position = self.skip_ws(key_end);
428 if self.source.get(*position) != Some(&b':') {
429 return self.error(*position, "expected `:` after object key");
430 }
431 *position += 1;
432 let child = self.parse_value(*position)?;
433 *position = child.end;
434 let mut child = child;
435 child.member_start = Some(key_start);
436 entries.push((key, child));
437 *position = self.skip_ws(*position);
438 match self.source.get(*position) {
439 Some(b',') => *position += 1,
440 Some(b'}') => {
441 *position += 1;
442 return Ok(NodeKind::Object(entries));
443 }
444 _ => return self.error(*position, "expected `,` or `}` in object"),
445 }
446 }
447 }
448
449 fn parse_array(&mut self, position: &mut usize) -> DocumentResult<NodeKind> {
450 *position += 1;
451 let mut items = Vec::new();
452 loop {
453 *position = self.skip_ws(*position);
454 if self.source.get(*position) == Some(&b']') {
455 *position += 1;
456 return Ok(NodeKind::Array(items));
457 }
458 let child = self.parse_value(*position)?;
459 *position = child.end;
460 items.push(child);
461 *position = self.skip_ws(*position);
462 match self.source.get(*position) {
463 Some(b',') => *position += 1,
464 Some(b']') => {
465 *position += 1;
466 return Ok(NodeKind::Array(items));
467 }
468 _ => return self.error(*position, "expected `,` or `]` in array"),
469 }
470 }
471 }
472
473 fn parse_string(&self, mut position: usize) -> DocumentResult<usize> {
474 if self.source.get(position) != Some(&b'"') {
475 return self.error(position, "expected JSON string");
476 }
477 position += 1;
478 let mut escaped = false;
479 while let Some(byte) = self.source.get(position).copied() {
480 position += 1;
481 if escaped {
482 escaped = false;
483 } else if byte == b'\\' {
484 escaped = true;
485 } else if byte == b'"' {
486 return Ok(position);
487 }
488 }
489 self.error(position, "unterminated JSON string")
490 }
491
492 fn parse_scalar(&self, mut position: usize) -> DocumentResult<usize> {
493 let start = position;
494 while let Some(byte) = self.source.get(position).copied() {
495 if matches!(byte, b',' | b']' | b'}' | b' ' | b'\n' | b'\r' | b'\t') {
496 break;
497 }
498 position += 1;
499 }
500 if position == start {
501 return self.error(position, "empty JSON scalar");
502 }
503 serde_json::from_slice::<serde_json::Value>(&self.source[start..position])
504 .map_err(|error| DocumentError::ParseError {
505 format: "JSON".to_string(),
506 detail: error.to_string(),
507 })
508 .map(|_| position)
509 }
510
511 fn skip_ws(&self, mut position: usize) -> usize {
512 while self
513 .source
514 .get(position)
515 .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t'))
516 {
517 position += 1;
518 }
519 position
520 }
521
522 fn error<T>(&self, position: usize, detail: &str) -> DocumentResult<T> {
523 Err(DocumentError::ParseError {
524 format: "JSON".to_string(),
525 detail: format!("at byte {position}: {detail}"),
526 })
527 }
528}
529
530pub fn load(content: &str) -> DocumentResult<Value> {
531 serde_json::from_str::<serde_json::Value>(content)
532 .map(Value::from)
533 .map_err(|e| DocumentError::ParseError {
534 format: "JSON".to_string(),
535 detail: e.to_string(),
536 })
537}
538
539pub fn save(value: &Value) -> DocumentResult<String> {
540 let json_val: serde_json::Value = value.clone().into();
541 serde_json::to_string_pretty(&json_val).map_err(|e| DocumentError::ParseError {
542 format: "JSON".to_string(),
543 detail: e.to_string(),
544 })
545}