vika_cli/config/
model.rs

1use serde::{Deserialize, Serialize};
2
3/// Main configuration structure for vika-cli.
4///
5/// Represents the `.vika.json` configuration file that controls
6/// code generation behavior, output directories, and module selection.
7///
8/// # Example
9///
10/// ```no_run
11/// use vika_cli::Config;
12///
13/// let config = Config::default();
14/// println!("Root directory: {}", config.root_dir);
15/// ```
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Config {
18    #[serde(rename = "$schema", default = "default_schema")]
19    pub schema: String,
20
21    #[serde(default = "default_root_dir")]
22    pub root_dir: String,
23
24    #[serde(default)]
25    pub schemas: SchemasConfig,
26
27    #[serde(default)]
28    pub apis: ApisConfig,
29
30    #[serde(default)]
31    pub modules: ModulesConfig,
32
33    #[serde(default)]
34    pub generation: GenerationConfig,
35
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub spec_path: Option<String>,
38}
39
40pub fn default_schema() -> String {
41    "https://raw.githubusercontent.com/vikarno/vika-cli/main/schema/vika-config.schema.json"
42        .to_string()
43}
44
45fn default_root_dir() -> String {
46    "src".to_string()
47}
48
49/// Configuration for schema generation (TypeScript types and Zod schemas).
50///
51/// Controls where schemas are generated and how they are named.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SchemasConfig {
54    #[serde(default = "default_schemas_output")]
55    pub output: String,
56
57    #[serde(default = "default_naming")]
58    pub naming: String,
59}
60
61fn default_naming() -> String {
62    "PascalCase".to_string()
63}
64
65fn default_schemas_output() -> String {
66    "src/schemas".to_string()
67}
68
69/// Configuration for API client generation.
70///
71/// Controls API client output location, style, base URL, and header strategy.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ApisConfig {
74    #[serde(default = "default_apis_output")]
75    pub output: String,
76
77    #[serde(default = "default_style")]
78    pub style: String,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub base_url: Option<String>,
82
83    #[serde(default = "default_header_strategy")]
84    pub header_strategy: String,
85}
86
87fn default_header_strategy() -> String {
88    "consumerInjected".to_string()
89}
90
91fn default_apis_output() -> String {
92    "src/apis".to_string()
93}
94
95fn default_style() -> String {
96    "fetch".to_string()
97}
98
99/// Configuration for module selection and filtering.
100///
101/// Controls which OpenAPI tags/modules are included or excluded from generation.
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103pub struct ModulesConfig {
104    #[serde(default)]
105    pub ignore: Vec<String>,
106
107    #[serde(default)]
108    pub selected: Vec<String>,
109}
110
111/// Configuration for generation behavior and preferences.
112///
113/// Controls caching, backups, and conflict resolution strategy.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct GenerationConfig {
116    #[serde(default = "default_enable_cache")]
117    pub enable_cache: bool,
118
119    #[serde(default = "default_enable_backup")]
120    pub enable_backup: bool,
121
122    #[serde(default = "default_conflict_strategy")]
123    pub conflict_strategy: String,
124}
125
126fn default_enable_cache() -> bool {
127    true
128}
129
130fn default_enable_backup() -> bool {
131    false
132}
133
134fn default_conflict_strategy() -> String {
135    "ask".to_string()
136}
137
138impl Default for GenerationConfig {
139    fn default() -> Self {
140        Self {
141            enable_cache: default_enable_cache(),
142            enable_backup: default_enable_backup(),
143            conflict_strategy: default_conflict_strategy(),
144        }
145    }
146}
147
148impl Default for Config {
149    fn default() -> Self {
150        Self {
151            schema: default_schema(),
152            root_dir: default_root_dir(),
153            schemas: SchemasConfig {
154                output: default_schemas_output(),
155                naming: default_naming(),
156            },
157            apis: ApisConfig {
158                output: default_apis_output(),
159                style: default_style(),
160                base_url: None,
161                header_strategy: default_header_strategy(),
162            },
163            modules: ModulesConfig {
164                ignore: vec![],
165                selected: vec![],
166            },
167            generation: GenerationConfig::default(),
168            spec_path: None,
169        }
170    }
171}
172
173impl Default for SchemasConfig {
174    fn default() -> Self {
175        Self {
176            output: default_schemas_output(),
177            naming: default_naming(),
178        }
179    }
180}
181
182impl Default for ApisConfig {
183    fn default() -> Self {
184        Self {
185            output: default_apis_output(),
186            style: default_style(),
187            base_url: None,
188            header_strategy: default_header_strategy(),
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_load_default_config() {
199        let config = Config::default();
200        assert_eq!(config.root_dir, "src");
201        assert_eq!(config.schemas.output, "src/schemas");
202        assert_eq!(config.apis.output, "src/apis");
203        assert_eq!(config.apis.style, "fetch");
204        assert!(!config.schema.is_empty());
205    }
206
207    #[test]
208    fn test_config_serialization() {
209        let config = Config::default();
210        let json = serde_json::to_string_pretty(&config).unwrap();
211
212        assert!(json.contains("\"root_dir\""));
213        assert!(json.contains("\"schemas\""));
214        assert!(json.contains("\"apis\""));
215        assert!(json.contains("\"$schema\""));
216    }
217
218    #[test]
219    fn test_config_deserialization() {
220        let json = r#"
221        {
222            "$schema": "https://example.com/schema.json",
223            "root_dir": "test",
224            "schemas": {
225                "output": "test/schemas",
226                "naming": "camelCase"
227            },
228            "apis": {
229                "output": "test/apis",
230                "style": "fetch",
231                "header_strategy": "bearerToken"
232            },
233            "modules": {
234                "ignore": ["test"],
235                "selected": []
236            }
237        }
238        "#;
239
240        let config: Config = serde_json::from_str(json).unwrap();
241        assert_eq!(config.root_dir, "test");
242        assert_eq!(config.schemas.output, "test/schemas");
243        assert_eq!(config.schemas.naming, "camelCase");
244        assert_eq!(config.apis.header_strategy, "bearerToken");
245        assert_eq!(config.modules.ignore, vec!["test"]);
246    }
247
248    #[test]
249    fn test_schemas_config_default() {
250        let config = SchemasConfig::default();
251        assert_eq!(config.output, "src/schemas");
252        assert_eq!(config.naming, "PascalCase");
253    }
254
255    #[test]
256    fn test_apis_config_default() {
257        let config = ApisConfig::default();
258        assert_eq!(config.output, "src/apis");
259        assert_eq!(config.style, "fetch");
260        assert_eq!(config.header_strategy, "consumerInjected");
261        assert!(config.base_url.is_none());
262    }
263
264    #[test]
265    fn test_config_with_base_url() {
266        let mut config = Config::default();
267        config.apis.base_url = Some("/api/v1".to_string());
268
269        let json = serde_json::to_string_pretty(&config).unwrap();
270        assert!(json.contains("\"base_url\""));
271        assert!(json.contains("/api/v1"));
272    }
273
274    #[test]
275    fn test_config_schema_field() {
276        let config = Config::default();
277        let json = serde_json::to_string_pretty(&config).unwrap();
278
279        // Check that $schema is included
280        assert!(json.contains("\"$schema\""));
281    }
282
283    #[test]
284    fn test_generation_config_defaults() {
285        let config = Config::default();
286        assert!(config.generation.enable_cache);
287        assert!(!config.generation.enable_backup);
288        assert_eq!(config.generation.conflict_strategy, "ask");
289    }
290
291    #[test]
292    fn test_config_with_generation_settings() {
293        let json = r#"
294        {
295            "$schema": "https://example.com/schema.json",
296            "root_dir": "test",
297            "schemas": {
298                "output": "test/schemas",
299                "naming": "camelCase"
300            },
301            "apis": {
302                "output": "test/apis",
303                "style": "fetch",
304                "header_strategy": "bearerToken"
305            },
306            "modules": {
307                "ignore": ["test"],
308                "selected": []
309            },
310            "generation": {
311                "enable_cache": false,
312                "enable_backup": true,
313                "conflict_strategy": "force"
314            }
315        }
316        "#;
317
318        let config: Config = serde_json::from_str(json).unwrap();
319        assert!(!config.generation.enable_cache);
320        assert!(config.generation.enable_backup);
321        assert_eq!(config.generation.conflict_strategy, "force");
322    }
323}