Add yaml support (#227)

This commit is contained in:
John Vandenberg 2023-01-15 00:58:57 +08:00 committed by GitHub
parent 6eaacdfc93
commit 1ef131a244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 153 additions and 38 deletions

View File

@ -16,6 +16,7 @@
https://github.com/oxidecomputer/progenitor/compare/v0.2.0\...HEAD[Full list of commits] https://github.com/oxidecomputer/progenitor/compare/v0.2.0\...HEAD[Full list of commits]
* Add support for header parameters (#210) * Add support for header parameters (#210)
* Add support for YAML input (#227)
== 0.2.0 (released 2022-09-11) == 0.2.0 (released 2022-09-11)

22
Cargo.lock generated
View File

@ -1110,6 +1110,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml",
"uuid", "uuid",
] ]
@ -1145,6 +1146,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml",
"syn", "syn",
"thiserror", "thiserror",
"typify", "typify",
@ -1163,6 +1165,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_tokenstream", "serde_tokenstream",
"serde_yaml",
"syn", "syn",
] ]
@ -1539,6 +1542,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.4" version = "0.10.4"
@ -1998,6 +2014,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unsafe-libyaml"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"

View File

@ -48,7 +48,7 @@ field set to `date` or `date-time`, include
+chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"] }
``` ```
Similarly if there is a `format` field set to `uuid`: Similarly, if there is a `format` field set to `uuid`:
```diff ```diff
[dependencies] [dependencies]
@ -76,7 +76,7 @@ generate_api!(
); );
``` ```
Note that the macro will be re-evaluated when the OpenAPI json document Note that the macro will be re-evaluated when the `spec` OpenAPI document
changes (when its mtime is updated). changes (when its mtime is updated).
### `build.rs` ### `build.rs`
@ -111,7 +111,7 @@ code:
include!(concat!(env!("OUT_DIR"), "/codegen.rs")); include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
``` ```
You'll need to add add the following to `Cargo.toml`: You'll need to add the following to `Cargo.toml`:
```diff ```diff
[dependencies] [dependencies]
@ -143,7 +143,7 @@ Usage:
progenitor progenitor
Options: Options:
-i INPUT OpenAPI definition document (JSON) -i INPUT OpenAPI definition document (JSON or YAML)
-o OUTPUT Generated Rust crate directory -o OUTPUT Generated Rust crate directory
-n CRATE Target Rust crate name -n CRATE Target Rust crate name
-v VERSION Target Rust crate version -v VERSION Target Rust crate version
@ -155,7 +155,7 @@ For example:
This will produce a package in the specified directory. The output has no This will produce a package in the specified directory. The output has no
persistent dependency on Progenitor including the `progenitor-client` crate. persistent dependency on Progenitor including the `progenitor-client` crate.
Here's a excerpt from the emitted `Cargo.toml`: Here is an excerpt from the emitted `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]

View File

