From eac966effc1092ec7596219c70ba4b3094ccb004 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Mon, 29 Aug 2022 11:16:34 -0700 Subject: [PATCH] move format checking from the command to the library (#179) --- progenitor-impl/src/lib.rs | 50 +++++++++++++++++++++++ progenitor-impl/src/method.rs | 6 ++- progenitor/src/main.rs | 74 ++--------------------------------- rustfmt.toml | 2 +- 4 files changed, 60 insertions(+), 72 deletions(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index b3beeb1..4988210 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -1,5 +1,7 @@ // Copyright 2022 Oxide Computer Company +use std::collections::HashSet; + use openapiv3::OpenAPI; use proc_macro2::TokenStream; use quote::quote; @@ -135,6 +137,8 @@ impl Generator { } pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result { + validate_openapi(spec)?; + // Convert our components dictionary to schemars let schemas = spec .components @@ -455,6 +459,52 @@ impl Generator { } } +fn validate_openapi(spec: &OpenAPI) -> Result<()> { + match spec.openapi.as_str() { + "3.0.0" | "3.0.1" | "3.0.2" | "3.0.3" => (), + v => { + return Err(Error::UnexpectedFormat(format!( + "invalid version: {}", + v + ))) + } + } + + let mut opids = HashSet::new(); + spec.paths.paths.iter().try_for_each(|p| { + match p.1 { + openapiv3::ReferenceOr::Reference { reference: _ } => { + Err(Error::UnexpectedFormat(format!( + "path {} uses reference, unsupported", + p.0, + ))) + } + openapiv3::ReferenceOr::Item(item) => { + // Make sure every operation has an operation ID, and that each + // operation ID is only used once in the document. + item.iter().try_for_each(|(_, o)| { + if let Some(oid) = o.operation_id.as_ref() { + if !opids.insert(oid.to_string()) { + return Err(Error::UnexpectedFormat(format!( + "duplicate operation ID: {}", + oid, + ))); + } + } else { + return Err(Error::UnexpectedFormat(format!( + "path {} is missing operation ID", + p.0, + ))); + } + Ok(()) + }) + } + } + })?; + + Ok(()) +} + #[cfg(test)] mod tests { use serde_json::json; diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 97d7d23..a8c1b62 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -1699,7 +1699,11 @@ impl Generator { match (body.content.first(), body.content.len()) { (None, _) => return Ok(None), (Some(first), 1) => first, - (_, n) => todo!("more media types than expected: {}", n), + (_, n) => todo!( + "more media types than expected for {}: {}", + operation.operation_id.as_ref().unwrap(), + n, + ), }; let schema = media_type.schema.as_ref().ok_or_else(|| { diff --git a/progenitor/src/main.rs b/progenitor/src/main.rs index 9ae0f6e..c950724 100644 --- a/progenitor/src/main.rs +++ b/progenitor/src/main.rs @@ -1,7 +1,6 @@ -// Copyright 2021 Oxide Computer Company +// Copyright 2022 Oxide Computer Company use std::{ - collections::HashSet, fs::{File, OpenOptions}, io::Write, path::{Path, PathBuf}, @@ -11,7 +10,6 @@ use anyhow::{bail, Result}; use clap::{Parser, ValueEnum}; use openapiv3::OpenAPI; use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle}; -use serde::Deserialize; #[derive(Parser)] struct Args { @@ -124,7 +122,7 @@ fn main() -> Result<()> { "[package]\n\ name = \"{}\"\n\ version = \"{}\"\n\ - edition = \"2018\"\n\ + edition = \"2021\"\n\ \n\ [dependencies]\n\ {}\n\ @@ -169,75 +167,11 @@ fn main() -> Result<()> { Ok(()) } -fn load(p: P) -> Result -where - P: AsRef, - for<'de> T: Deserialize<'de>, -{ - let p = p.as_ref(); - let f = File::open(p)?; - Ok(serde_json::from_reader(f)?) -} - -// TODO some or all of this validation should be in progenitor-impl pub fn load_api

(p: P) -> Result where P: AsRef, { - let api: OpenAPI = load(p)?; - - if api.openapi != "3.0.3" { - /* - * XXX During development we are being very strict, but this should - * probably be relaxed. - */ - bail!("unexpected version {}", api.openapi); - } - - if !api.servers.is_empty() { - bail!("servers not presently supported"); - } - - if api.security.is_some() { - bail!("security not presently supported"); - } - - let mut opids = HashSet::new(); - for p in api.paths.paths.iter() { - match p.1 { - openapiv3::ReferenceOr::Reference { reference: _ } => { - bail!("path {} uses reference, unsupported", p.0); - } - openapiv3::ReferenceOr::Item(item) => { - /* - * Make sure every operation has an operation ID, and that each - * operation ID is only used once in the document. - */ - item.iter().try_for_each(|(_, o)| { - if let Some(oid) = o.operation_id.as_ref() { - if !opids.insert(oid.to_string()) { - bail!("duplicate operation ID: {}", oid); - } - - if !o.servers.is_empty() { - bail!("op {}: servers, unsupported", oid); - } - - if o.security.is_some() { - bail!("op {}: security, unsupported", oid); - } - } else { - bail!("path {} is missing operation ID", p.0); - } - Ok(()) - })?; - - if !item.servers.is_empty() { - bail!("path {} has servers; unsupported", p.0); - } - } - } - } - + let f = File::open(p)?; + let api = serde_json::from_reader(f)?; Ok(api) } diff --git a/rustfmt.toml b/rustfmt.toml index 0c2ca5c..60a370b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,2 @@ -edition = "2018" +edition = "2021" max_width = 80