oas_forge/
config.rs

1use clap::Parser;
2use serde::Deserialize;
3use std::path::PathBuf;
4
5#[derive(Debug, Deserialize, Parser, Default, Clone)]
6#[serde(default)]
7#[command(author, version, about, long_about = None)]
8pub struct Config {
9    /// Input directories to scan for Rust files and OpenAPI fragments
10    #[arg(short = 'i', long = "input")]
11    pub input: Option<Vec<PathBuf>>,
12
13    /// Specific files to include (e.g., .json, .yaml)
14    #[arg(long = "include")]
15    pub include: Option<Vec<PathBuf>>,
16
17    /// Output file for the generated OpenAPI definition (defaults to openapi.yaml)
18    #[arg(short = 'o', long = "output")]
19    pub output: Option<PathBuf>,
20
21    /// Path to a configuration file (toml)
22    #[arg(long = "config")]
23    #[serde(skip)]
24    pub config_file: Option<PathBuf>,
25}
26
27#[derive(Deserialize)]
28struct CargoConfig {
29    package: Option<CargoPackage>,
30}
31
32#[derive(Deserialize)]
33struct CargoPackage {
34    metadata: Option<CargoMetadata>,
35}
36
37#[derive(Deserialize)]
38struct CargoMetadata {
39    #[serde(rename = "oas-forge")]
40    oas_forge: Option<Config>,
41}
42
43impl Config {
44    /// Load configuration with priority:
45    /// 1. CLI Arguments (Highest)
46    /// 2. --config file
47    /// 3. openapi.toml
48    /// 4. Cargo.toml [package.metadata.oas-forge]
49    pub fn load() -> Self {
50        let cli_args = Config::parse();
51
52        // Start with default empty config
53        let mut final_config = Config::default();
54
55        // 4. Try loading Cargo.toml
56        if let Ok(cargo_conf) = load_cargo_toml() {
57            final_config.merge(cargo_conf);
58        }
59
60        // 3. Try loading openapi.toml
61        if let Ok(toml_conf) = load_toml_file("openapi.toml") {
62            final_config.merge(toml_conf);
63        }
64
65        // 2. Try loading explicit config file
66        if let Some(path) = &cli_args.config_file {
67            if let Ok(file_conf) = load_toml_file(path) {
68                final_config.merge(file_conf);
69            }
70        }
71
72        // 1. Merge CLI args (taking precedence)
73        final_config.merge(cli_args);
74
75        final_config
76    }
77
78    fn merge(&mut self, other: Config) {
79        if let Some(input) = other.input {
80            self.input = Some(input);
81        }
82        if let Some(include) = other.include {
83            self.include = Some(include);
84        }
85        if let Some(output) = other.output {
86            self.output = Some(output);
87        }
88    }
89}
90
91fn load_cargo_toml() -> Result<Config, Box<dyn std::error::Error>> {
92    let content = std::fs::read_to_string("Cargo.toml")?;
93    let config: CargoConfig = toml::from_str(&content)?;
94    Ok(config
95        .package
96        .and_then(|p| p.metadata)
97        .and_then(|m| m.oas_forge)
98        .unwrap_or_default())
99}
100
101fn load_toml_file<P: AsRef<std::path::Path>>(
102    path: P,
103) -> Result<Config, Box<dyn std::error::Error>> {
104    let content = std::fs::read_to_string(path)?;
105    let config: Config = toml::from_str(&content)?;
106    Ok(config)
107}