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 mut config = Config::default();
79        config.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                modules: ModulesConfig::default(),
86            },
87            SpecEntry {
88                name: "orders".to_string(),
89                path: "specs/orders.json".to_string(),
90                schemas: SchemasConfig::default(),
91                apis: ApisConfig::default(),
92                modules: ModulesConfig::default(),
93            },
94        ];
95
96        let specs = list_specs(&config);
97        assert_eq!(specs.len(), 2);
98        assert_eq!(specs[0].name, "auth");
99        assert_eq!(specs[1].name, "orders");
100    }
101
102    #[test]
103    fn test_get_spec_by_name_single_mode() {
104        let mut config = Config::default();
105        config.specs = vec![SpecEntry {
106            name: "default".to_string(),
107            path: "openapi.json".to_string(),
108            schemas: SchemasConfig::default(),
109            apis: ApisConfig::default(),
110            modules: ModulesConfig::default(),
111        }];
112
113        let spec = get_spec_by_name(&config, "default").unwrap();
114        assert_eq!(spec.name, "default");
115        assert_eq!(spec.path, "openapi.json");
116
117        let result = get_spec_by_name(&config, "auth");
118        assert!(result.is_err());
119    }
120
121    #[test]
122    fn test_get_spec_by_name_multi_mode() {
123        let mut config = Config::default();
124        config.specs = vec![
125            SpecEntry {
126                name: "auth".to_string(),
127                path: "specs/auth.yaml".to_string(),
128                schemas: SchemasConfig::default(),
129                apis: ApisConfig::default(),
130                modules: ModulesConfig::default(),
131            },
132            SpecEntry {
133                name: "orders".to_string(),
134                path: "specs/orders.json".to_string(),
135                schemas: SchemasConfig::default(),
136                apis: ApisConfig::default(),
137                modules: ModulesConfig::default(),
138            },
139        ];
140
141        let spec = get_spec_by_name(&config, "auth").unwrap();
142        assert_eq!(spec.name, "auth");
143        assert_eq!(spec.path, "specs/auth.yaml");
144
145        let result = get_spec_by_name(&config, "nonexistent");
146        assert!(result.is_err());
147    }
148
149    #[test]
150    fn test_resolve_spec_selection_all_specs() {
151        let mut config = Config::default();
152        config.specs = vec![
153            SpecEntry {
154                name: "auth".to_string(),
155                path: "specs/auth.yaml".to_string(),
156                schemas: SchemasConfig::default(),
157                apis: ApisConfig::default(),
158                modules: ModulesConfig::default(),
159            },
160            SpecEntry {
161                name: "orders".to_string(),
162                path: "specs/orders.json".to_string(),
163                schemas: SchemasConfig::default(),
164                apis: ApisConfig::default(),
165                modules: ModulesConfig::default(),
166            },
167        ];
168
169        let specs = resolve_spec_selection(&config, None, true).unwrap();
170        assert_eq!(specs.len(), 2);
171    }
172
173    #[test]
174    fn test_resolve_spec_selection_specific_spec() {
175        let mut config = Config::default();
176        config.specs = vec![
177            SpecEntry {
178                name: "auth".to_string(),
179                path: "specs/auth.yaml".to_string(),
180                schemas: SchemasConfig::default(),
181                apis: ApisConfig::default(),
182                modules: ModulesConfig::default(),
183            },
184            SpecEntry {
185                name: "orders".to_string(),
186                path: "specs/orders.json".to_string(),
187                schemas: SchemasConfig::default(),
188                apis: ApisConfig::default(),
189                modules: ModulesConfig::default(),
190            },
191        ];
192
193        let specs = resolve_spec_selection(&config, Some("auth".to_string()), false).unwrap();
194        assert_eq!(specs.len(), 1);
195        assert_eq!(specs[0].name, "auth");
196    }
197
198    #[test]
199    fn test_resolve_spec_selection_single_mode() {
200        let mut config = Config::default();
201        config.specs = vec![SpecEntry {
202            name: "default".to_string(),
203            path: "openapi.json".to_string(),
204            schemas: SchemasConfig::default(),
205            apis: ApisConfig::default(),
206            modules: ModulesConfig::default(),
207        }];
208
209        let specs = resolve_spec_selection(&config, None, false).unwrap();
210        assert_eq!(specs.len(), 1);
211        assert_eq!(specs[0].name, "default");
212    }
213}