1use core::fmt;
40use core::str::FromStr;
41
42use crate::ConfigContent;
43use crate::error::{ConfigError, ConfigResult};
44use crate::key::{Key, KeySegment};
45use crate::value::Value;
46use toml::Value as TomlValue;
47use toml::map::Map;
48
49#[derive(Debug, Clone, PartialEq)]
53pub struct TomlContent {
54 data: TomlValue,
55}
56
57#[allow(dead_code)]
58impl TomlContent {
59 pub fn from_value(value: TomlValue) -> Self {
61 Self { data: value }
62 }
63
64 pub fn as_value(&self) -> &TomlValue {
66 &self.data
67 }
68
69 pub fn into_value(self) -> TomlValue {
71 self.data
72 }
73
74 fn get_value_at_key(&self, key: &Key) -> ConfigResult<&TomlValue> {
75 let mut current = &self.data;
76
77 for segment in key.segments() {
78 match segment {
79 KeySegment::Key(s) => {
80 current = match current {
81 TomlValue::Table(t) => t
82 .get(s)
83 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))?,
84 _ => {
85 return Err(ConfigError::KeyNotFound(key.to_key_string()));
86 }
87 };
88 }
89 KeySegment::Index(i) => {
90 current = match current {
91 TomlValue::Array(arr) => arr
92 .get(*i)
93 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))?,
94 _ => {
95 return Err(ConfigError::KeyNotFound(key.to_key_string()));
96 }
97 };
98 }
99 KeySegment::Attribute(_) => {
100 return Err(ConfigError::InvalidKey(
101 "TOML does not support attributes".to_string(),
102 ));
103 }
104 }
105 }
106
107 Ok(current)
108 }
109
110 fn contains_key_at(&self, key: &Key) -> bool {
111 self.get_value_at_key(key).is_ok()
112 }
113
114 fn keys_at(&self) -> Vec<Key> {
115 match &self.data {
116 TomlValue::Table(t) => t
117 .keys()
118 .filter_map(|k| Key::from_str(&format!(".{k}")).ok())
119 .collect(),
120 TomlValue::Array(arr) => (0..arr.len())
121 .filter_map(|i| Key::from_str(&format!(".[{i}]")).ok())
122 .collect(),
123 _ => vec![],
124 }
125 }
126
127 fn len_at(&self) -> usize {
128 match &self.data {
129 TomlValue::Table(t) => t.len(),
130 TomlValue::Array(arr) => arr.len(),
131 _ => 0,
132 }
133 }
134
135 fn delete_at(&mut self, key: &Key) -> ConfigResult<()> {
136 if key.segments().is_empty() {
137 return Err(ConfigError::DeleteError("Cannot delete root".to_string()));
138 }
139
140 let segments = key.segments();
141 let last_idx = segments.len() - 1;
142
143 let mut current = &mut self.data;
144
145 for (i, segment) in segments.iter().enumerate() {
146 if i == last_idx {
147 match segment {
148 KeySegment::Key(s) => {
149 if let TomlValue::Table(t) = current {
150 drop(t.remove(s));
151 return Ok(());
152 }
153 }
154 KeySegment::Index(idx) => {
155 if let TomlValue::Array(arr) = current {
156 if *idx < arr.len() {
157 drop(arr.remove(*idx));
158 return Ok(());
159 }
160 }
161 }
162 KeySegment::Attribute(_) => {
163 return Err(ConfigError::InvalidKey(
164 "TOML does not support attributes".to_string(),
165 ));
166 }
167 }
168 } else {
169 match segment {
170 KeySegment::Key(s) => {
171 if let TomlValue::Table(t) = current {
172 if let Some(next) = t.get_mut(s) {
173 current = next;
174 } else {
175 return Err(ConfigError::KeyNotFound(key.to_key_string()));
176 }
177 } else {
178 return Err(ConfigError::KeyNotFound(key.to_key_string()));
179 }
180 }
181 KeySegment::Index(idx) => {
182 if let TomlValue::Array(arr) = current {
183 if let Some(next) = arr.get_mut(*idx) {
184 current = next;
185 } else {
186 return Err(ConfigError::KeyNotFound(key.to_key_string()));
187 }
188 } else {
189 return Err(ConfigError::KeyNotFound(key.to_key_string()));
190 }
191 }
192 KeySegment::Attribute(_) => {
193 return Err(ConfigError::InvalidKey(
194 "TOML does not support attributes".to_string(),
195 ));
196 }
197 }
198 }
199 }
200
201 Err(ConfigError::KeyNotFound(key.to_key_string()))
202 }
203
204 fn upsert_at(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
205 if key.segments().is_empty() {
206 self.data = toml_value_from_value(value);
207 return Ok(());
208 }
209
210 let segments = key.segments();
211 let last_idx = segments.len() - 1;
212
213 let mut current = &mut self.data;
214
215 for (i, segment) in segments.iter().enumerate() {
216 if i == last_idx {
217 match segment {
218 KeySegment::Key(s) => {
219 if let TomlValue::Table(t) = current {
220 drop(t.insert(s.clone(), toml_value_from_value(value)));
221 return Ok(());
222 }
223 }
224 KeySegment::Index(idx) => {
225 if let TomlValue::Array(arr) = current {
226 if *idx < arr.len() {
227 arr[*idx] = toml_value_from_value(value);
228 } else if *idx == arr.len() {
229 arr.push(toml_value_from_value(value));
230 } else {
231 return Err(ConfigError::KeyNotFound(key.to_key_string()));
232 }
233 return Ok(());
234 }
235 }
236 KeySegment::Attribute(_) => {
237 return Err(ConfigError::InvalidKey(
238 "TOML does not support attributes".to_string(),
239 ));
240 }
241 }
242 } else {
243 match segment {
244 KeySegment::Key(s) => {
245 let next = match current {
246 TomlValue::Table(t) => {
247 let key_exists = t.contains_key(s);
248 if !key_exists {
249 drop(t.insert(s.clone(), TomlValue::Table(Map::new())));
250 }
251 t.get_mut(s)
252 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))?
253 }
254 _ => {
255 return Err(ConfigError::KeyNotFound(key.to_key_string()));
256 }
257 };
258 current = next;
259 }
260 KeySegment::Index(idx) => {
261 let next = match current {
262 TomlValue::Array(arr) => {
263 if *idx < arr.len() {
264 &mut arr[*idx]
265 } else if *idx == arr.len() {
266 arr.push(TomlValue::String(String::new()));
267 arr.last_mut().ok_or_else(|| {
268 ConfigError::KeyNotFound(key.to_key_string())
269 })?
270 } else {
271 return Err(ConfigError::KeyNotFound(key.to_key_string()));
272 }
273 }
274 _ => {
275 return Err(ConfigError::KeyNotFound(key.to_key_string()));
276 }
277 };
278 current = next;
279 }
280 KeySegment::Attribute(_) => {
281 return Err(ConfigError::InvalidKey(
282 "TOML does not support attributes".to_string(),
283 ));
284 }
285 }
286 }
287 }
288
289 Ok(())
290 }
291}
292
293impl ConfigContent for TomlContent {
294 fn select(&self, key: &Key) -> ConfigResult<Value> {
295 let toml_value = self.get_value_at_key(key)?;
296 Ok(value_from_toml_value(toml_value))
297 }
298
299 fn insert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
300 if self.contains_key_at(key) {
301 return Err(ConfigError::KeyAlreadyExists(key.to_key_string()));
302 }
303 self.upsert_at(key, value)
304 }
305
306 fn update(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
307 if !self.contains_key_at(key) {
308 return Err(ConfigError::KeyDoesNotExist(key.to_key_string()));
309 }
310 self.upsert_at(key, value)
311 }
312
313 fn delete(&mut self, key: &Key) -> ConfigResult<()> {
314 self.delete_at(key)
315 }
316
317 fn upsert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
318 self.upsert_at(key, value)
319 }
320}
321
322impl FromStr for TomlContent {
323 type Err = ConfigError;
324
325 fn from_str(s: &str) -> Result<Self, Self::Err> {
326 let value: toml::Value =
327 toml::from_str(s).map_err(|e| ConfigError::ParseError(e.to_string()))?;
328 Ok(Self { data: value })
329 }
330}
331
332impl fmt::Display for TomlContent {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 let output = toml::to_string_pretty(&self.data).map_err(|_| fmt::Error)?;
335 write!(f, "{}", output)
336 }
337}
338
339fn value_from_toml_value(toml: &TomlValue) -> Value {
340 match toml {
341 TomlValue::String(s) if s.is_empty() => Value::Null,
342 TomlValue::String(s) => Value::String(s.clone()),
343 TomlValue::Integer(i) => Value::Integer(*i),
344 TomlValue::Float(f) => Value::Float(*f),
345 TomlValue::Boolean(b) => Value::Bool(*b),
346 TomlValue::Datetime(dt) => Value::String(dt.to_string()),
347 TomlValue::Array(arr) => Value::Array(arr.iter().map(value_from_toml_value).collect()),
348 TomlValue::Table(t) => Value::Map(
349 t.iter()
350 .map(|(k, v)| (k.clone(), value_from_toml_value(v)))
351 .collect(),
352 ),
353 }
354}
355
356fn toml_value_from_value(value: &Value) -> TomlValue {
357 match value {
358 Value::Null => TomlValue::String(String::new()),
359 Value::Bool(b) => TomlValue::Boolean(*b),
360 Value::Integer(i) => TomlValue::Integer(*i),
361 Value::Float(f) => {
362 if f.is_nan() {
364 TomlValue::String("NaN".to_string())
365 } else if f.is_infinite() {
366 if f.is_sign_positive() {
367 TomlValue::String("Infinity".to_string())
368 } else {
369 TomlValue::String("-Infinity".to_string())
370 }
371 } else {
372 TomlValue::Float(*f)
373 }
374 }
375 Value::String(s) => TomlValue::String(s.clone()),
376 Value::Array(arr) => TomlValue::Array(arr.iter().map(toml_value_from_value).collect()),
377 Value::Map(m) => {
378 let mut t = Map::new();
379 for (k, v) in m {
380 drop(t.insert(k.clone(), toml_value_from_value(v)));
381 }
382 TomlValue::Table(t)
383 }
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_toml_from_str() {
393 let content = TomlContent::from_str("key = \"value\"").expect("test should succeed");
394 assert!(
395 content
396 .select(&Key::from_str(".key").expect("test should succeed"))
397 .is_ok()
398 );
399 }
400
401 #[test]
402 fn test_toml_to_string() {
403 let content = TomlContent::from_str("key = \"value\"").expect("test should succeed");
404 let s = content.to_string();
405 assert!(s.contains("key"));
406 }
407
408 #[test]
409 fn test_toml_select() {
410 let content = TomlContent::from_str("name = \"test\"").expect("test should succeed");
411 let value = content
412 .select(&Key::from_str(".name").expect("test should succeed"))
413 .expect("test should succeed");
414 assert_eq!(value.as_str(), Some("test"));
415 }
416
417 #[test]
418 fn test_toml_select_nested() {
419 let content =
420 TomlContent::from_str("[database]\nurl = \"localhost\"").expect("test should succeed");
421 let value = content
422 .select(&Key::from_str(".database.url").expect("test should succeed"))
423 .expect("test should succeed");
424 assert_eq!(value.as_str(), Some("localhost"));
425 }
426
427 #[test]
428 fn test_toml_insert() {
429 let mut content = TomlContent::from_str("").expect("test should succeed");
430 content
431 .insert(
432 &Key::from_str(".name").expect("test should succeed"),
433 &Value::string("test"),
434 )
435 .expect("test should succeed");
436 let value = content
437 .select(&Key::from_str(".name").expect("test should succeed"))
438 .expect("test should succeed");
439 assert_eq!(value.as_str(), Some("test"));
440 }
441
442 #[test]
443 fn test_toml_update() {
444 let mut content = TomlContent::from_str("name = \"old\"").expect("test should succeed");
445 content
446 .update(
447 &Key::from_str(".name").expect("test should succeed"),
448 &Value::string("new"),
449 )
450 .expect("test should succeed");
451 let value = content
452 .select(&Key::from_str(".name").expect("test should succeed"))
453 .expect("test should succeed");
454 assert_eq!(value.as_str(), Some("new"));
455 }
456
457 #[test]
458 fn test_toml_delete() {
459 let mut content = TomlContent::from_str("name = \"test\"").expect("test should succeed");
460 content
461 .delete(&Key::from_str(".name").expect("test should succeed"))
462 .expect("test should succeed");
463 assert!(
464 content
465 .select(&Key::from_str(".name").expect("test should succeed"))
466 .is_err()
467 );
468 }
469
470 #[test]
471 fn test_toml_upsert() {
472 let mut content = TomlContent::from_str("").expect("test should succeed");
473 content
474 .upsert(
475 &Key::from_str(".name").expect("test should succeed"),
476 &Value::string("test"),
477 )
478 .expect("test should succeed");
479 let value = content
480 .select(&Key::from_str(".name").expect("test should succeed"))
481 .expect("test should succeed");
482 assert_eq!(value.as_str(), Some("test"));
483 }
484
485 #[test]
486 fn test_toml_contains_key() {
487 let content = TomlContent::from_str("name = \"test\"").expect("test should succeed");
488 assert!(
489 content
490 .select(&Key::from_str(".name").expect("test should succeed"))
491 .is_ok()
492 );
493 }
494
495 #[test]
496 fn test_toml_display_is_stable_and_pretty() {
497 let content = TomlContent::from_str("b = 2\n[a]\nx = 1").expect("test should succeed");
498 let first = content.to_string();
499 let second = content.to_string();
500 assert_eq!(first, second);
501 assert!(first.contains("\n"));
502 }
503
504 #[test]
505 fn test_toml_string_basic() {
506 let content = TomlContent::from_str(r#"str = "hello""#).expect("test should succeed");
507 let value = content
508 .select(&Key::from_str(".str").expect("test should succeed"))
509 .expect("test should succeed");
510 assert_eq!(value.as_str(), Some("hello"));
511 }
512
513 #[test]
514 fn test_toml_string_multiline() {
515 let content =
516 TomlContent::from_str("str = '''\nmultiline\nstring'''").expect("test should succeed");
517 let value = content
518 .select(&Key::from_str(".str").expect("test should succeed"))
519 .expect("test should succeed");
520 assert!(
521 value
522 .as_str()
523 .expect("test should succeed")
524 .contains("multiline")
525 );
526 }
527
528 #[test]
529 fn test_toml_integer_decimal() {
530 let content = TomlContent::from_str("int = 42").expect("test should succeed");
531 let value = content
532 .select(&Key::from_str(".int").expect("test should succeed"))
533 .expect("test should succeed");
534 assert_eq!(value.as_integer(), Some(42));
535 }
536
537 #[test]
538 fn test_toml_integer_negative() {
539 let content = TomlContent::from_str("int = -17").expect("test should succeed");
540 let value = content
541 .select(&Key::from_str(".int").expect("test should succeed"))
542 .expect("test should succeed");
543 assert_eq!(value.as_integer(), Some(-17));
544 }
545
546 #[test]
547 fn test_toml_integer_underscore() {
548 let content = TomlContent::from_str("int = 1_000").expect("test should succeed");
549 let value = content
550 .select(&Key::from_str(".int").expect("test should succeed"))
551 .expect("test should succeed");
552 assert_eq!(value.as_integer(), Some(1000));
553 }
554
555 #[test]
556 fn test_toml_float_basic() {
557 let content = TomlContent::from_str("flt = 3.14").expect("test should succeed");
558 let value = content
559 .select(&Key::from_str(".flt").expect("test should succeed"))
560 .expect("test should succeed");
561 let f = value.as_float().expect("test should succeed");
562 assert!((f - 3.14).abs() < 1e-6);
563 }
564
565 #[test]
566 fn test_toml_float_scientific() {
567 let content = TomlContent::from_str("flt = 1e10").expect("test should succeed");
568 let value = content
569 .select(&Key::from_str(".flt").expect("test should succeed"))
570 .expect("test should succeed");
571 let f = value.as_float().expect("test should succeed");
572 assert!((f - 1e10).abs() < 1.0);
573 }
574
575 #[test]
576 fn test_toml_boolean() {
577 let content = TomlContent::from_str("t = true\nf = false").expect("test should succeed");
578 assert_eq!(
579 content
580 .select(&Key::from_str(".t").expect("test should succeed"))
581 .expect("test should succeed")
582 .as_bool(),
583 Some(true)
584 );
585 assert_eq!(
586 content
587 .select(&Key::from_str(".f").expect("test should succeed"))
588 .expect("test should succeed")
589 .as_bool(),
590 Some(false)
591 );
592 }
593
594 #[test]
595 fn test_toml_array() {
596 let content = TomlContent::from_str("arr = [1, 2, 3]").expect("test should succeed");
597 let value = content
598 .select(&Key::from_str(".arr[0]").expect("test should succeed"))
599 .expect("test should succeed");
600 assert_eq!(value.as_integer(), Some(1));
601 }
602
603 #[test]
604 fn test_toml_array_of_strings() {
605 let content =
606 TomlContent::from_str(r#"arr = ["a", "b", "c"]"#).expect("test should succeed");
607 let value = content
608 .select(&Key::from_str(".arr[1]").expect("test should succeed"))
609 .expect("test should succeed");
610 assert_eq!(value.as_str(), Some("b"));
611 }
612
613 #[test]
614 fn test_toml_inline_table() {
615 let content =
616 TomlContent::from_str(r#"point = {x = 1, y = 2}"#).expect("test should succeed");
617 let value = content
618 .select(&Key::from_str(".point.x").expect("test should succeed"))
619 .expect("test should succeed");
620 assert_eq!(value.as_integer(), Some(1));
621 }
622
623 #[test]
624 fn test_toml_nested_table() {
625 let content = TomlContent::from_str("[server]\nhost = \"localhost\"\n[server.port]")
626 .expect("test should succeed");
627 let value = content
628 .select(&Key::from_str(".server.host").expect("test should succeed"))
629 .expect("test should succeed");
630 assert_eq!(value.as_str(), Some("localhost"));
631 }
632
633 #[test]
634 fn test_toml_array_of_tables() {
635 let content =
636 TomlContent::from_str("[[users]]\nname = \"Alice\"\n[[users]]\nname = \"Bob\"")
637 .expect("test should succeed");
638 let value = content
639 .select(&Key::from_str(".users[0].name").expect("test should succeed"))
640 .expect("test should succeed");
641 assert_eq!(value.as_str(), Some("Alice"));
642 }
643
644 #[test]
645 fn test_toml_delete_nested() {
646 let mut content = TomlContent::from_str("[database]\nhost = \"localhost\"\nport = 5432")
647 .expect("test should succeed");
648 content
649 .delete(&Key::from_str(".database.port").expect("test should succeed"))
650 .expect("test should succeed");
651 assert!(
652 content
653 .select(&Key::from_str(".database.port").expect("test should succeed"))
654 .is_err()
655 );
656 }
657
658 #[test]
659 fn test_toml_update_nested() {
660 let mut content =
661 TomlContent::from_str("[database]\nhost = \"old\"").expect("test should succeed");
662 content
663 .update(
664 &Key::from_str(".database.host").expect("test should succeed"),
665 &Value::string("new"),
666 )
667 .expect("test should succeed");
668 let value = content
669 .select(&Key::from_str(".database.host").expect("test should succeed"))
670 .expect("test should succeed");
671 assert_eq!(value.as_str(), Some("new"));
672 }
673
674 #[test]
675 fn test_toml_special_float() {
676 let content = TomlContent::from_str("inf = inf\nneg_inf = -inf\nnan = nan")
677 .expect("test should succeed");
678 assert!(
679 content
680 .select(&Key::from_str(".inf").expect("test should succeed"))
681 .expect("test should succeed")
682 .as_float()
683 .expect("test should succeed")
684 .is_infinite()
685 );
686 assert!(
687 content
688 .select(&Key::from_str(".neg_inf").expect("test should succeed"))
689 .expect("test should succeed")
690 .as_float()
691 .expect("test should succeed")
692 .is_sign_negative()
693 );
694 assert!(
695 content
696 .select(&Key::from_str(".nan").expect("test should succeed"))
697 .expect("test should succeed")
698 .as_float()
699 .expect("test should succeed")
700 .is_nan()
701 );
702 }
703}