vtcode_config/loader/
builder.rs1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4
5use crate::loader::layers::{ConfigLayerEntry, ConfigLayerSource};
6use crate::loader::manager::ConfigManager;
7
8#[derive(Debug, Clone, Default)]
10pub struct ConfigBuilder {
11 workspace: Option<PathBuf>,
12 config_file: Option<PathBuf>,
13 cli_overrides: Vec<(String, toml::Value)>,
14}
15
16impl ConfigBuilder {
17 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn workspace(mut self, path: PathBuf) -> Self {
24 self.workspace = Some(path);
25 self
26 }
27
28 pub fn config_file(mut self, path: PathBuf) -> Self {
30 self.config_file = Some(path);
31 self
32 }
33
34 pub fn cli_override(mut self, key: String, value: toml::Value) -> Self {
36 self.cli_overrides.push((key, value));
37 self
38 }
39
40 pub fn cli_overrides(mut self, overrides: &[(String, String)]) -> Self {
44 for (key, value) in overrides {
45 let toml_value = value
46 .parse::<toml::Value>()
47 .unwrap_or_else(|_| toml::Value::String(value.clone()));
48 self.cli_overrides.push((key.clone(), toml_value));
49 }
50 self
51 }
52
53 pub fn build(self) -> Result<ConfigManager> {
55 let workspace = self
56 .workspace
57 .clone()
58 .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
59
60 let mut manager = if let Some(config_file) = self.config_file {
61 ConfigManager::load_from_file(config_file)?
62 } else {
63 ConfigManager::load_from_workspace(workspace)?
64 };
65
66 if !self.cli_overrides.is_empty() {
67 let mut runtime_toml = toml::Table::new();
68 for (key, value) in self.cli_overrides {
69 Self::insert_dotted_key(&mut runtime_toml, &key, value);
70 }
71
72 let runtime_layer =
73 ConfigLayerEntry::new(ConfigLayerSource::Runtime, toml::Value::Table(runtime_toml));
74
75 manager.layer_stack.push(runtime_layer);
76
77 let effective_toml = manager.layer_stack.effective_config();
79 manager.config = effective_toml
80 .try_into()
81 .context("Failed to deserialize effective configuration after runtime overrides")?;
82 manager
83 .config
84 .validate()
85 .context("Configuration failed validation after runtime overrides")?;
86 }
87
88 Ok(manager)
89 }
90
91 pub(crate) fn insert_dotted_key(table: &mut toml::Table, key: &str, value: toml::Value) {
92 let parts: Vec<&str> = key.split('.').collect();
93 let mut current = table;
94 for (i, part) in parts.iter().enumerate() {
95 if i == parts.len() - 1 {
96 current.insert(part.to_string(), value);
97 return;
98 }
99
100 if !current.contains_key(*part) || !current[*part].is_table() {
101 current.insert(part.to_string(), toml::Value::Table(toml::Table::new()));
102 }
103
104 current = current
105 .get_mut(*part)
106 .and_then(|v| v.as_table_mut())
107 .expect("Value must be a table");
108 }
109 }
110}