move format checking from the command to the library (#179)

This commit is contained in:
Adam Leventhal 2022-08-29 11:16:34 -07:00 committed by GitHub
parent fa4e09f00a
commit eac966effc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 72 deletions

View File

@ -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<TokenStream> {
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;

View File

@ -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(|| {

View File

@ -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, T>(p: P) -> Result<T>
where
P: AsRef<Path>,
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: P) -> Result<OpenAPI>
where
P: AsRef<Path>,
{
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)
}

View File

@ -1,2 +1,2 @@
edition = "2018"
edition = "2021"
max_width = 80