@ -31,3 +31,4 @@ dropshot = { git = "https://github.com/oxidecomputer/dropshot", default-features
expectorate = "1.0" expectorate = "1.0"
http = "0.2.8" http = "0.2.8"
hyper = "0.14.23" hyper = "0.14.23"
serde_yaml = "0.9"

View File

@ -1,23 +1,42 @@
// Copyright 2022 Oxide Computer Company // Copyright 2022 Oxide Computer Company
use std::{fs::File, path::PathBuf}; use std::{
fs::File,
path::{Path, PathBuf},
};
use progenitor_impl::{ use progenitor_impl::{
GenerationSettings, Generator, InterfaceStyle, TagStyle, TypePatch, GenerationSettings, Generator, InterfaceStyle, TagStyle, TypePatch,
}; };
use openapiv3::OpenAPI;
fn load_api<P>(p: P) -> OpenAPI
where
P: AsRef<Path> + std::clone::Clone + std::fmt::Debug,
{
let mut f = File::open(p.clone()).unwrap();
match serde_json::from_reader(f) {
Ok(json_value) => json_value,
_ => {
f = File::open(p.clone()).unwrap();
serde_yaml::from_reader(f).unwrap()
}
}
}
#[track_caller] #[track_caller]
fn verify_apis(openapi_file: &str) { fn verify_apis(openapi_file: &str) {
let mut in_path = PathBuf::from("../sample_openapi"); let mut in_path = PathBuf::from("../sample_openapi");
in_path.push(format!("{}.json", openapi_file)); in_path.push(openapi_file);
let openapi_stem = openapi_file.split('.').next().unwrap();
let file = File::open(in_path).unwrap(); let spec = load_api(in_path);
let spec = serde_json::from_reader(file).unwrap();
let mut generator = Generator::default(); let mut generator = Generator::default();
let output = generator.generate_text_normalize_comments(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}-positional.out", openapi_file), format!("tests/output/{}-positional.out", openapi_stem),
&output, &output,
); );
@ -41,7 +60,7 @@ fn verify_apis(openapi_file: &str) {
); );
let output = generator.generate_text_normalize_comments(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}-builder.out", openapi_file), format!("tests/output/{}-builder.out", openapi_stem),
&output, &output,
); );
@ -53,34 +72,39 @@ fn verify_apis(openapi_file: &str) {
let output = generator.generate_text_normalize_comments(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
println!("{output}"); println!("{output}");
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}-builder-tagged.out", openapi_file), format!("tests/output/{}-builder-tagged.out", openapi_stem),
&output, &output,
); );
} }
#[test] #[test]
fn test_keeper() { fn test_keeper() {
verify_apis("keeper"); verify_apis("keeper.json");
} }
#[test] #[test]
fn test_buildomat() { fn test_buildomat() {
verify_apis("buildomat"); verify_apis("buildomat.json");
} }
#[test] #[test]
fn test_nexus() { fn test_nexus() {
verify_apis("nexus"); verify_apis("nexus.json");
} }
#[test] #[test]
fn test_propolis_server() { fn test_propolis_server() {
verify_apis("propolis-server"); verify_apis("propolis-server.json");
} }
#[test] #[test]
fn test_param_override() { fn test_param_override() {
verify_apis("param-overrides"); verify_apis("param-overrides.json");
}
#[test]
fn test_yaml() {
verify_apis("param-overrides.yaml");
} }
// TODO this file is full of inconsistencies and incorrectly specified types. // TODO this file is full of inconsistencies and incorrectly specified types.
@ -89,5 +113,5 @@ fn test_param_override() {
#[ignore] #[ignore]
#[test] #[test]
fn test_github() { fn test_github() {
verify_apis("api.github.com"); verify_apis("api.github.com.json");
} }

View File

@ -18,5 +18,6 @@ quote = "1.0"
schemars = "0.8.11" schemars = "0.8.11"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.9"
serde_tokenstream = "0.1.6" serde_tokenstream = "0.1.6"
syn = "1.0" syn = "1.0"

View File

@ -1,6 +1,11 @@
// Copyright 2022 Oxide Computer Company // Copyright 2022 Oxide Computer Company
use std::{collections::HashMap, fmt::Display, path::Path}; use std::{
collections::HashMap,
fmt::Display,
fs::File,
path::{Path, PathBuf},
};
use openapiv3::OpenAPI; use openapiv3::OpenAPI;
use proc_macro::TokenStream; use proc_macro::TokenStream;
@ -33,7 +38,7 @@ use syn::LitStr;
/// ); /// );
/// ``` /// ```
/// ///
/// The `spec` key is required; it is the OpenAPI document from which the /// The `spec` key is required; it is the OpenAPI document (JSON or YAML) from which the
/// client is derived. /// client is derived.
/// ///
/// The optional `interface` lets you specify either a `Positional` argument or /// The optional `interface` lets you specify either a `Positional` argument or
@ -158,6 +163,16 @@ impl syn::parse::Parse for ClosureOrPath {
} }
} }
fn open_file(
path: PathBuf,
span: proc_macro2::Span,
) -> Result<File, syn::Error> {
File::open(path.clone()).map_err(|e| {
let path_str = path.to_string_lossy();
syn::Error::new(span, format!("couldn't read file {}: {}", path_str, e))
})
}
fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> { fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
let (spec, settings) = if let Ok(spec) = syn::parse::<LitStr>(item.clone()) let (spec, settings) = if let Ok(spec) = syn::parse::<LitStr>(item.clone())
{ {
@ -214,19 +229,19 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
let path = dir.join(spec.value()); let path = dir.join(spec.value());
let path_str = path.to_string_lossy(); let path_str = path.to_string_lossy();
let oapi: OpenAPI = let mut f = open_file(path.clone(), spec.span())?;
serde_json::from_reader(std::fs::File::open(&path).map_err(|e| { let oapi: OpenAPI = match serde_json::from_reader(f) {
syn::Error::new( Ok(json_value) => json_value,
spec.span(), _ => {
format!("couldn't read file {}: {}", path_str, e), f = open_file(path.clone(), spec.span())?;
) serde_yaml::from_reader(f).map_err(|e| {
})?) syn::Error::new(
.map_err(|e| { spec.span(),
syn::Error::new( format!("failed to parse {}: {}", path_str, e),
spec.span(), )
format!("failed to parse {}: {}", path_str, e), })?
) }
})?; };
let mut builder = Generator::new(&settings); let mut builder = Generator::new(&settings);

View File

@ -17,6 +17,7 @@ anyhow = "1.0"
openapiv3 = "1.0.0" openapiv3 = "1.0.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.9"
clap = { version = "4.0.32", features = ["derive"] } clap = { version = "4.0.32", features = ["derive"] }
[dev-dependencies] [dev-dependencies]

View File

@ -13,7 +13,7 @@ use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle};
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {
/// OpenAPI definition document (JSON) /// OpenAPI definition document (JSON or YAML)
#[clap(short = 'i', long)] #[clap(short = 'i', long)]
input: String, input: String,
/// Output directory for Rust crate /// Output directory for Rust crate
@ -167,11 +167,17 @@ fn main() -> Result<()> {
Ok(()) Ok(())
} }
pub fn load_api<P>(p: P) -> Result<OpenAPI> fn load_api<P>(p: P) -> Result<OpenAPI>
where where
P: AsRef<Path>, P: AsRef<Path> + std::clone::Clone + std::fmt::Debug,
{ {
let f = File::open(p)?; let mut f = File::open(p.clone())?;
let api = serde_json::from_reader(f)?; let api = match serde_json::from_reader(f) {
Ok(json_value) => json_value,
_ => {
f = File::open(p)?;
serde_yaml::from_reader(f)?
}
};
Ok(api) Ok(api)
} }

View File

@ -0,0 +1,7 @@
mod load_yaml {
progenitor::generate_api!("../sample_openapi/param-overrides.yaml");
fn _ignore() {
let _ = Client::new("").key_get(None, None);
}
}

View File

@ -0,0 +1,37 @@
openapi: 3.0.0
info:
description: Minimal API for testing parameter overrides
title: Parameter override test
version: v1
components:
parameters:
key:
description: A key parameter that will be overridden by the path spec
in: query
name: key
schema:
type: string
unique-key:
description: A key parameter that will not be overridden by the path spec
in: query
name: uniqueKey
schema:
type: string
paths:
/key:
get:
description: Gets a key
operationId: key.get
parameters:
- description: The same key parameter that overlaps with the path level parameter
in: query
name: key
schema:
type: boolean
responses:
'200':
description: Successful response
type: string
parameters:
- $ref: '#/components/parameters/key'
- $ref: '#/components/parameters/unique-key'