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'