1use std::any::type_name;
2
3use documented::{Documented, DocumentedFields};
4use toml_edit::{ArrayOfTables, Decor, Item, RawString, Table};
5use tracing::warn;
6
7use crate::error::ConfigError;
8
9pub fn append_docs_as_toml_comments(decor: &mut Decor, docs: &str) {
19 let old_prefix = decor.prefix().and_then(RawString::as_str);
20 let last_line = old_prefix.and_then(|prefix| prefix.lines().last());
21
22 let comments = docs
23 .lines()
24 .map(|l| {
25 if l.is_empty() {
26 "#\n".into()
27 } else {
28 format!("# {l}\n")
29 }
30 })
31 .collect();
32
33 let new_prefix = match (old_prefix, last_line) {
34 (None | Some(""), None) => comments,
35 (None, Some(_)) => unreachable!(),
36 (Some(_), None) => unreachable!(),
37 (Some(prefix), Some("")) => format!("{prefix}{comments}"),
38 (Some(prefix), Some(_)) => format!("{prefix}#\n{comments}"),
39 };
40 decor.set_prefix(new_prefix);
41}
42
43pub fn annotate_toml_table<T>(table: &mut Table, is_root: bool) -> Result<(), ConfigError>
56where
57 T: Documented + DocumentedFields,
58{
59 if !is_root {
60 append_docs_as_toml_comments(table.decor_mut(), T::DOCS);
61 }
62
63 for (mut key_mut, value_item) in table.iter_mut() {
64 let key_str = key_mut.get();
65 match T::get_field_docs(key_str) {
66 Ok(docs) => match value_item {
67 Item::None => {
68 return Err(ConfigError::Custom(format!(
69 "Encountered TomlEditItem::None for key '{key_str}' unexpectedly",
70 )))
71 }
72 Item::Value(_) => append_docs_as_toml_comments(key_mut.leaf_decor_mut(), docs),
73 Item::Table(sub_table) => append_docs_as_toml_comments(sub_table.decor_mut(), docs),
74 Item::ArrayOfTables(array) => {
75 let first_table = array
76 .iter_mut()
77 .next()
78 .expect("Array of table should not be empty");
79 append_docs_as_toml_comments(first_table.decor_mut(), docs);
80 }
81 },
82 Err(_) => {
83 warn!(
84 "Field '{}' found in TOML but not in struct '{}' for documentation lookup, or it's a complex case like flatten.",
85 key_str,
86 type_name::<T>()
87 );
88 }
89 }
90 }
91
92 Ok(())
93}
94
95pub fn annotate_toml_array_of_tables<T>(array: &mut ArrayOfTables) -> Result<(), ConfigError>
105where
106 T: Documented + DocumentedFields,
107{
108 if let Some(first_table) = array.iter_mut().next() {
109 annotate_toml_table::<T>(first_table, false).map_err(|err| {
110 ConfigError::Custom(format!("Failed to annotate first table in array: {err}"))
111 })?;
112 }
113 Ok(())
114}