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
|
||||
|
||||
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;
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
edition = "2018"
|
||||
edition = "2021"
|
||||
max_width = 80
|
||||
|
|
Loading…
Reference in New Issue