diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index e076ff2..9ea7a3e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -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) diff --git a/Cargo.lock b/Cargo.lock index cd5cb25..a02a88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/README.md b/README.md index 3953251..3b0dcee 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/progenitor-impl/Cargo.toml b/progenitor-impl/Cargo.toml index 5e3aee8..adff765 100644 --- a/progenitor-impl/Cargo.toml +++ b/progenitor-impl/Cargo.toml @@ -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" diff --git a/progenitor-impl/tests/test_output.rs b/progenitor-impl/tests/test_output.rs index 8510a0c..eb80b41 100644 --- a/progenitor-impl/tests/test_output.rs +++ b/progenitor-impl/tests/test_output.rs @@ -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) -> OpenAPI +where + P: AsRef + 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"); } diff --git a/progenitor-macro/Cargo.toml b/progenitor-macro/Cargo.toml index 6a08414..adef09c 100644 --- a/progenitor-macro/Cargo.toml +++ b/progenitor-macro/Cargo.toml @@ -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" diff --git a/progenitor-macro/src/lib.rs b/progenitor-macro/src/lib.rs index 3310fd1..74cd9c9 100644 --- a/progenitor-macro/src/lib.rs +++ b/progenitor-macro/src/lib.rs @@ -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::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 { let (spec, settings) = if let Ok(spec) = syn::parse::(item.clone()) { @@ -214,19 +229,19 @@ fn do_generate_api(item: TokenStream) -> Result { 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| { - syn::Error::new( - spec.span(), - format!("failed to parse {}: {}", path_str, 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); diff --git a/progenitor/Cargo.toml b/progenitor/Cargo.toml index 41a30e1..5fe38c1 100644 --- a/progenitor/Cargo.toml +++ b/progenitor/Cargo.toml @@ -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] diff --git a/progenitor/src/main.rs b/progenitor/src/main.rs index c950724..1818d02 100644 --- a/progenitor/src/main.rs +++ b/progenitor/src/main.rs @@ -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) -> Result +fn load_api

(p: P) -> Result where - P: AsRef, + P: AsRef + 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) } diff --git a/progenitor/tests/load_yaml.rs b/progenitor/tests/load_yaml.rs new file mode 100644 index 0000000..558dbd6 --- /dev/null +++ b/progenitor/tests/load_yaml.rs @@ -0,0 +1,7 @@ +mod load_yaml { + progenitor::generate_api!("../sample_openapi/param-overrides.yaml"); + + fn _ignore() { + let _ = Client::new("").key_get(None, None); + } +} diff --git a/sample_openapi/param-overrides.yaml b/sample_openapi/param-overrides.yaml new file mode 100644 index 0000000..9ac6f27 --- /dev/null +++ b/sample_openapi/param-overrides.yaml @@ -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'