agent_first_data/document/format/
toml.rs1use crate::document::{DocumentError, DocumentResult, Value};
4
5pub fn set_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 {
34 let table =
35 current
36 .as_table_like_mut()
37 .ok_or_else(|| DocumentError::UnsupportedOperation {
38 format: "TOML".to_string(),
39 operation: "set".to_string(),
40 detail: "cannot address a key inside a non-table TOML value".to_string(),
41 })?;
42 if table.get(parent).filter(|item| !item.is_none()).is_none() {
43 let mut created = toml_edit::Table::new();
44 created.set_implicit(true);
45 table.insert(parent, toml_edit::Item::Table(created));
46 }
47 }
48 current = current
49 .get_mut(parent)
50 .filter(|item| !item.is_none())
51 .ok_or_else(|| DocumentError::PathNotFound {
52 path: path.to_string(),
53 })?;
54 }
55 let table = current
56 .as_table_like_mut()
57 .ok_or_else(|| DocumentError::UnsupportedOperation {
58 format: "TOML".to_string(),
59 operation: "set".to_string(),
60 detail: "cannot address a key inside a non-table TOML value".to_string(),
61 })?;
62 match table.get_mut(last).filter(|item| !item.is_none()) {
63 Some(target) => {
64 if !target.is_value() {
65 return Err(DocumentError::UnsupportedOperation {
66 format: "TOML".to_string(),
67 operation: "set".to_string(),
68 detail: "only existing scalar TOML values are supported by the document editor"
69 .to_string(),
70 });
71 }
72 let decor = target.as_value().map(|value| value.decor().clone());
73 *target = item;
74 if let (Some(decor), Some(value)) = (decor, target.as_value_mut()) {
75 *value.decor_mut() = decor;
76 }
77 }
78 None => {
81 table.insert(last, item);
82 }
83 }
84 Ok(document.to_string())
85}
86
87pub fn unset_preserving(content: &str, path: &str) -> DocumentResult<String> {
89 let segments = crate::document::parse_path(path)?;
90 if segments.iter().any(|segment| segment.contains(['.', '\\'])) {
91 return Err(DocumentError::UnsupportedOperation {
92 format: "TOML".to_string(),
93 operation: "unset".to_string(),
94 detail: "escaped TOML keys are not supported by the current document path adapter"
95 .to_string(),
96 });
97 }
98 let (last, parents) = segments.split_last().ok_or(DocumentError::EmptyPath)?;
99 let mut document =
100 content
101 .parse::<toml_edit::DocumentMut>()
102 .map_err(|error| DocumentError::ParseError {
103 format: "TOML".to_string(),
104 detail: error.to_string(),
105 })?;
106 let mut current = document.as_item_mut();
107 for parent in parents {
108 current = current
109 .get_mut(parent)
110 .ok_or_else(|| DocumentError::PathNotFound {
111 path: path.to_string(),
112 })?;
113 }
114 let table = current
115 .as_table_mut()
116 .ok_or_else(|| DocumentError::UnsupportedOperation {
117 format: "TOML".to_string(),
118 operation: "unset".to_string(),
119 detail: "only table entries can be removed by the current TOML editor".to_string(),
120 })?;
121 if table.remove(last).is_none() {
122 return Err(DocumentError::PathNotFound {
123 path: path.to_string(),
124 });
125 }
126 Ok(document.to_string())
127}
128
129fn toml_item(value: &Value) -> DocumentResult<toml_edit::Item> {
130 match value {
131 Value::Null => Err(DocumentError::UnsupportedOperation {
132 format: "TOML".to_string(),
133 operation: "set".to_string(),
134 detail: "TOML has no null value".to_string(),
135 }),
136 Value::Bool(value) => Ok(toml_edit::value(*value)),
137 Value::Integer(value) => Ok(toml_edit::value(*value)),
138 Value::Unsigned(value) => i64::try_from(*value).map(toml_edit::value).map_err(|_| {
139 DocumentError::UnsupportedOperation {
140 format: "TOML".to_string(),
141 operation: "set".to_string(),
142 detail: "unsigned integer exceeds TOML i64 range".to_string(),
143 }
144 }),
145 Value::Float(value) if value.is_finite() => Ok(toml_edit::value(*value)),
146 Value::Float(_) => Err(DocumentError::UnsupportedOperation {
147 format: "TOML".to_string(),
148 operation: "set".to_string(),
149 detail: "non-finite TOML float is not representable".to_string(),
150 }),
151 Value::Number(text) if value.is_float() => text
160 .parse::<f64>()
161 .ok()
162 .filter(|value| value.is_finite())
163 .map(toml_edit::value)
164 .ok_or_else(|| DocumentError::UnsupportedOperation {
165 format: "TOML".to_string(),
166 operation: "set".to_string(),
167 detail: format!("float literal `{text}` is not representable in TOML"),
168 }),
169 Value::Number(text) => Err(DocumentError::UnsupportedOperation {
170 format: "TOML".to_string(),
171 operation: "set".to_string(),
172 detail: format!("integer literal `{text}` exceeds TOML's 64-bit integer range"),
173 }),
174 Value::String(value) => Ok(toml_edit::value(value.clone())),
175 Value::Array(_) | Value::Object(_) => Err(DocumentError::UnsupportedOperation {
176 format: "TOML".to_string(),
177 operation: "set".to_string(),
178 detail: "collection mutation requires a dedicated TOML editor".to_string(),
179 }),
180 }
181}
182
183pub fn load(content: &str) -> DocumentResult<Value> {
184 toml::from_str::<toml::Value>(content)
185 .map(value_to_our_value)
186 .map_err(|e| DocumentError::ParseError {
187 format: "TOML".to_string(),
188 detail: e.to_string(),
189 })
190}
191
192pub fn save(value: &Value) -> DocumentResult<String> {
193 let toml_val = our_value_to_toml_value(value)?;
194 toml::to_string_pretty(&toml_val).map_err(|e| DocumentError::ParseError {
195 format: "TOML".to_string(),
196 detail: e.to_string(),
197 })
198}
199
200fn value_to_our_value(v: toml::Value) -> Value {
201 match v {
202 toml::Value::Boolean(b) => Value::Bool(b),
203 toml::Value::Integer(i) => Value::Integer(i),
204 toml::Value::Float(f) => Value::Float(f),
205 toml::Value::String(s) => Value::String(s),
206 toml::Value::Array(a) => Value::Array(a.into_iter().map(value_to_our_value).collect()),
207 toml::Value::Table(t) => {
208 let map = t
209 .into_iter()
210 .map(|(k, v)| (k, value_to_our_value(v)))
211 .collect();
212 Value::Object(map)
213 }
214 toml::Value::Datetime(dt) => Value::String(dt.to_string()),
215 }
216}
217
218fn our_value_to_toml_value(v: &Value) -> DocumentResult<toml::Value> {
219 match v {
220 Value::Null => Err(DocumentError::UnsupportedOperation {
221 format: "TOML".to_string(),
222 operation: "save".to_string(),
223 detail: "TOML has no null value".to_string(),
224 }),
225 Value::Bool(b) => Ok(toml::Value::Boolean(*b)),
226 Value::Integer(i) => Ok(toml::Value::Integer(*i)),
227 Value::Unsigned(i) => i64::try_from(*i).map(toml::Value::Integer).map_err(|_| {
228 DocumentError::UnsupportedOperation {
229 format: "TOML".to_string(),
230 operation: "save".to_string(),
231 detail: "unsigned integer exceeds TOML i64 range".to_string(),
232 }
233 }),
234 Value::Float(f) => Ok(toml::Value::Float(*f)),
235 Value::Number(text) if v.is_float() => text
236 .parse::<f64>()
237 .ok()
238 .filter(|value| value.is_finite())
239 .map(toml::Value::Float)
240 .ok_or_else(|| DocumentError::UnsupportedOperation {
241 format: "TOML".to_string(),
242 operation: "save".to_string(),
243 detail: format!("float literal `{text}` is not representable in TOML"),
244 }),
245 Value::Number(text) => Err(DocumentError::UnsupportedOperation {
246 format: "TOML".to_string(),
247 operation: "save".to_string(),
248 detail: format!("integer literal `{text}` exceeds TOML's 64-bit integer range"),
249 }),
250 Value::String(s) => Ok(toml::Value::String(s.clone())),
251 Value::Array(a) => {
252 let arr = a
253 .iter()
254 .map(our_value_to_toml_value)
255 .collect::<DocumentResult<Vec<_>>>()?;
256 Ok(toml::Value::Array(arr))
257 }
258 Value::Object(o) => {
259 let mut table = toml::map::Map::new();
260 for (k, v) in o {
261 table.insert(k.clone(), our_value_to_toml_value(v)?);
262 }
263 Ok(toml::Value::Table(table))
264 }
265 }
266}