use std::path::Path;
use serde::Deserialize;
use crate::embeddings::Provider;
use crate::index::CTX_DIR;
pub const CONFIG_FILE: &str = "config.toml";
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct CtxConfig {
pub embedding: EmbeddingConfig,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct EmbeddingConfig {
pub provider: Option<Provider>,
pub model: Option<String>,
pub host: Option<String>,
}
impl CtxConfig {
pub fn load(root: &Path) -> Self {
Self::load_file(&root.join(CTX_DIR).join(CONFIG_FILE))
}
pub fn load_file(path: &Path) -> Self {
let text = match std::fs::read_to_string(path) {
Ok(text) => text,
Err(_) => return Self::default(), };
match toml::from_str(&text) {
Ok(config) => config,
Err(e) => {
eprintln!("Warning: ignoring malformed {} ({e})", path.display());
Self::default()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn write_temp(contents: &str) -> tempfile::NamedTempFile {
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(contents.as_bytes()).unwrap();
f
}
#[test]
fn missing_file_is_default() {
let cfg = CtxConfig::load_file(Path::new("/nonexistent/.ctx/config.toml"));
assert!(cfg.embedding.provider.is_none());
assert!(cfg.embedding.model.is_none());
}
#[test]
fn parses_embedding_section() {
let f = write_temp(
r#"
[embedding]
provider = "ollama"
model = "qwen3-embedding:8b"
"#,
);
let cfg = CtxConfig::load_file(f.path());
assert_eq!(cfg.embedding.provider, Some(Provider::Ollama));
assert_eq!(cfg.embedding.model.as_deref(), Some("qwen3-embedding:8b"));
assert!(cfg.embedding.host.is_none());
}
#[test]
fn unknown_keys_ignored() {
let f = write_temp(
r#"
[embedding]
provider = "openai"
[future_section]
whatever = true
"#,
);
let cfg = CtxConfig::load_file(f.path());
assert_eq!(cfg.embedding.provider, Some(Provider::Openai));
}
#[test]
fn malformed_file_is_default() {
let f = write_temp("this is not valid toml : : :");
let cfg = CtxConfig::load_file(f.path());
assert!(cfg.embedding.provider.is_none());
}
#[test]
fn config_provides_resolution_default() {
assert_eq!(
Provider::resolve(Some(Provider::Local), false, Some(Provider::Ollama)),
Provider::Local
);
assert_eq!(
Provider::resolve(None, false, Some(Provider::Ollama)),
Provider::Ollama
);
assert_eq!(Provider::resolve(None, false, None), Provider::Local);
assert_eq!(
Provider::resolve(None, true, Some(Provider::Ollama)),
Provider::Openai
);
}
}