Add yaml support (#227)
This commit is contained in:
parent
6eaacdfc93
commit
1ef131a244
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
10
README.md
10
README.md
|
@ -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]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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| {
|
||||||
})?)
|
|
||||||
.map_err(|e| {
|
|
||||||
syn::Error::new(
|
syn::Error::new(
|
||||||
spec.span(),
|
spec.span(),
|
||||||
format!("failed to parse {}: {}", path_str, e),
|
format!("failed to parse {}: {}", path_str, e),
|
||||||
)
|
)
|
||||||
})?;
|
})?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut builder = Generator::new(&settings);
|
let mut builder = Generator::new(&settings);
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod load_yaml {
|
||||||
|
progenitor::generate_api!("../sample_openapi/param-overrides.yaml");
|
||||||
|
|
||||||
|
fn _ignore() {
|
||||||
|
let _ = Client::new("").key_get(None, None);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
Loading…
Reference in New Issue