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]
* Add support for header parameters (#210)
* Add support for YAML input (#227)
== 0.2.0 (released 2022-09-11)

22
Cargo.lock generated
View File

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

View File

@ -48,7 +48,7 @@ field set to `date` or `date-time`, include
+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
[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).
### `build.rs`
@ -111,7 +111,7 @@ code:
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
[dependencies]
@ -143,7 +143,7 @@ Usage:
progenitor
Options:
-i INPUT OpenAPI definition document (JSON)
-i INPUT OpenAPI definition document (JSON or YAML)
-o OUTPUT Generated Rust crate directory
-n CRATE Target Rust crate name
-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
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
[dependencies]

View File

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

View File

@ -1,23 +1,42 @@
// Copyright 2022 Oxide Computer Company
use std::{fs::File, path::PathBuf};
use std::{
fs::File,
path::{Path, PathBuf},
};
use progenitor_impl::{
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]
fn verify_apis(openapi_file: &str) {
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 = serde_json::from_reader(file).unwrap();
let spec = load_api(in_path);
let mut generator = Generator::default();
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(
format!("tests/output/{}-positional.out", openapi_file),
format!("tests/output/{}-positional.out", openapi_stem),
&output,
);
@ -41,7 +60,7 @@ fn verify_apis(openapi_file: &str) {
);
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(
format!("tests/output/{}-builder.out", openapi_file),
format!("tests/output/{}-builder.out", openapi_stem),
&output,
);
@ -53,34 +72,39 @@ fn verify_apis(openapi_file: &str) {
let output = generator.generate_text_normalize_comments(&spec).unwrap();
println!("{output}");
expectorate::assert_contents(
format!("tests/output/{}-builder-tagged.out", openapi_file),
format!("tests/output/{}-builder-tagged.out", openapi_stem),
&output,
);
}
#[test]
fn test_keeper() {
verify_apis("keeper");
verify_apis("keeper.json");
}
#[test]
fn test_buildomat() {
verify_apis("buildomat");
verify_apis("buildomat.json");
}
#[test]
fn test_nexus() {
verify_apis("nexus");
verify_apis("nexus.json");
}
#[test]
fn test_propolis_server() {
verify_apis("propolis-server");
verify_apis("propolis-server.json");
}
#[test]
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.
@ -89,5 +113,5 @@ fn test_param_override() {
#[ignore]
#[test]
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"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
serde_tokenstream = "0.1.6"
syn = "1.0"

View File

@ -1,6 +1,11 @@
// 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 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.
///
/// 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> {
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_str = path.to_string_lossy();
let oapi: OpenAPI =
serde_json::from_reader(std::fs::File::open(&path).map_err(|e| {
syn::Error::new(
spec.span(),
format!("couldn't read file {}: {}", path_str, e),
)
})?)
.map_err(|e| {
let mut f = open_file(path.clone(), spec.span())?;
let oapi: OpenAPI = match serde_json::from_reader(f) {
Ok(json_value) => json_value,
_ => {
f = open_file(path.clone(), spec.span())?;
serde_yaml::from_reader(f).map_err(|e| {
syn::Error::new(
spec.span(),
format!("failed to parse {}: {}", path_str, e),
)
})?;
})?
}
};
let mut builder = Generator::new(&settings);

View File

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

View File

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