Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"dev-tools/omicron-dev",
"dev-tools/omicron-dev-lib",
"dev-tools/openapi-manager",
"dev-tools/openapi-manager/types",
"dev-tools/oxlog",
"dev-tools/reconfigurator-cli",
"dev-tools/releng",
Expand Down Expand Up @@ -145,6 +146,7 @@ default-members = [
"dev-tools/omicron-dev",
"dev-tools/omicron-dev-lib",
"dev-tools/openapi-manager",
"dev-tools/openapi-manager/types",
"dev-tools/oxlog",
"dev-tools/reconfigurator-cli",
"dev-tools/releng",
Expand Down Expand Up @@ -453,6 +455,7 @@ oxlog = { path = "dev-tools/oxlog" }
oxnet = { git = "https://github.com/oxidecomputer/oxnet" }
once_cell = "1.19.0"
openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = "main" }
openapi-manager-types = { path = "dev-tools/openapi-manager/types" }
openapiv3 = "2.0.0"
# must match samael's crate!
openssl = "0.10"
Expand Down
1 change: 1 addition & 0 deletions dev-tools/openapi-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ nexus-internal-api.workspace = true
omicron-workspace-hack.workspace = true
openapiv3.workspace = true
openapi-lint.workspace = true
openapi-manager-types.workspace = true
owo-colors.workspace = true
oximeter-api.workspace = true
serde_json.workspace = true
Expand Down
125 changes: 78 additions & 47 deletions dev-tools/openapi-manager/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
use std::{io::Write, process::ExitCode};

use anyhow::Result;
use camino::Utf8Path;
use indent_write::io::IndentWriter;
use owo_colors::OwoColorize;
use similar::TextDiff;

use crate::{
output::{
display_api_spec, display_error, display_summary, headers::*, plural,
write_diff, OutputOpts, Styles,
display_api_spec, display_api_spec_file, display_error,
display_summary, headers::*, plural, write_diff, OutputOpts, Styles,
},
spec::{all_apis, CheckStatus},
spec::{all_apis, CheckStale, Environment},
FAILURE_EXIT_CODE, NEEDS_UPDATE_EXIT_CODE,
};

Expand All @@ -37,7 +36,7 @@ impl CheckResult {
}

pub(crate) fn check_impl(
dir: &Utf8Path,
env: &Environment,
output: &OutputOpts,
) -> Result<CheckResult> {
let mut styles = Styles::default();
Expand All @@ -48,6 +47,7 @@ pub(crate) fn check_impl(
let all_apis = all_apis();
let total = all_apis.len();
let count_width = total.to_string().len();
let count_section_indent = count_section_indent(count_width);
let continued_indent = continued_indent(count_width);

eprintln!("{:>HEADER_WIDTH$}", SEPARATOR);
Expand All @@ -58,57 +58,89 @@ pub(crate) fn check_impl(
total.style(styles.bold),
plural::documents(total),
);
let mut num_up_to_date = 0;
let mut num_fresh = 0;
let mut num_stale = 0;
let mut num_missing = 0;
let mut num_failed = 0;

for (ix, spec) in all_apis.iter().enumerate() {
let count = ix + 1;

match spec.check(&dir) {
Ok(status) => match status {
CheckStatus::Ok(summary) => {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}: {}",
UP_TO_DATE.style(styles.success_header),
display_api_spec(spec, &styles),
display_summary(&summary, &styles),
);
match spec.check(env) {
Ok(status) => {
let total_errors = status.total_errors();
let total_errors_width = total_errors.to_string().len();

if total_errors == 0 {
// Success case.
let extra = if status.extra_files_len() > 0 {
format!(
", {} extra files",
status.extra_files_len().style(styles.bold)
)
} else {
"".to_string()
};

num_up_to_date += 1;
}
CheckStatus::Stale { full_path, actual, expected } => {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}",
STALE.style(styles.warning_header),
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}: {}{extra}",
FRESH.style(styles.success_header),
display_api_spec(spec, &styles),
display_summary(&status.summary, &styles),
);

let diff = TextDiff::from_lines(&actual, &expected);
write_diff(
&diff,
&full_path,
&styles,
// Add an indent to align diff with the status message.
&mut IndentWriter::new(
&continued_indent,
std::io::stderr(),
),
)?;

num_stale += 1;
num_fresh += 1;
continue;
}
CheckStatus::Missing => {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}",
MISSING.style(styles.warning_header),
display_api_spec(spec, &styles),
);

num_missing += 1;
// Out of date: print errors.
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}",
STALE.style(styles.warning_header),
display_api_spec(spec, &styles),
);
num_stale += 1;

