vika_cli/specs/
manager.rs

1use crate::config::model::{Config, SpecEntry};
2use crate::error::{ConfigError, GenerationError, Result};
3use dialoguer::Select;
4
5/// Lists all specs from the config
6pub fn list_specs(config: &Config) -> Vec<SpecEntry> {
7    config.specs.clone()
8}
9
10/// Gets a spec by name from the config
11pub fn get_spec_by_name(config: &Config, name: &str) -> Result<SpecEntry> {
12    config
13        .specs
14        .iter()
15        .find(|s| s.name == name)
16        .cloned()
17        .ok_or_else(|| {
18            let available: Vec<String> = config.specs.iter().map(|s| s.name.clone()).collect();
19            GenerationError::SpecNotFound {
20                name: name.to_string(),
21                available,
22            }
23            .into()
24        })
25}
26
27/// Resolves which specs to generate based on CLI flags and config
28pub fn resolve_spec_selection(
29    config: &Config,
30    cli_spec: Option<String>,
31    all_specs: bool,
32) -> Result<Vec<SpecEntry>> {
33    let specs = list_specs(config);
34
35    if specs.is_empty() {
36        return Err(ConfigError::NoSpecDefined.into());
37    }
38
39    if all_specs {
40        // Generate all specs
41        Ok(specs)
42    } else if let Some(spec_name) = cli_spec {
43        // Generate specific spec by name
44        let spec = get_spec_by_name(config, &spec_name)?;
45        Ok(vec![spec])
46    } else if specs.len() == 1 {
47        // Single spec: use it automatically
48        Ok(specs)
49    } else {
50        // Multiple specs but no flag: prompt user
51        let spec_names: Vec<String> = specs.iter().map(|s| s.name.clone()).collect();
52        let selection = Select::new()
53            .with_prompt("Which spec do you want to generate?")
54            .items(&spec_names)
55            .interact()
56            .map_err(|e| GenerationError::InvalidOperation {
57                message: format!("Failed to get user selection: {}", e),
58            })?;
59
60        let selected_spec =
61            specs
62                .get(selection)
63                .ok_or_else(|| GenerationError::InvalidOperation {
64                    message: "Invalid selection".to_string(),
65                })?;
66
67        Ok(vec![selected_spec.clone()])
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::config::model::{ApisConfig, ModulesConfig, SchemasConfig};
75
76    #[test]
77    fn test_list_specs_multi_mode() {
78        let config = Config {
79            specs: vec![
80                SpecEntry {
81                    name: "auth".to_string(),
82                    path: "specs/auth.yaml".to_string(),
83                    schemas: SchemasConfig::default(),
84                    apis: ApisConfig::default(),
85                    hooks: None,
86                    modules: ModulesConfig::default(),
87                },
88                SpecEntry {
89                    name: "orders".to_string(),
90                    path: "specs/orders.json".to_string(),
91                    schemas: SchemasConfig::default(),
92                    apis: ApisConfig::default(),
93                    hooks: None,
94                    modules: ModulesConfig::default(),
95                },
96            ],
97            ..Default::default()
98        };
99
100        let specs = list_specs(&config);
101        assert_eq!(specs.len(), 2);
102        assert_eq!(specs[0].name, "auth");
103        assert_eq!(specs[1].name, "orders");
104    }
105
106    #[test]
107    fn test_get_spec_by_name_single_mode() {
108        let config = Config {
109            specs: vec![SpecEntry {
110                name: "default".to_string(),
111                path: "openapi.json".to_string(),
112                schemas: SchemasConfig::default(),
113                apis: ApisConfig::default(),
114                hooks: None,
115                modules: ModulesConfig::default(),
116            }],
117            ..Default::default()
118        };
119
120        let spec = get_spec_by_name(&config, "default").unwrap();
121        assert_eq!(spec.name, "default");
122        assert_eq!(spec.path, "openapi.json");
123
124        let result = get_spec_by_name(&config, "auth");
125        assert!(result.is_err());
126    }
127
128    #[test]
129    fn test_get_spec_by_name_multi_mode() {
130        let config = Config {
131            specs: vec![
132                SpecEntry {
133                    name: "auth".to_string(),
134                    path: "specs/auth.yaml".to_string(),
135                    schemas: SchemasConfig::default(),
136                    apis: ApisConfig::default(),
137                    hooks: None,
138                    modules: ModulesConfig::default(),
139                },
140                SpecEntry {
141                    name: "orders".to_string(),
142                    path: "specs/orders.json".to_string(),
143                    schemas: SchemasConfig::default(),
144                    apis: ApisConfig::default(),
145                    hooks: None,
146                    modules: ModulesConfig::default(),
147                },
148            ],
149            ..Default::default()
150        };
151
152        let spec = get_spec_by_name(&config, "auth").unwrap();
153        assert_eq!(spec.name, "auth");
154        assert_eq!(spec.path, "specs/auth.yaml");
155
156        let result = get_spec_by_name(&config, "nonexistent");
157        assert!(result.is_err());
158    }
159
160    #[test]
161    fn test_resolve_spec_selection_all_specs() {
162        let config = Config {
163            specs: vec![
164                SpecEntry {
165                    name: "auth".to_string(),
166                    path: "specs/auth.yaml".to_string(),
167                    schemas: SchemasConfig::default(),
168                    apis: ApisConfig::default(),
169                    hooks: None,
170                    modules: ModulesConfig::default(),
171                },
172                SpecEntry {
173                    name: "orders".to_string(),
174                    path: "specs/orders.json".to_string(),
175                    schemas: SchemasConfig::default(),
176                    apis: ApisConfig::default(),
177                    hooks: None,
178                    modules: ModulesConfig::default(),
179                },
180            ],
181            ..Default::default()
182        };
183
184        let specs = resolve_spec_selection(&config, None, true).unwrap();
185        assert_eq!(specs.len(), 2);
186    }
187
188    #[test]
189    fn test_resolve_spec_selection_specific_spec() {
190        let config = Config {
191            specs: vec![
192                SpecEntry {
193                    name: "auth".to_string(),
194                    path: "specs/auth.yaml".to_string(),
195                    schemas: SchemasConfig::default(),
196                    apis: ApisConfig::default(),
197                    hooks: None,
198                    modules: ModulesConfig::default(),
199                },
200                SpecEntry {
201                    name: "orders".to_string(),
202                    path: "specs/orders.json".to_string(),
203                    schemas: SchemasConfig::default(),
204                    apis: ApisConfig::default(),
205                    hooks: None,
206                    modules: ModulesConfig::default(),
207                },
208            ],
209            ..Default::default()
210        };
211
212        let specs = resolve_spec_selection(&config, Some("auth".to_string()), false).unwrap();
213        assert_eq!(specs.len(), 1);
214        assert_eq!(specs[0].name, "auth");
215    }
216
217    #[test]
218    fn test_resolve_spec_selection_single_mode() {
219        let config = Config {
220            specs: vec![SpecEntry {
221                name: "default".to_string(),
222                path: "openapi.json".to_string(),
223                schemas: SchemasConfig::default(),
224                apis: ApisConfig::default(),
225                hooks: None,
226                modules: ModulesConfig::default(),
227            }],
228            ..Default::default()
229        };
230
231        let specs = resolve_spec_selection(&config, None, false).unwrap();
232        assert_eq!(specs.len(), 1);
233        assert_eq!(specs[0].name, "default");
234    }
235}