Skip to main content

openapi_nexus_config/
config.rs

1//! Configuration structures for OpenAPI Nexus
2
3use std::collections::HashMap;
4
5use crate::cli::{CliArgs, Commands};
6use crate::config_file::ConfigFile;
7use crate::errors::ConfigError;
8use crate::global_config::GlobalConfig;
9use crate::loader::ConfigLoader;
10use openapi_nexus_common::GeneratorType;
11
12/// Unified configuration structure that works for both file and CLI configs
13#[derive(Debug, Clone)]
14pub struct Config {
15    /// Input file path (CLI only)
16    pub input: String,
17    /// Global settings
18    pub global: GlobalConfig,
19    /// Generator-specific configurations stored as TOML tables
20    pub generators: HashMap<GeneratorType, toml::value::Table>,
21}
22
23impl Config {
24    /// Load configuration from CLI arguments and file system
25    ///
26    /// This method handles:
27    /// - Loading config file from specified path or auto-discovery
28    /// - Parsing generator config overrides from CLI
29    /// - Merging all configurations with proper precedence
30    ///
31    /// Precedence: CLI overrides > CLI global args > Env vars > Config file > Defaults
32    pub fn load(cli_args: &CliArgs) -> Result<Self, ConfigError> {
33        // Load config file (if exists)
34        let config_file = match &cli_args.command {
35            Commands::Generate {
36                config: Some(path), ..
37            } => Some(ConfigLoader::load_from_file(path)?),
38            Commands::Generate { .. } => ConfigLoader::discover_config_file()
39                .and_then(|path| ConfigLoader::load_from_file(&path).ok()),
40        };
41
42        // Merge configurations
43        Self::merge(config_file.as_ref(), cli_args)
44    }
45
46    /// Merge CLI arguments with config file, applying precedence rules
47    /// Precedence: CLI overrides > Config file > Defaults
48    fn merge(config_file: Option<&ConfigFile>, cli_args: &CliArgs) -> Result<Self, ConfigError> {
49        let mut config = Config {
50            input: String::new(),
51            global: GlobalConfig::default(),
52            generators: HashMap::new(),
53        };
54
55        // Extract generator configs from config file
56        if let Some(config_file) = config_file {
57            config.merge_file(config_file);
58        }
59
60        // Extract CLI configs
61        let (input, cli_global) = match &cli_args.command {
62            Commands::Generate { input, global, .. } => (input, global),
63        };
64        if input.is_empty() {
65            return Err(ConfigError::Validation(
66                "Input is required and cannot be empty".to_string(),
67            ));
68        }
69        let generator_overrides = cli_args.command.parse_generator_overrides()?;
70
71        config.input = input.clone();
72        config.merge_global(cli_global);
73        config.merge_generator_configs(&generator_overrides);
74        Ok(config)
75    }
76
77    fn merge_file(&mut self, config_file: &ConfigFile) {
78        self.merge_global(&config_file.global);
79        self.merge_generator_configs(&config_file.generators);
80    }
81
82    fn merge_global(&mut self, global: &GlobalConfig) {
83        if global.output.is_some() {
84            self.global.output = global.output.clone();
85        }
86        if global.generators.is_some() {
87            self.global.generators = global.generators.clone();
88        }
89    }
90
91    fn merge_generator_configs(
92        &mut self,
93        generator_configs: &HashMap<GeneratorType, toml::value::Table>,
94    ) {
95        for (generator, table_rhs) in generator_configs {
96            if let Some(table_lhs) = self.generators.get_mut(generator) {
97                for (key, value) in table_rhs.iter() {
98                    table_lhs.insert(key.clone(), value.clone());
99                }
100            } else {
101                self.generators.insert(*generator, table_rhs.clone());
102            }
103        }
104    }
105}