use crate::config::{
CartogConfig, DatabaseConfig, EmbeddingConfig, IndexConfig, LocalEmbeddingConfig, McpConfig,
OllamaConfig, OpenAiConfig, ProjectConfig, RagConfig, RerankerConfig, SecurityConfig,
};
pub(crate) fn is_unknown_field_error(e: &toml::de::Error) -> bool {
unknown_field_name(e).is_some()
}
pub(crate) fn unknown_field_name(e: &toml::de::Error) -> Option<String> {
let msg = e.to_string();
let rest = msg.split_once("unknown field `")?.1;
let (name, _) = rest.split_once('`')?;
Some(name.to_string())
}
fn section_unknown_field<S: serde::de::DeserializeOwned>(
section: &toml::value::Table,
) -> Option<String> {
let rendered = toml::to_string(&toml::Value::Table(section.clone())).ok()?;
match toml::from_str::<S>(&rendered) {
Err(ref e) => unknown_field_name(e),
Ok(_) => None,
}
}
pub(crate) fn section_has_unknown_field<S: serde::de::DeserializeOwned>(
section: &toml::value::Table,
) -> bool {
section_unknown_field::<S>(section).is_some()
}
fn strip_unknown_in_section<S: serde::de::DeserializeOwned>(section: &mut toml::value::Table) {
for _ in 0..=section.len() {
let Some(name) = section_unknown_field::<S>(section) else {
return;
};
match section.get(&name) {
Some(v) if !v.is_table() => {
section.remove(&name);
}
_ => return,
}
}
}
fn strip_unknown_in_subtable<S: serde::de::DeserializeOwned>(
section: &mut toml::value::Table,
name: &str,
) {
if let Some(toml::Value::Table(sub)) = section.get_mut(name) {
strip_unknown_in_section::<S>(sub);
}
}
pub(crate) fn reparse_ignoring_unknown_keys(text: &str) -> Option<CartogConfig> {
let mut raw: toml::value::Table = toml::from_str(text).ok()?;
if let Some(toml::Value::Table(remote)) = raw.get("remote") {
if section_has_unknown_field::<crate::config::RemoteConfig>(remote) {
return None;
}
}
if let Some(toml::Value::Table(lsp)) = raw.get("lsp") {
for value in lsp.values() {
if let toml::Value::Table(lang) = value {
if section_has_unknown_field::<crate::config::LspLangConfig>(lang) {
return None;
}
}
}
}
for (name, value) in raw.iter_mut() {
let toml::Value::Table(section) = value else {
continue;
};
strip_section_by_name(name.as_str(), section);
}
let rendered = toml::to_string(&raw).ok()?;
toml::from_str::<CartogConfig>(&rendered).ok()
}
fn strip_section_by_name(name: &str, section: &mut toml::value::Table) {
match name {
"database" => strip_unknown_in_section::<DatabaseConfig>(section),
"embedding" => {
strip_unknown_in_subtable::<LocalEmbeddingConfig>(section, "local");
strip_unknown_in_subtable::<OllamaConfig>(section, "ollama");
strip_unknown_in_subtable::<OpenAiConfig>(section, "openai");
strip_unknown_in_section::<EmbeddingConfig>(section);
}
"reranker" => strip_unknown_in_section::<RerankerConfig>(section),
"rag" => strip_unknown_in_section::<RagConfig>(section),
"security" => strip_unknown_in_section::<SecurityConfig>(section),
"index" => strip_unknown_in_section::<IndexConfig>(section),
"project" => strip_unknown_in_section::<ProjectConfig>(section),
"mcp" => strip_unknown_in_section::<McpConfig>(section),
_ => {}
}
}