agent_first_data/document/format/
toml.rs1use crate::document::{DocumentError, DocumentResult, Value};
4
5pub fn set_scalar_preserving(content: &str, path: &str, value: &Value) -> DocumentResult<String> {
9 let segments = crate::document::parse_path(path)?;
10 if segments.iter().any(|segment| segment.contains(['.', '\\'])) {
11 return Err(DocumentError::UnsupportedOperation {
12 format: "TOML".to_string(),
13 operation: "set".to_string(),
14 detail: "escaped TOML keys are not supported by the current document path adapter"
15 .to_string(),
16 });
17 }
18 let mut document =
19 content
20 .parse::<toml_edit::DocumentMut>()
21 .map_err(|error| DocumentError::ParseError {
22 format: "TOML".to_string(),
23 detail: error.to_string(),
24 })?;
25 let item = toml_item(value)?;
26 let (last, parents) = segments.split_last().ok_or(DocumentError::EmptyPath)?;
27 let mut current = document.as_item_mut();
28 for parent in parents {
29 current = current
32 .get_mut(parent)
33 .filter(|item| !item.is_none())
34 .ok_or_else(|| DocumentError::PathNotFound {
35 path: path.to_string(),
36 })?;
37 }
38 let table = current
39 .as_table_like_mut()
40 .ok_or_else(|| DocumentError::UnsupportedOperation {
41 format: "TOML".to_string(),
42 operation: "set".to_string(),
43 detail: "cannot address a key inside a non-table TOML value".to_string(),
44 })?;
45 match table.get_mut(last).filter(|item| !item.is_none()) {
46 Some(target) => {
47 if !target.is_value() {
48 return Err(DocumentError::UnsupportedOperation {
49 format: "TOML".to_string(),
50 operation: "set".to_string(),
51 detail: "only existing scalar TOML values are supported by the document editor"
52 .to_string(),
53 });
54 }
55 let decor = target.as_value().map(|value| value.decor().clone());
56 *target = item;
57 if let (Some(decor), Some(value)) = (decor, target.as_value_mut()) {
58 *value.decor_mut() = decor;
59 }
60 }
61 None => {
64 table.insert(last, item);
65 }
66 }
67 Ok(document.to_string())
68}
69
70pub fn unset_preserving(content: &str, path: &str) -> DocumentResult<String> {
72 let segments = crate::document::parse_path(path)?;
73 if segments.iter().any(|segment| segment.contains(['.', '\\'])) {
74 return Err(DocumentError::UnsupportedOperation {
75 format: "TOML".to_string(),
76 operation: "unset".to_string(),
77 detail: "escaped TOML keys are not supported by the current document path adapter"
78 .to_string(),
79 });
80 }
81 let (last, parents) = segments.split_last().ok_or(DocumentError::EmptyPath)?;
82 let mut document =
83 content
84 .parse::<toml_edit::DocumentMut>()
85 .map_err(|error| DocumentError::ParseError {
86 format: "TOML".to_string(),
87 detail: error.to_string(),
88 })?;
89 let mut current = document.as_item_mut();
90 for parent in parents {
91 current = current
92 .get_mut(parent)
93 .ok_or_else(|| DocumentError::PathNotFound {
94 path: path.to_string(),
95 })?;
96 }
97 let table = current
98 .as_table_mut()
99 .ok_or_else(|| DocumentError::UnsupportedOperation {
100 format: "TOML".to_string(),
101 operation: "unset".to_string(),
102 detail: "only table entries can be removed by the current TOML editor".to_string(),
103 })?;
104 if table.remove(last).is_none() {
105 return Err(DocumentError::PathNotFound {
106 path: path.to_string(),
107 });
108 }
109 Ok(document.to_string())
110}
111
112fn toml_item(value: &Value) -> DocumentResult<toml_edit::Item> {
113 match value {
114 Value::Null => Err(DocumentError::UnsupportedOperation {
115 format: "TOML".to_string(),
116 operation: "set".to_string(),
117 detail: "TOML has no null value".to_string(),
118 }),
119 Value::Bool(value) => Ok(toml_edit::value(*value)),
120 Value::Integer(value) => Ok(toml_edit::value(*value)),
121 Value::Unsigned(value) => i64::try_from(*value).map(toml_edit::value).map_err(|_| {
122 DocumentError::UnsupportedOperation {
123 format: "TOML".to_string(),
124 operation: "set".to_string(),
125 detail: "unsigned integer exceeds TOML i64 range".to_string(),
126 }
127 }),
128 Value::Float(value) if value.is_finite() => Ok(toml_edit::value(*value)),
129 Value::Float(_) => Err(DocumentError::UnsupportedOperation {
130 format: "TOML".to_string(),
131 operation: "set".to_string(),
132 detail: "non-finite TOML float is not representable".to_string(),
133 }),
134 Value::String(value) => Ok(toml_edit::value(value.clone())),
135 Value::Array(_) | Value::Object(_) => Err(DocumentError::UnsupportedOperation {
136 format: "TOML".to_string(),
137 operation: "set".to_string(),
138 detail: "collection mutation requires a dedicated TOML editor".to_string(),
139 }),
140 }
141}
142
143pub fn load(content: &str) -> DocumentResult<Value> {
144 toml::from_str::<toml::Value>(content)
145 .map(value_to_our_value)
146 .map_err(|e| DocumentError::ParseError {
147 format: "TOML".to_string(),
148 detail: e.to_string(),
149 })
150}
151
152pub fn save(value: &Value) -> DocumentResult<String> {
153 let toml_val = our_value_to_toml_value(value)?;
154 toml::to_string_pretty(&toml_val).map_err(|e| DocumentError::ParseError {
155 format: "TOML".to_string(),
156 detail: e.to_string(),
157 })
158}
159
160fn value_to_our_value(v: toml::Value) -> Value {
161 match v {
162 toml::Value::Boolean(b) => Value::Bool(b),
163 toml::Value::Integer(i) => Value::Integer(i),
164 toml::Value::Float(f) => Value::Float(f),
165 toml::Value::String(s) => Value::String(s),
166 toml::Value::Array(a) => Value::Array(a.into_iter().map(value_to_our_value).collect()),
167 toml::Value::Table(t) => {
168 let map = t
169 .into_iter()
170 .map(|(k, v)| (k, value_to_our_value(v)))
171 .collect();
172 Value::Object(map)
173 }
174 toml::Value::Datetime(dt) => Value::String(dt.to_string()),
175 }
176}
177
178fn our_value_to_toml_value(v: &Value) -> DocumentResult<toml::Value> {
179 match v {
180 Value::Null => Err(DocumentError::UnsupportedOperation {
181 format: "TOML".to_string(),
182 operation: "save".to_string(),
183 detail: "TOML has no null value".to_string(),
184 }),
185 Value::Bool(b) => Ok(toml::Value::Boolean(*b)),
186 Value::Integer(i) => Ok(toml::Value::Integer(*i)),
187 Value::Unsigned(i) => i64::try_from(*i).map(toml::Value::Integer).map_err(|_| {
188 DocumentError::UnsupportedOperation {
189 format: "TOML".to_string(),
190 operation: "save".to_string(),
191 detail: "unsigned integer exceeds TOML i64 range".to_string(),
192 }
193 }),
194 Value::Float(f) => Ok(toml::Value::Float(*f)),
195 Value::String(s) => Ok(toml::Value::String(s.clone())),
196 Value::Array(a) => {
197 let arr = a
198 .iter()
199 .map(our_value_to_toml_value)
200 .collect::<DocumentResult<Vec<_>>>()?;
201 Ok(toml::Value::Array(arr))
202 }
203 Value::Object(o) => {
204 let mut table = toml::map::Map::new();
205 for (k, v) in o {
206 table.insert(k.clone(), our_value_to_toml_value(v)?);
207 }
208 Ok(toml::Value::Table(table))
209 }
210 }
211}