// Copyright 2022 Oxide Computer Company
use std::{
fs::File,
path::{Path, PathBuf},
};
use progenitor_impl::{
space_out_items, GenerationSettings, Generator, InterfaceStyle, TagStyle,
TypeImpl, TypePatch,
};
use openapiv3::OpenAPI;
use proc_macro2::TokenStream;
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).unwrap();
serde_yaml::from_reader(f).unwrap()
}
}
}
fn generate_formatted(generator: &mut Generator, spec: &OpenAPI) -> String {
let content = generator.generate_tokens(&spec).unwrap();
reformat_code(content)
}
fn reformat_code(content: TokenStream) -> String {
let rustfmt_config = rustfmt_wrapper::config::Config {
format_strings: Some(true),
normalize_doc_attributes: Some(true),
wrap_comments: Some(true),
..Default::default()
};
space_out_items(
rustfmt_wrapper::rustfmt_config(rustfmt_config, content).unwrap(),
)
.unwrap()
}
#[track_caller]
fn verify_apis(openapi_file: &str) {
let mut in_path = PathBuf::from("../sample_openapi");
in_path.push(openapi_file);
let openapi_stem =
openapi_file.split('.').next().unwrap().replace('-', "_");
let spec = load_api(in_path);
// Positional generation.
let mut generator = Generator::default();
let output = generate_formatted(&mut generator, &spec);
expectorate::assert_contents(
format!("tests/output/src/{}_positional.rs", openapi_stem),
&output,
);
// Builder generation with derives and patches.
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Merged)
.with_derive("schemars::JsonSchema")
.with_patch("Name", TypePatch::default().with_derive("Hash"))
.with_conversion(
schemars::schema::SchemaObject {
instance_type: Some(
schemars::schema::InstanceType::Integer.into(),
),
format: Some("int32".to_string()),
..Default::default()
},
"usize",
[TypeImpl::Display].into_iter(),
),
);
let output = generate_formatted(&mut generator, &spec);
expectorate::assert_contents(
format!("tests/output/src/{}_builder.rs", openapi_stem),
&output,
);
// Builder generation with tags.
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Separate),
);
let output = generate_formatted(&mut generator, &spec);
expectorate::assert_contents(
format!("tests/output/src/{}_builder_tagged.rs", openapi_stem),
&output,
);
// CLI generation.
let tokens = generator
.cli(&spec, &format!("crate::{openapi_stem}_builder"))
.unwrap();
let output = reformat_code(tokens);
expectorate::assert_contents(
format!("tests/output/src/{}_cli.rs", openapi_stem),
&output,
);
// httpmock generation.
let code = generator
.httpmock(&spec, &format!("crate::{openapi_stem}_builder"))
.unwrap();
// TODO pending #368
let output = rustfmt_wrapper::rustfmt_config(
rustfmt_wrapper::config::Config {
format_strings: Some(true),
..Default::default()
},
code,
)
.unwrap();
let output = progenitor_impl::space_out_items(output).unwrap();
expectorate::assert_contents(
format!("tests/output/src/{}_httpmock.rs", openapi_stem),
&output,
);
}
#[test]
fn test_keeper() {
verify_apis("keeper.json");
}
#[test]
fn test_buildomat() {
verify_apis("buildomat.json");
}
#[test]
fn test_nexus() {
verify_apis("nexus.json");
}
#[test]
fn test_propolis_server() {
verify_apis("propolis-server.json");
}
#[test]
fn test_param_override() {
verify_apis("param-overrides.json");
}
#[test]
fn test_yaml() {
verify_apis("param-overrides.yaml");
}
#[test]
fn test_param_collision() {
verify_apis("param-collision.json");
}
// TODO this file is full of inconsistencies and incorrectly specified types.
// It's an interesting test to consider whether we try to do our best to
// interpret the intent or just fail.
#[ignore]
#[test]
fn test_github() {
verify_apis("api.github.com.json");
}