for (error_ix, (spec_file, error)) in
status.iter_errors().enumerate()
{
let error_count = error_ix + 1;

let display_heading = |heading: &str| {
eprintln!(
"{:>HEADER_WIDTH$}{count_section_indent}\
({error_count:>total_errors_width$}/{total_errors}) {}",
heading.style(styles.warning_header),
display_api_spec_file(spec, spec_file, &styles),
);
};

match error {
CheckStale::Modified {
full_path,
actual,
expected,
} => {
display_heading(MODIFIED);

let diff =
TextDiff::from_lines(&**actual, &**expected);
write_diff(
&diff,
&full_path,
&styles,
// Add an indent to align diff with the status message.
&mut IndentWriter::new(
&continued_indent,
std::io::stderr(),
),
)?;
}
CheckStale::New => {
display_heading(NEW);
}
}
}
},
}
Err(error) => {
eprint!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}",
Expand Down Expand Up @@ -138,13 +170,12 @@ pub(crate) fn check_impl(
};

eprintln!(
"{:>HEADER_WIDTH$} {} {} checked: {} up-to-date, {} stale, {} missing, {} failed",
"{:>HEADER_WIDTH$} {} {} checked: {} fresh, {} stale, {} failed",
status_header,
total.style(styles.bold),
plural::documents(total),
num_up_to_date.style(styles.bold),
num_fresh.style(styles.bold),
num_stale.style(styles.bold),
num_missing.style(styles.bold),
num_failed.style(styles.bold),
);
if num_failed > 0 {
Expand All @@ -170,14 +201,14 @@ pub(crate) fn check_impl(
mod tests {
use std::process::ExitCode;

use crate::spec::find_openapi_dir;
use crate::spec::Environment;

use super::*;

#[test]
fn check_apis_up_to_date() -> Result<ExitCode> {
let output = OutputOpts { color: clap::ColorChoice::Auto };
let dir = find_openapi_dir()?;
let dir = Environment::new(None)?;

let result = check_impl(&dir, &output)?;
Ok(result.to_exit_code())
Expand Down
8 changes: 4 additions & 4 deletions dev-tools/openapi-manager/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use clap::{Args, Parser, Subcommand};

use crate::{
check::check_impl, generate::generate_impl, list::list_impl,
output::OutputOpts, spec::openapi_dir,
output::OutputOpts, spec::Environment,
};

/// Manage OpenAPI specifications.
Expand Down Expand Up @@ -73,7 +73,7 @@ pub struct GenerateArgs {

impl GenerateArgs {
fn exec(self, output: &OutputOpts) -> anyhow::Result<ExitCode> {
let dir = openapi_dir(self.dir)?;
let dir = Environment::new(self.dir)?;
Ok(generate_impl(&dir, output)?.to_exit_code())
}
}
Expand All @@ -87,8 +87,8 @@ pub struct CheckArgs {

impl CheckArgs {
fn exec(self, output: &OutputOpts) -> anyhow::Result<ExitCode> {
let dir = openapi_dir(self.dir)?;
Ok(check_impl(&dir, output)?.to_exit_code())
let env = Environment::new(self.dir)?;
Ok(check_impl(&env, output)?.to_exit_code())
}
}

Expand Down
26 changes: 14 additions & 12 deletions dev-tools/openapi-manager/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use std::{io::Write, process::ExitCode};

use anyhow::Result;
use camino::Utf8Path;
use indent_write::io::IndentWriter;
use owo_colors::OwoColorize;

Expand All @@ -14,7 +13,7 @@ use crate::{
display_api_spec, display_error, display_summary, headers::*, plural,
OutputOpts, Styles,
},
spec::{all_apis, OverwriteStatus},
spec::{all_apis, Environment},
FAILURE_EXIT_CODE,
};

Expand All @@ -34,7 +33,7 @@ impl GenerateResult {
}

pub(crate) fn generate_impl(
dir: &Utf8Path,
env: &Environment,
output: &OutputOpts,
) -> Result<GenerateResult> {
let mut styles = Styles::default();
Expand Down Expand Up @@ -62,27 +61,30 @@ pub(crate) fn generate_impl(
for (ix, spec) in all_apis.iter().enumerate() {
let count = ix + 1;

match spec.overwrite(&dir) {
Ok((status, summary)) => match status {
OverwriteStatus::Updated => {
match spec.overwrite(env) {
Ok(status) => {
let updated_count = status.updated_count();

if updated_count > 0 {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}: {}",
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}: {} ({} {} updated)",
UPDATED.style(styles.success_header),
display_api_spec(spec, &styles),
display_summary(&summary, &styles),
display_summary(&status.summary, &styles),
updated_count.style(styles.bold),
plural::files(updated_count),
);
num_updated += 1;
}
OverwriteStatus::Unchanged => {
} else {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}: {}",
UNCHANGED.style(styles.unchanged_header),
display_api_spec(spec, &styles),
display_summary(&summary, &styles),
display_summary(&status.summary, &styles),
);
num_unchanged += 1;
}
},
}
Err(err) => {
eprintln!(
"{:>HEADER_WIDTH$} [{count:>count_width$}/{total}] {}",
Expand Down
Loading