ralph_workflow/cli/init/config_generation/
validation.rs1use crate::config::loader::{load_config_from_path_with_env, ConfigLoadWithValidationError};
6use crate::config::validation::ConfigValidationError;
7use crate::config::{ConfigEnvironment, RealConfigEnvironment};
8use crate::logger::Colors;
9
10pub fn handle_check_config_with<R: ConfigEnvironment>(
25 colors: Colors,
26 env: &R,
27 verbose: bool,
28) -> anyhow::Result<bool> {
29 println!(
30 "{}Checking configuration...{}",
31 colors.dim(),
32 colors.reset()
33 );
34 println!();
35
36 let (config, merged_unified, warnings) = match load_config_from_path_with_env(None, env) {
38 Ok(result) => result,
39 Err(ConfigLoadWithValidationError::ValidationErrors(errors)) => {
40 println!("{}Validation errors found:{}", colors.red(), colors.reset());
42 println!();
43
44 let mut global_errors: Vec<_> = Vec::new();
46 let mut local_errors: Vec<_> = Vec::new();
47 let mut other_errors: Vec<_> = Vec::new();
48
49 for error in &errors {
50 let path_str = error.file().to_string_lossy();
51 if path_str.contains(".config") {
52 global_errors.push(error);
53 } else if path_str.contains(".agent") {
54 local_errors.push(error);
55 } else {
56 other_errors.push(error);
57 }
58 }
59
60 if !global_errors.is_empty() {
61 println!(
62 "{}~/.config/ralph-workflow.toml:{}",
63 colors.yellow(),
64 colors.reset()
65 );
66 for error in global_errors {
67 print_config_error(colors, error);
68 }
69 println!();
70 }
71
72 if !local_errors.is_empty() {
73 println!(
74 "{}.agent/ralph-workflow.toml:{}",
75 colors.yellow(),
76 colors.reset()
77 );
78 for error in local_errors {
79 print_config_error(colors, error);
80 }
81 println!();
82 }
83
84 if !other_errors.is_empty() {
85 for error in other_errors {
86 println!(
87 "{}{}:{}",
88 colors.yellow(),
89 error.file().display(),
90 colors.reset()
91 );
92 print_config_error(colors, error);
93 println!();
94 }
95 }
96
97 println!(
98 "{}Fix these errors and try again.{}",
99 colors.red(),
100 colors.reset()
101 );
102
103 return Err(anyhow::anyhow!("Configuration validation failed"));
105 }
106 Err(ConfigLoadWithValidationError::Io(e)) => {
107 return Err(anyhow::anyhow!("Failed to read config file: {}", e));
108 }
109 };
110
111 if !warnings.is_empty() {
113 println!("{}Warnings:{}", colors.yellow(), colors.reset());
114 for warning in &warnings {
115 println!(" {}", warning);
116 }
117 println!();
118 }
119
120 let global_path = env.unified_config_path();
122 let local_path = env.local_config_path();
123
124 println!("{}Configuration sources:{}", colors.cyan(), colors.reset());
125
126 if let Some(path) = global_path {
127 let exists = env.file_exists(&path);
128 println!(
129 " Global: {} {}",
130 path.display(),
131 if exists {
132 format!("{}(active){}", colors.green(), colors.reset())
133 } else {
134 format!("{}(not found){}", colors.dim(), colors.reset())
135 }
136 );
137 }
138
139 if let Some(path) = local_path {
140 let exists = env.file_exists(&path);
141 println!(
142 " Local: {} {}",
143 path.display(),
144 if exists {
145 format!("{}(active){}", colors.green(), colors.reset())
146 } else {
147 format!("{}(not found){}", colors.dim(), colors.reset())
148 }
149 );
150 }
151
152 println!();
153
154 println!("{}Effective settings:{}", colors.cyan(), colors.reset());
156 println!(" Verbosity: {}", config.verbosity as u8);
157 println!(" Developer iterations: {}", config.developer_iters);
158 println!(" Reviewer reviews: {}", config.reviewer_reviews);
159 println!(" Interactive: {}", config.behavior.interactive);
160 println!(" Isolation mode: {}", config.isolation_mode);
161
162 if verbose {
163 println!();
164 println!(
165 "{}Full merged configuration:{}",
166 colors.cyan(),
167 colors.reset()
168 );
169 if let Some(unified) = merged_unified {
170 let toml_str = toml::to_string_pretty(&unified)
171 .unwrap_or_else(|_| "Error serializing config".to_string());
172 println!("{}", toml_str);
173 }
174 }
175
176 println!();
177 println!("{}Configuration valid{}", colors.green(), colors.reset());
178
179 Ok(true)
180}
181
182fn print_config_error(colors: Colors, error: &ConfigValidationError) {
184 match error {
185 ConfigValidationError::TomlSyntax { error, .. } => {
186 println!(" {}TOML syntax error:{}", colors.red(), colors.reset());
187 println!(" {}", error);
188 }
189 ConfigValidationError::UnknownKey {
190 key, suggestion, ..
191 } => {
192 println!(" {}Unknown key '{}'{}", colors.red(), key, colors.reset());
193 if let Some(s) = suggestion {
194 println!(
195 " {}Did you mean '{}'?{}",
196 colors.dim(),
197 s,
198 colors.reset()
199 );
200 }
201 }
202 ConfigValidationError::InvalidValue { key, message, .. } => {
203 println!(
204 " {}Invalid value for '{}'{}",
205 colors.red(),
206 key,
207 colors.reset()
208 );
209 println!(" {}", message);
210 }
211 }
212}
213
214pub fn handle_check_config(colors: Colors, verbose: bool) -> anyhow::Result<bool> {
218 handle_check_config_with(colors, &RealConfigEnvironment, verbose)
219}