headless_lms_mcp/engine/page_content/
pointer.rs1use serde_json::{Map, Value};
4
5use crate::engine::error::{EditError, EditErrorCode};
6use crate::engine::path::unescape_segment;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub(super) enum PointerWrite {
10 Add,
11 Replace,
12}
13
14fn with_parent<T>(
17 attributes: &mut Map<String, Value>,
18 pointer: &str,
19 edit: impl FnOnce(&mut Value, &str) -> Result<T, EditError>,
20) -> Result<T, EditError> {
21 let (parent, last) = pointer.rsplit_once('/').ok_or_else(|| {
22 EditError::new(
23 EditErrorCode::InvalidPath,
24 format!("attribute path {pointer:?} doesn't start with /"),
25 )
26 })?;
27 let mut holder = Value::Object(std::mem::take(attributes));
28 let result = match holder.pointer_mut(parent) {
29 Some(value) => edit(value, &unescape_segment(last)),
30 None => Err(EditError::new(
31 EditErrorCode::NotFound,
32 format!("attributes{parent} isn't set"),
33 )
34 .with_fix("replace the enclosing attribute with a whole object instead")),
35 };
36 if let Value::Object(object) = holder {
37 *attributes = object;
38 }
39 result
40}
41
42pub(super) fn pointer_write(
43 attributes: &mut Map<String, Value>,
44 pointer: &str,
45 value: Value,
46 mode: PointerWrite,
47) -> Result<(), EditError> {
48 with_parent(attributes, pointer, |parent, last| match parent {
49 Value::Object(object) => {
50 object.insert(last.to_string(), value);
51 Ok(())
52 }
53 Value::Array(array) => {
54 if last == "-" {
55 if mode == PointerWrite::Add {
56 array.push(value);
57 return Ok(());
58 }
59 return Err(EditError::new(
60 EditErrorCode::InvalidPath,
61 "\"-\" appends, so it only works with add",
62 ));
63 }
64 let index: usize = last.parse().map_err(|_| {
65 EditError::new(
66 EditErrorCode::InvalidPath,
67 format!("\"{last}\" isn't an array index"),
68 )
69 })?;
70 match mode {
71 PointerWrite::Replace if index < array.len() => {
72 array[index] = value;
73 Ok(())
74 }
75 PointerWrite::Add if index <= array.len() => {
76 array.insert(index, value);
77 Ok(())
78 }
79 _ => Err(EditError::new(
80 EditErrorCode::NotFound,
81 format!(
82 "index {index} is past the end of an array of {}",
83 array.len()
84 ),
85 )),
86 }
87 }
88 _ => Err(EditError::new(
89 EditErrorCode::InvalidPath,
90 format!("attributes{pointer} goes into a value that isn't an object or array"),
91 )),
92 })
93}
94
95pub(super) fn pointer_remove(
96 attributes: &mut Map<String, Value>,
97 pointer: &str,
98) -> Result<(), EditError> {
99 with_parent(attributes, pointer, |parent, last| {
100 let removed = match parent {
101 Value::Object(object) => object.remove(last).is_some(),
102 Value::Array(array) => match last.parse::<usize>() {
103 Ok(index) if index < array.len() => {
104 array.remove(index);
105 true
106 }
107 _ => false,
108 },
109 _ => false,
110 };
111 if removed {
112 Ok(())
113 } else {
114 Err(EditError::new(
115 EditErrorCode::NotFound,
116 format!("attributes{pointer} isn't set"),
117 ))
118 }
119 })
120}