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