1use fionn_core::Result;
45use fionn_core::tape_source::{TapeNodeKind, TapeSource, TapeValue};
46use serde_json::{Map, Value};
47
48use crate::diff_tape::{TapeDiff, TapeDiffOp, TapeValueOwned};
49
50pub fn tape_to_value<T: TapeSource>(tape: &T) -> Result<Value> {
62 if tape.is_empty() {
63 return Ok(Value::Null);
64 }
65
66 let (value, _) = convert_node(tape, 0)?;
67 Ok(value)
68}
69
70fn convert_node<T: TapeSource>(tape: &T, idx: usize) -> Result<(Value, usize)> {
72 let node = tape.node_at(idx);
73
74 match node {
75 Some(n) => {
76 match n.kind {
77 TapeNodeKind::ObjectStart { count } => {
78 let mut map = Map::with_capacity(count);
79 let mut current_idx = idx + 1;
80
81 for _ in 0..count {
82 let key = tape
84 .key_at(current_idx)
85 .map(std::borrow::Cow::into_owned)
86 .unwrap_or_default();
87 current_idx += 1;
88
89 let (value, next_idx) = convert_node(tape, current_idx)?;
91 map.insert(key, value);
92 current_idx = next_idx;
93 }
94
95 Ok((Value::Object(map), current_idx))
97 }
98
99 TapeNodeKind::ArrayStart { count } => {
100 let mut arr = Vec::with_capacity(count);
101 let mut current_idx = idx + 1;
102
103 for _ in 0..count {
104 let (value, next_idx) = convert_node(tape, current_idx)?;
105 arr.push(value);
106 current_idx = next_idx;
107 }
108
109 Ok((Value::Array(arr), current_idx))
111 }
112
113 TapeNodeKind::Value | TapeNodeKind::Key => {
114 let value = n.value.map_or(Value::Null, tape_value_to_json);
116 Ok((value, idx + 1))
117 }
118
119 TapeNodeKind::ObjectEnd | TapeNodeKind::ArrayEnd => {
120 Ok((Value::Null, idx + 1))
122 }
123 }
124 }
125 None => Ok((Value::Null, idx + 1)),
126 }
127}
128
129fn tape_value_to_json(val: TapeValue<'_>) -> Value {
131 match val {
132 TapeValue::Null => Value::Null,
133 TapeValue::Bool(b) => Value::Bool(b),
134 TapeValue::Int(n) => Value::Number(n.into()),
135 TapeValue::Float(f) => serde_json::Number::from_f64(f).map_or(Value::Null, Value::Number),
136 TapeValue::String(s) => Value::String(s.into_owned()),
137 TapeValue::RawNumber(s) => {
138 #[allow(clippy::option_if_let_else)]
140 if let Ok(n) = s.parse::<i64>() {
142 Value::Number(n.into())
143 } else if let Ok(f) = s.parse::<f64>() {
144 serde_json::Number::from_f64(f)
145 .map_or_else(|| Value::String(s.into_owned()), Value::Number)
146 } else {
147 Value::String(s.into_owned())
148 }
149 }
150 }
151}
152
153pub fn apply_tape_diff(value: &mut Value, diff: &TapeDiff<'_>) -> Result<()> {
165 for op in &diff.operations {
166 apply_tape_diff_op(value, op)?;
167 }
168 Ok(())
169}
170
171fn apply_tape_diff_op(value: &mut Value, op: &TapeDiffOp<'_>) -> Result<()> {
173 match op {
174 TapeDiffOp::Add {
175 path,
176 value: new_value,
177 }
178 | TapeDiffOp::Replace {
179 path,
180 value: new_value,
181 } => {
182 let json_value = tape_value_owned_to_json(new_value);
183 set_at_path(value, path, json_value)?;
184 }
185
186 TapeDiffOp::Remove { path } => {
187 remove_at_path(value, path)?;
188 }
189
190 TapeDiffOp::Move { from, path } => {
191 let moved = remove_at_path(value, from)?;
192 set_at_path(value, path, moved)?;
193 }
194
195 TapeDiffOp::Copy { from, path } => {
196 let copied = get_at_path(value, from)?.clone();
197 set_at_path(value, path, copied)?;
198 }
199
200 TapeDiffOp::AddRef {
201 path,
202 tape_index: _,
203 ..
204 }
205 | TapeDiffOp::ReplaceRef {
206 path,
207 tape_index: _,
208 ..
209 } => {
210 set_at_path(value, path, Value::Null)?;
212 }
213 }
214
215 Ok(())
216}
217
218fn tape_value_owned_to_json(val: &TapeValueOwned) -> Value {
220 match val {
221 TapeValueOwned::Null => Value::Null,
222 TapeValueOwned::Bool(b) => Value::Bool(*b),
223 TapeValueOwned::Int(n) => Value::Number((*n).into()),
224 TapeValueOwned::Float(f) => {
225 serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number)
226 }
227 TapeValueOwned::String(s) => Value::String(s.clone()),
228 TapeValueOwned::RawNumber(s) => {
229 #[allow(clippy::option_if_let_else)]
230 if let Ok(n) = s.parse::<i64>() {
232 Value::Number(n.into())
233 } else if let Ok(f) = s.parse::<f64>() {
234 serde_json::Number::from_f64(f)
235 .map_or_else(|| Value::String(s.clone()), Value::Number)
236 } else {
237 Value::String(s.clone())
238 }
239 }
240 TapeValueOwned::Json(json_str) => {
241 serde_json::from_str(json_str).unwrap_or(Value::Null)
243 }
244 }
245}
246
247fn get_at_path<'a>(value: &'a Value, path: &str) -> Result<&'a Value> {
253 if path.is_empty() {
254 return Ok(value);
255 }
256
257 let segments = parse_json_pointer(path)?;
258 let mut current = value;
259
260 for segment in segments {
261 current = match current {
262 Value::Object(map) => map.get(&segment).ok_or_else(|| {
263 fionn_core::DsonError::InvalidField(format!("Path not found: {path}"))
264 })?,
265 Value::Array(arr) => {
266 let index: usize = segment.parse().map_err(|_| {
267 fionn_core::DsonError::InvalidField(format!("Invalid array index: {segment}"))
268 })?;
269 arr.get(index).ok_or_else(|| {
270 fionn_core::DsonError::InvalidField(format!("Index out of bounds: {index}"))
271 })?
272 }
273 _ => {
274 return Err(fionn_core::DsonError::InvalidField(format!(
275 "Cannot navigate into scalar at {path}"
276 )));
277 }
278 };
279 }
280
281 Ok(current)
282}
283
284fn set_at_path(value: &mut Value, path: &str, new_value: Value) -> Result<()> {
286 if path.is_empty() {
287 *value = new_value;
288 return Ok(());
289 }
290
291 let segments = parse_json_pointer(path)?;
292 if segments.is_empty() {
293 *value = new_value;
294 return Ok(());
295 }
296
297 let parent_segments = &segments[..segments.len() - 1];
299 let final_key = &segments[segments.len() - 1];
300
301 let mut current = value;
302 for segment in parent_segments {
303 current = match current {
304 Value::Object(map) => map
305 .entry(segment.clone())
306 .or_insert(Value::Object(Map::new())),
307 Value::Array(arr) => {
308 let index: usize = segment.parse().map_err(|_| {
309 fionn_core::DsonError::InvalidField(format!("Invalid array index: {segment}"))
310 })?;
311 while arr.len() <= index {
313 arr.push(Value::Null);
314 }
315 arr.get_mut(index).ok_or_else(|| {
316 fionn_core::DsonError::InvalidField(format!("Index out of bounds: {index}"))
317 })?
318 }
319 _ => {
320 return Err(fionn_core::DsonError::InvalidField(
321 "Cannot navigate into scalar at path".to_string(),
322 ));
323 }
324 };
325 }
326
327 match current {
329 Value::Object(map) => {
330 map.insert(final_key.clone(), new_value);
331 }
332 Value::Array(arr) => {
333 if final_key == "-" {
334 arr.push(new_value);
335 } else {
336 let index: usize = final_key.parse().map_err(|_| {
337 fionn_core::DsonError::InvalidField(format!("Invalid array index: {final_key}"))
338 })?;
339 while arr.len() <= index {
340 arr.push(Value::Null);
341 }
342 arr[index] = new_value;
343 }
344 }
345 _ => {
346 return Err(fionn_core::DsonError::InvalidField(
347 "Cannot set value on scalar".to_string(),
348 ));
349 }
350 }
351
352 Ok(())
353}
354
355fn remove_at_path(value: &mut Value, path: &str) -> Result<Value> {
357 if path.is_empty() {
358 return Err(fionn_core::DsonError::InvalidField(
359 "Cannot remove root".to_string(),
360 ));
361 }
362
363 let segments = parse_json_pointer(path)?;
364 if segments.is_empty() {
365 return Err(fionn_core::DsonError::InvalidField(
366 "Cannot remove root".to_string(),
367 ));
368 }
369
370 let parent_segments = &segments[..segments.len() - 1];
372 let final_key = &segments[segments.len() - 1];
373
374 let mut current = value;
375 for segment in parent_segments {
376 current = match current {
377 Value::Object(map) => map.get_mut(segment).ok_or_else(|| {
378 fionn_core::DsonError::InvalidField(format!("Path not found: {path}"))
379 })?,
380 Value::Array(arr) => {
381 let index: usize = segment.parse().map_err(|_| {
382 fionn_core::DsonError::InvalidField(format!("Invalid array index: {segment}"))
383 })?;
384 arr.get_mut(index).ok_or_else(|| {
385 fionn_core::DsonError::InvalidField(format!("Index out of bounds: {index}"))
386 })?
387 }
388 _ => {
389 return Err(fionn_core::DsonError::InvalidField(format!(
390 "Cannot navigate into scalar at {path}"
391 )));
392 }
393 };
394 }
395
396 match current {
398 Value::Object(map) => map.remove(final_key).ok_or_else(|| {
399 fionn_core::DsonError::InvalidField(format!("Key not found: {final_key}"))
400 }),
401 Value::Array(arr) => {
402 let index: usize = final_key.parse().map_err(|_| {
403 fionn_core::DsonError::InvalidField(format!("Invalid array index: {final_key}"))
404 })?;
405 if index >= arr.len() {
406 return Err(fionn_core::DsonError::InvalidField(format!(
407 "Index out of bounds: {index}"
408 )));
409 }
410 Ok(arr.remove(index))
411 }
412 _ => Err(fionn_core::DsonError::InvalidField(
413 "Cannot remove from scalar".to_string(),
414 )),
415 }
416}
417
418fn parse_json_pointer(path: &str) -> Result<Vec<String>> {
420 if path.is_empty() {
421 return Ok(vec![]);
422 }
423
424 if !path.starts_with('/') {
425 return Err(fionn_core::DsonError::InvalidField(format!(
426 "JSON Pointer must start with '/': {path}"
427 )));
428 }
429
430 Ok(path[1..].split('/').map(unescape_json_pointer).collect())
431}
432
433fn unescape_json_pointer(s: &str) -> String {
435 s.replace("~1", "/").replace("~0", "~")
436}
437
438#[must_use]
444pub fn value_to_json(value: &Value) -> String {
445 serde_json::to_string(value).unwrap_or_default()
446}
447
448#[must_use]
450pub fn value_to_json_pretty(value: &Value) -> String {
451 serde_json::to_string_pretty(value).unwrap_or_default()
452}
453
454#[cfg(feature = "yaml")]
460pub fn value_to_yaml(value: &Value) -> Result<String> {
461 serde_yaml::to_string(value).map_err(|e| fionn_core::DsonError::InvalidField(e.to_string()))
462}
463
464#[cfg(feature = "toml")]
472pub fn value_to_toml(value: &Value) -> Result<String> {
473 let toml_value: toml::Value = serde_json::from_value(value.clone())
475 .map_err(|e| fionn_core::DsonError::InvalidField(e.to_string()))?;
476
477 toml::to_string(&toml_value).map_err(|e| fionn_core::DsonError::InvalidField(e.to_string()))
478}
479
480pub fn patch_tape<T: TapeSource>(source_tape: &T, diff: &TapeDiff<'_>) -> Result<Value> {
507 let mut value = tape_to_value(source_tape)?;
508 apply_tape_diff(&mut value, diff)?;
509 Ok(value)
510}
511
512pub fn three_way_patch<S: TapeSource, T: TapeSource>(base: &S, target: &T) -> Result<Value> {
520 use crate::diff_tape::diff_tapes;
521
522 let diff = diff_tapes(base, target)?;
523 patch_tape(base, &diff)
524}
525
526#[cfg(test)]
531mod tests {
532 use super::*;
533 use crate::diff_tape::diff_tapes;
534 use fionn_tape::DsonTape;
535
536 fn parse_json(s: &str) -> DsonTape {
537 DsonTape::parse(s).expect("valid JSON")
538 }
539
540 #[test]
541 fn test_tape_to_value_simple() {
542 let tape = parse_json(r#"{"name": "Alice", "age": 30}"#);
543 let value = tape_to_value(&tape).unwrap();
544
545 assert_eq!(value["name"], "Alice");
546 assert_eq!(value["age"], 30);
547 }
548
549 #[test]
550 fn test_tape_to_value_nested() {
551 let tape = parse_json(r#"{"user": {"name": "Alice", "profile": {"age": 30}}}"#);
552 let value = tape_to_value(&tape).unwrap();
553
554 assert_eq!(value["user"]["name"], "Alice");
555 assert_eq!(value["user"]["profile"]["age"], 30);
556 }
557
558 #[test]
559 fn test_tape_to_value_array() {
560 let tape = parse_json(r"[1, 2, 3, 4, 5]");
561 let value = tape_to_value(&tape).unwrap();
562
563 assert!(value.is_array());
564 assert_eq!(value.as_array().unwrap().len(), 5);
565 assert_eq!(value[0], 1);
566 assert_eq!(value[4], 5);
567 }
568
569 #[test]
570 fn test_tape_to_value_mixed() {
571 let tape = parse_json(r#"{"items": [{"id": 1}, {"id": 2}]}"#);
572 let value = tape_to_value(&tape).unwrap();
573
574 assert_eq!(value["items"][0]["id"], 1);
575 assert_eq!(value["items"][1]["id"], 2);
576 }
577
578 #[test]
579 fn test_apply_tape_diff_replace() {
580 let tape_a = parse_json(r#"{"name": "Alice"}"#);
581 let tape_b = parse_json(r#"{"name": "Bob"}"#);
582
583 let diff = diff_tapes(&tape_a, &tape_b).unwrap();
584 let mut value = tape_to_value(&tape_a).unwrap();
585
586 apply_tape_diff(&mut value, &diff).unwrap();
587
588 assert_eq!(value["name"], "Bob");
589 }
590
591 #[test]
592 fn test_apply_tape_diff_add() {
593 let tape_a = parse_json(r#"{"name": "Alice"}"#);
594 let tape_b = parse_json(r#"{"name": "Alice", "age": 30}"#);
595
596 let diff = diff_tapes(&tape_a, &tape_b).unwrap();
597 let mut value = tape_to_value(&tape_a).unwrap();
598
599 apply_tape_diff(&mut value, &diff).unwrap();
600
601 assert_eq!(value["name"], "Alice");
602 assert_eq!(value["age"], 30);
603 }
604
605 #[test]
606 fn test_apply_tape_diff_remove() {
607 let tape_a = parse_json(r#"{"name": "Alice", "age": 30}"#);
608 let tape_b = parse_json(r#"{"name": "Alice"}"#);
609
610 let diff = diff_tapes(&tape_a, &tape_b).unwrap();
611 let mut value = tape_to_value(&tape_a).unwrap();
612
613 apply_tape_diff(&mut value, &diff).unwrap();
614
615 assert_eq!(value["name"], "Alice");
616 assert!(value.get("age").is_none());
617 }
618
619 #[test]
620 fn test_apply_tape_diff_nested() {
621 let tape_a = parse_json(r#"{"user": {"name": "Alice"}}"#);
622 let tape_b = parse_json(r#"{"user": {"name": "Bob"}}"#);
623
624 let diff = diff_tapes(&tape_a, &tape_b).unwrap();
625 let mut value = tape_to_value(&tape_a).unwrap();
626
627 apply_tape_diff(&mut value, &diff).unwrap();
628
629 assert_eq!(value["user"]["name"], "Bob");
630 }
631
632 #[test]
633 fn test_patch_tape_helper() {
634 let tape_a = parse_json(r#"{"name": "Alice", "count": 1}"#);
635 let tape_b = parse_json(r#"{"name": "Bob", "count": 2}"#);
636
637 let diff = diff_tapes(&tape_a, &tape_b).unwrap();
638 let patched = patch_tape(&tape_a, &diff).unwrap();
639
640 assert_eq!(patched["name"], "Bob");
641 assert_eq!(patched["count"], 2);
642 }
643
644 #[test]
645 fn test_three_way_patch() {
646 let tape_a = parse_json(r#"{"version": 1}"#);
647 let tape_b = parse_json(r#"{"version": 2}"#);
648
649 let result = three_way_patch(&tape_a, &tape_b).unwrap();
650
651 assert_eq!(result["version"], 2);
652 }
653
654 #[test]
655 fn test_roundtrip_to_json() {
656 let tape = parse_json(r#"{"items": [1, 2, 3]}"#);
657 let value = tape_to_value(&tape).unwrap();
658 let json = value_to_json(&value);
659
660 let reparsed: Value = serde_json::from_str(&json).unwrap();
662 assert_eq!(reparsed["items"][0], 1);
663 }
664
665 #[test]
666 fn test_parse_json_pointer() {
667 assert_eq!(parse_json_pointer("").unwrap(), Vec::<String>::new());
668 assert_eq!(parse_json_pointer("/").unwrap(), vec![""]);
669 assert_eq!(parse_json_pointer("/foo").unwrap(), vec!["foo"]);
670 assert_eq!(parse_json_pointer("/foo/bar").unwrap(), vec!["foo", "bar"]);
671 assert_eq!(parse_json_pointer("/a~1b").unwrap(), vec!["a/b"]);
672 assert_eq!(parse_json_pointer("/c~0d").unwrap(), vec!["c~d"]);
673 }
674
675 #[test]
676 fn test_set_at_path_nested() {
677 let mut value = serde_json::json!({});
678
679 set_at_path(&mut value, "/user/name", Value::String("Alice".to_string())).unwrap();
680
681 assert_eq!(value["user"]["name"], "Alice");
682 }
683
684 #[test]
685 fn test_set_at_path_array() {
686 let mut value = serde_json::json!([1, 2, 3]);
687
688 set_at_path(&mut value, "/1", Value::Number(99.into())).unwrap();
689
690 assert_eq!(value[1], 99);
691 }
692
693 #[test]
694 fn test_remove_at_path() {
695 let mut value = serde_json::json!({"a": 1, "b": 2});
696
697 let removed = remove_at_path(&mut value, "/a").unwrap();
698
699 assert_eq!(removed, 1);
700 assert!(value.get("a").is_none());
701 assert_eq!(value["b"], 2);
702 }
703}