#![allow(
clippy::print_stderr,
reason = "this binary reports status and guided errors to stderr"
)]
mod console;
use std::io::IsTerminal;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::ExitCode;
use clap::Parser;
use oapi_codegen::Config;
use oapi_codegen::Drift;
use oapi_codegen::Error;
use oapi_codegen::Result;
use oapi_codegen::cli::Cli;
use oapi_codegen::config::Generate;
use crate::console::SpecStats;
enum CliFailure {
Generator(Error),
NoArtifacts {
config: PathBuf,
},
NoOutput,
EmptyOutput {
spec: PathBuf,
stats: SpecStats,
generate: Generate,
},
Drift {
output: PathBuf,
absent: bool,
},
}
impl CliFailure {
fn report(&self) {
match self {
CliFailure::Generator(err) => {
console::report_error(err);
}
CliFailure::NoArtifacts { config } => {
console::report_no_artifacts(config);
}
CliFailure::NoOutput => {
console::report_no_output();
}
CliFailure::EmptyOutput { spec, stats, generate } => {
console::report_empty_output(spec, stats, generate);
}
CliFailure::Drift { output, absent } => {
console::report_drift(output, *absent);
}
}
}
}
impl From<Error> for CliFailure {
fn from(err: Error) -> Self {
return CliFailure::Generator(err);
}
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(()) => {
return ExitCode::SUCCESS;
}
Err(failure) => {
failure.report();
return ExitCode::FAILURE;
}
}
}
fn run(cli: &Cli) -> std::result::Result<(), CliFailure> {
let config = Config::load(&cli.config_file)?;
if config.generate.embedded_spec {
return Err(CliFailure::Generator(Error::Unimplemented("embedded-spec".to_owned())));
}
let generate = &config.generate;
if !generate.models && !generate.std_http_server && !generate.client && !generate.server_urls {
return Err(CliFailure::NoArtifacts {
config: cli.config_file.clone(),
});
}
let output = cli.output_file.clone().or_else(|| {
return config.output.clone();
});
let output = output.ok_or(CliFailure::NoOutput)?;
let code = oapi_codegen::generate(&cli.spec_file, &config)?;
if console::is_effectively_empty(&code) {
let stats = spec_stats(&cli.spec_file, &config)?;
return Err(CliFailure::EmptyOutput {
spec: cli.spec_file.clone(),
stats,
generate: config.generate.clone(),
});
}
if cli.check {
match oapi_codegen::check_output(&output, &code)? {
Drift::None => {
console::report_check_passed(&output);
return Ok(());
}
Drift::Absent => {
return Err(CliFailure::Drift { output, absent: true });
}
Drift::Differs => {
return Err(CliFailure::Drift { output, absent: false });
}
}
}
oapi_codegen::write_output(&output, &code)?;
console::report_wrote(&output);
let manifest = nearest_manifest(&output);
let deps = oapi_codegen::deps::required_dependencies(&code);
console::report_dependencies(&deps);
if !deps.is_empty() && should_install_deps(cli.install_deps) {
install_dependencies(&deps, manifest.as_deref());
}
return Ok(());
}
fn nearest_manifest(output: &Path) -> Option<PathBuf> {
let canonical = std::fs::canonicalize(output).unwrap_or_else(|_| return output.to_path_buf());
let mut directory = canonical.parent();
while let Some(current) = directory {
let candidate = current.join("Cargo.toml");
if candidate.is_file() {
return Some(candidate);
}
directory = current.parent();
}
return None;
}
fn should_install_deps(flag: bool) -> bool {
if flag {
return true;
}
if std::io::stdin().is_terminal() {
return console::prompt_install_dependencies();
}
return false;
}
fn install_dependencies(deps: &[oapi_codegen::deps::Dependency], manifest: Option<&Path>) {
for dep in deps {
console::report_installing(dep);
let mut command = Command::new("cargo");
command.args(dep.cargo_add_args());
if let Some(path) = manifest {
command.arg("--manifest-path").arg(path);
}
match command.status() {
Ok(status) if status.success() => {}
Ok(status) => console::report_install_failed(dep, &format!("cargo exited with {status}")),
Err(error) => console::report_install_failed(dep, &error.to_string()),
}
}
}
fn spec_stats(spec_path: &Path, config: &Config) -> Result<SpecStats> {
let mut spec = oapi_codegen::loader::Spec::load(spec_path)?;
spec.apply_filters(&config.output_options);
return Ok(SpecStats {
schemas: spec.schemas().len(),
paths: spec.paths().paths.len(),
servers: spec.servers().len(),
});
}