dynamic_cli/plugin/builtin/
config.rs1use crate::config::{validate_config, CommandsConfig};
12use crate::context::ExecutionContext;
13use crate::executor::CommandHandler;
14use crate::parser::ParsedArgs;
15use crate::plugin::Plugin;
16use crate::Result;
17
18pub struct ConfigPlugin {
55 config: Option<CommandsConfig>,
57}
58
59impl ConfigPlugin {
60 pub fn new() -> Self {
71 Self { config: None }
72 }
73
74 pub fn with_config(mut self, config: CommandsConfig) -> Self {
101 self.config = Some(config);
102 self
103 }
104}
105
106impl Default for ConfigPlugin {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl Plugin for ConfigPlugin {
113 fn name(&self) -> &str {
114 "config"
115 }
116
117 fn version(&self) -> &str {
118 env!("CARGO_PKG_VERSION")
119 }
120
121 fn description(&self) -> &str {
122 "Show/validate the loaded YAML config, without restarting (feature-gated, #47)"
123 }
124
125 fn handlers(&self) -> Vec<(String, Box<dyn CommandHandler>)> {
126 vec![
127 (
128 "config_show".to_string(),
129 Box::new(ConfigShowHandler {
130 config: self.config.clone(),
131 }),
132 ),
133 (
134 "config_validate".to_string(),
135 Box::new(ConfigValidateHandler {
136 config: self.config.clone(),
137 }),
138 ),
139 ]
140 }
141}
142
143struct ConfigShowHandler {
145 config: Option<CommandsConfig>,
146}
147
148impl CommandHandler for ConfigShowHandler {
149 fn execute(&self, _ctx: &mut dyn ExecutionContext, _args: &ParsedArgs) -> Result<()> {
150 match &self.config {
151 Some(cfg) => match serde_yaml::to_string(cfg) {
152 Ok(yaml) => println!("{yaml}"),
153 Err(e) => println!("Failed to render config as YAML: {e}"),
154 },
155 None => println!("No configuration attached to this application."),
156 }
157 Ok(())
158 }
159}
160
161struct ConfigValidateHandler {
164 config: Option<CommandsConfig>,
165}
166
167impl CommandHandler for ConfigValidateHandler {
168 fn execute(&self, _ctx: &mut dyn ExecutionContext, _args: &ParsedArgs) -> Result<()> {
169 match &self.config {
170 Some(cfg) => match validate_config(cfg) {
171 Ok(()) => println!("Configuration is valid."),
172 Err(e) => println!("Configuration is invalid: {e}"),
173 },
174 None => println!("No configuration attached to this application."),
175 }
176 Ok(())
177 }
178}
179
180#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::config::schema::Metadata;
188 use std::any::Any;
189
190 #[derive(Default)]
191 struct TestContext;
192
193 impl ExecutionContext for TestContext {
194 fn as_any(&self) -> &dyn Any {
195 self
196 }
197 fn as_any_mut(&mut self) -> &mut dyn Any {
198 self
199 }
200 }
201
202 fn test_config() -> CommandsConfig {
203 CommandsConfig {
204 metadata: Metadata {
205 version: "2.0.0".to_string(),
206 prompt: "testapp".to_string(),
207 prompt_suffix: " > ".to_string(),
208 },
209 commands: vec![],
210 global_options: vec![],
211 }
212 }
213
214 #[test]
215 fn test_config_plugin_metadata() {
216 let p = ConfigPlugin::new();
217 assert_eq!(p.name(), "config");
218 assert!(!p.version().is_empty());
219 assert!(!p.description().is_empty());
220 }
221
222 #[test]
223 fn test_config_plugin_default() {
224 let p = ConfigPlugin::default();
225 assert_eq!(p.name(), "config");
226 }
227
228 #[test]
229 fn test_config_plugin_handler_names() {
230 let handlers = ConfigPlugin::new().handlers();
231 assert_eq!(handlers.len(), 2);
232 let names: Vec<&str> = handlers.iter().map(|(n, _)| n.as_str()).collect();
233 assert!(names.contains(&"config_show"));
234 assert!(names.contains(&"config_validate"));
235 }
236
237 #[test]
238 fn test_config_plugin_with_config() {
239 let plugin = ConfigPlugin::new().with_config(test_config());
240 assert!(plugin.config.is_some());
241 assert_eq!(plugin.config.unwrap().metadata.version, "2.0.0");
242 }
243
244 #[test]
245 fn test_config_show_executes_with_config() {
246 let plugin = ConfigPlugin::new().with_config(test_config());
247 let handlers = plugin.handlers();
248 let (name, handler) = &handlers[0];
249 assert_eq!(name, "config_show");
250 let mut ctx = TestContext;
251 assert!(handler
252 .execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
253 .is_ok());
254 }
255
256 #[test]
257 fn test_config_show_executes_without_config() {
258 let handlers = ConfigPlugin::new().handlers();
259 let (_, handler) = &handlers[0];
260 let mut ctx = TestContext;
261 assert!(handler
262 .execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
263 .is_ok());
264 }
265
266 #[test]
267 fn test_config_validate_executes_with_valid_config() {
268 let plugin = ConfigPlugin::new().with_config(test_config());
269 let handlers = plugin.handlers();
270 let (name, handler) = &handlers[1];
271 assert_eq!(name, "config_validate");
272 let mut ctx = TestContext;
273 assert!(handler
274 .execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
275 .is_ok());
276 }
277
278 #[test]
279 fn test_config_validate_executes_without_config() {
280 let handlers = ConfigPlugin::new().handlers();
281 let (_, handler) = &handlers[1];
282 let mut ctx = TestContext;
283 assert!(handler
284 .execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
285 .is_ok());
286 }
287
288 #[test]
289 fn test_config_plugin_is_send_sync() {
290 fn assert_send_sync<T: Send + Sync>(_: T) {}
291 assert_send_sync(ConfigPlugin::new());
292 }
293}