Skip to main content

openapi_nexus/config/
cli.rs

1//! Command-line argument definitions
2
3use std::collections::HashMap;
4use std::str::FromStr;
5
6use clap::Parser;
7
8use super::errors::ConfigError;
9use super::global_config::GlobalConfig;
10use crate::codegen::GeneratorType;
11
12/// Command-line arguments with environment variable support
13#[derive(Debug, Parser)]
14#[command(name = "openapi-nexus")]
15#[command(about = "Generate code from OpenAPI 3.1 specifications")]
16#[command(version)]
17pub struct CliArgs {
18    #[command(subcommand)]
19    pub command: Commands,
20}
21
22#[derive(Debug, Parser)]
23pub enum Commands {
24    /// Generate code from an OpenAPI specification
25    Generate {
26        /// Path to the OpenAPI specification file
27        #[arg(short, long, env = "OPENAPI_NEXUS_INPUT")]
28        input: String,
29
30        /// Verbose output
31        #[arg(short, long, env = "OPENAPI_NEXUS_VERBOSE")]
32        verbose: bool,
33
34        /// Path to configuration file (overrides auto-discovery)
35        #[arg(long, env = "OPENAPI_NEXUS_CONFIG")]
36        config: Option<String>,
37
38        /// Global configuration options
39        #[command(flatten)]
40        global: GlobalConfig,
41
42        /// Override generator-specific config values
43        ///
44        /// Format: `<generator>.<key>=<value>`
45        ///
46        /// Example: `--generator-config typescript-fetch.file_naming_convention=PascalCase`
47        #[arg(long = "generator-config", value_name = "GENERATOR.KEY=VALUE")]
48        generator_config: Vec<String>,
49    },
50}
51
52impl Commands {
53    /// Parse generator config overrides from CLI arguments
54    /// Returns a map from Generator to TOML table values
55    pub fn parse_generator_overrides(
56        &self,
57    ) -> Result<HashMap<GeneratorType, toml::value::Table>, ConfigError> {
58        let generator_configs = match self {
59            Commands::Generate {
60                generator_config, ..
61            } => generator_config,
62        };
63
64        let mut overrides: HashMap<GeneratorType, toml::value::Table> = HashMap::new();
65
66        for config_str in generator_configs {
67            // Parse format: generator.key=value
68            let parts: Vec<&str> = config_str.splitn(2, '=').collect();
69            if parts.len() != 2 {
70                return Err(ConfigError::ParseOverrides(format!(
71                    "Invalid generator config format: '{}'. Expected format: <generator>.<key>=<value>",
72                    config_str
73                )));
74            }
75
76            let key_part = parts[0];
77            let value_str = parts[1];
78
79            // Split generator and key
80            let key_parts: Vec<&str> = key_part.splitn(2, '.').collect();
81            if key_parts.len() != 2 {
82                return Err(ConfigError::ParseOverrides(format!(
83                    "Invalid generator config format: '{}'. Expected format: <generator>.<key>=<value>",
84                    config_str
85                )));
86            }
87
88            let generator_str = key_parts[0];
89            let key = key_parts[1].to_string();
90
91            // Parse generator
92            let generator = GeneratorType::from_str(generator_str).map_err(|e| {
93                ConfigError::ParseOverrides(format!(
94                    "Invalid generator name '{}': {}",
95                    generator_str, e
96                ))
97            })?;
98
99            // Parse value as TOML value
100            let toml_value = Self::parse_toml_value(value_str);
101
102            // Add to overrides
103            overrides
104                .entry(generator)
105                .or_default()
106                .insert(key, toml_value);
107        }
108
109        Ok(overrides)
110    }
111
112    /// Parse a string value into an appropriate TOML value
113    fn parse_toml_value(value: &str) -> toml::Value {
114        // Try to parse as boolean
115        if let Ok(b) = value.parse::<bool>() {
116            return toml::Value::Boolean(b);
117        }
118        // Try to parse as integer
119        if let Ok(i) = value.parse::<i64>() {
120            return toml::Value::Integer(i);
121        }
122        // Try to parse as float
123        if let Ok(f) = value.parse::<f64>() {
124            return toml::Value::Float(f);
125        }
126        // Default to string
127        toml::Value::String(value.to_string())
128    }
129}