move format checking from the command to the library (#179)
This commit is contained in:
parent
fa4e09f00a
commit
eac966effc
|
@ -1,5 +1,7 @@
|
||||||
// Copyright 2022 Oxide Computer Company
|
// Copyright 2022 Oxide Computer Company
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use openapiv3::OpenAPI;
|
use openapiv3::OpenAPI;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -135,6 +137,8 @@ impl Generator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result<TokenStream> {
|
pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result<TokenStream> {
|
||||||
|
validate_openapi(spec)?;
|
||||||
|
|
||||||
// Convert our components dictionary to schemars
|
// Convert our components dictionary to schemars
|
||||||
let schemas = spec
|
let schemas = spec
|
||||||
.components
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
|
@ -1699,7 +1699,11 @@ impl Generator {
|
||||||
match (body.content.first(), body.content.len()) {
|
match (body.content.first(), body.content.len()) {
|
||||||
(None, _) => return Ok(None),
|
(None, _) => return Ok(None),
|
||||||
(Some(first), 1) => first,
|
(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(|| {
|
let schema = media_type.schema.as_ref().ok_or_else(|| {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2021 Oxide Computer Company
|
// Copyright 2022 Oxide Computer Company
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -11,7 +10,6 @@ use anyhow::{bail, Result};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use openapiv3::OpenAPI;
|
use openapiv3::OpenAPI;
|
||||||
use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle};
|
use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle};
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -124,7 +122,7 @@ fn main() -> Result<()> {
|
||||||
"[package]\n\
|
"[package]\n\
|
||||||
name = \"{}\"\n\
|
name = \"{}\"\n\
|
||||||
version = \"{}\"\n\
|
version = \"{}\"\n\
|
||||||
edition = \"2018\"\n\
|
edition = \"2021\"\n\
|
||||||
\n\
|
\n\
|
||||||
[dependencies]\n\
|
[dependencies]\n\
|
||||||
{}\n\
|
{}\n\
|
||||||
|
@ -169,75 +167,11 @@ fn main() -> Result<()> {
|
||||||
Ok(())
|
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>
|
pub fn load_api<P>(p: P) -> Result<OpenAPI>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
let api: OpenAPI = load(p)?;
|
let f = File::open(p)?;
|
||||||
|
let api = serde_json::from_reader(f)?;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(api)
|
Ok(api)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
max_width = 80
|
max_width = 80
|
||||||
|
|
Loading…
Reference in New Issue