Allow conversion overrides by specifying a schema (#280)

This commit is contained in:
Adam Leventhal 2022-12-27 11:55:57 -08:00 committed by GitHub
parent df5f513083
commit 57031d77d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 102 additions and 27 deletions

7
Cargo.lock generated
View File

@ -1158,6 +1158,7 @@ dependencies = [
"proc-macro2",
"progenitor-impl",
"quote",
"schemars",
"serde",
"serde_json",
"serde_tokenstream",
@ -1928,7 +1929,7 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3"
source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0"
dependencies = [
"typify-impl",
"typify-macro",
@ -1937,7 +1938,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3"
source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0"
dependencies = [
"heck",
"log",
@ -1955,7 +1956,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3"
source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0"
dependencies = [
"proc-macro2",
"quote",

View File

@ -15,5 +15,6 @@ members = [
#typify = { path = "../typify/typify" }
#[patch.crates-io]
#serde_tokenstream = { path = "../serde_tokenstream" }
#typify = { path = "../typify/typify" }
#rustfmt-wrapper = { path = "../rustfmt-wrapper" }

View File

@ -3,8 +3,9 @@ name = "progenitor-impl"
version = "0.2.1-dev"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator - core implementation"
repository = "https://github.com/oxidecomputer/progenitor.git"
readme = "../README.md"
[dependencies]
heck = "0.4.0"

View File

@ -0,0 +1 @@
pre-release-replacements = []

View File

@ -52,6 +52,7 @@ pub struct GenerationSettings {
post_hook: Option<TokenStream>,
extra_derives: Vec<String>,
patch: HashMap<String, TypePatch>,
convert: Vec<(schemars::schema::SchemaObject, String, Vec<String>)>,
}
#[derive(Clone, Deserialize, PartialEq, Eq)]
@ -122,6 +123,20 @@ impl GenerationSettings {
.insert(type_name.as_ref().to_string(), patch.clone());
self
}
pub fn with_conversion<S: ToString, I: Iterator<Item = impl ToString>>(
&mut self,
schema: schemars::schema::SchemaObject,
type_name: S,
impls: I,
) -> &mut Self {
self.convert.push((
schema,
type_name.to_string(),
impls.map(|x| x.to_string()).collect(),
));
self
}
}
impl Default for Generator {
@ -149,6 +164,16 @@ impl Generator {
settings.patch.iter().for_each(|(type_name, patch)| {
type_settings.with_patch(type_name, patch);
});
settings
.convert
.iter()
.for_each(|(schema, type_name, impls)| {
type_settings.with_conversion(
schema.clone(),
type_name,
impls.iter(),
);
});
Self {
type_space: TypeSpace::new(&type_settings),
settings: settings.clone(),

View File

@ -55,9 +55,9 @@ pub mod types {
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReportFinishBody {
pub duration_millis: i32,
pub duration_millis: usize,
pub end_time: chrono::DateTime<chrono::offset::Utc>,
pub exit_status: i32,
pub exit_status: usize,
pub id: ReportId,
}
@ -120,11 +120,11 @@ pub mod types {
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReportSummary {
pub age_seconds: i32,
pub duration_seconds: i32,
pub age_seconds: usize,
pub duration_seconds: usize,
pub host: String,
pub job: String,
pub status: i32,
pub status: usize,
pub when: chrono::DateTime<chrono::offset::Utc>,
}
@ -324,9 +324,9 @@ pub mod types {
}
pub struct ReportFinishBody {
duration_millis: Result<i32, String>,
duration_millis: Result<usize, String>,
end_time: Result<chrono::DateTime<chrono::offset::Utc>, String>,
exit_status: Result<i32, String>,
exit_status: Result<usize, String>,
id: Result<super::ReportId, String>,
}
@ -344,7 +344,7 @@ pub mod types {
impl ReportFinishBody {
pub fn duration_millis<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<i32>,
T: std::convert::TryInto<usize>,
T::Error: std::fmt::Display,
{
self.duration_millis = value.try_into().map_err(|e| {
@ -364,7 +364,7 @@ pub mod types {
}
pub fn exit_status<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<i32>,
T: std::convert::TryInto<usize>,
T::Error: std::fmt::Display,
{
self.exit_status = value
@ -624,11 +624,11 @@ pub mod types {
}
pub struct ReportSummary {
age_seconds: Result<i32, String>,
duration_seconds: Result<i32, String>,
age_seconds: Result<usize, String>,
duration_seconds: Result<usize, String>,
host: Result<String, String>,
job: Result<String, String>,
status: Result<i32, String>,
status: Result<usize, String>,
when: Result<chrono::DateTime<chrono::offset::Utc>, String>,
}
@ -648,7 +648,7 @@ pub mod types {
impl ReportSummary {
pub fn age_seconds<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<i32>,
T: std::convert::TryInto<usize>,
T::Error: std::fmt::Display,
{
self.age_seconds = value
@ -658,7 +658,7 @@ pub mod types {
}
pub fn duration_seconds<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<i32>,
T: std::convert::TryInto<usize>,
T::Error: std::fmt::Display,
{
self.duration_seconds = value.try_into().map_err(|e| {
@ -691,7 +691,7 @@ pub mod types {
}
pub fn status<T>(mut self, value: T) -> Self
where
T: std::convert::TryInto<i32>,
T: std::convert::TryInto<usize>,
T::Error: std::fmt::Display,
{
self.status = value

View File

@ -26,7 +26,18 @@ fn verify_apis(openapi_file: &str) {
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Merged)
.with_derive("JsonSchema")
.with_patch("Name", TypePatch::default().with_derive("Hash")),
.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",
["Display"].into_iter(),
),
);
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(

View File

@ -3,18 +3,20 @@ name = "progenitor-macro"
version = "0.2.1-dev"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator - macros"
repository = "https://github.com/oxidecomputer/progenitor.git"
readme = "../README.md"
[lib]
proc-macro = true
[dependencies]
openapiv3 = "1.0.0"
proc-macro2 = "1.0"
progenitor-impl = { version = "0.2.1-dev", path = "../progenitor-impl" }
quote = "1.0"
schemars = "0.8.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_tokenstream = "0.1.6"
syn = "1.0"
[lib]
proc-macro = true

View File

@ -0,0 +1 @@
pre-release-replacements = []

View File

@ -1,6 +1,6 @@
// Copyright 2022 Oxide Computer Company
use std::{collections::HashMap, path::Path};
use std::{collections::HashMap, fmt::Display, path::Path};
use openapiv3::OpenAPI;
use proc_macro::TokenStream;
@ -8,8 +8,9 @@ use progenitor_impl::{
GenerationSettings, Generator, InterfaceStyle, TagStyle, TypePatch,
};
use quote::{quote, ToTokens};
use schemars::schema::SchemaObject;
use serde::Deserialize;
use serde_tokenstream::ParseWrapper;
use serde_tokenstream::{OrderedMap, ParseWrapper};
use syn::LitStr;
/// Generates a client from the given OpenAPI document
@ -82,6 +83,24 @@ struct MacroSettings {
derives: Vec<ParseWrapper<syn::Path>>,
#[serde(default)]
patch: HashMap<ParseWrapper<syn::Type>, MacroPatch>,
#[serde(default)]
convert: OrderedMap<
SchemaObject,
(ParseWrapper<syn::Path>, Vec<MacroSettingsImpl>),
>,
}
#[derive(Deserialize)]
enum MacroSettingsImpl {
Display,
}
impl Display for MacroSettingsImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MacroSettingsImpl::Display => f.write_str("Display"),
}
}
}
#[derive(Deserialize)]
@ -153,6 +172,7 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
post_hook,
derives,
patch,
convert,
} = serde_tokenstream::from_tokenstream(&item.into())?;
let mut settings = GenerationSettings::default();
settings.with_interface(interface);
@ -174,6 +194,15 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
&patch.into(),
);
});
convert
.into_iter()
.for_each(|(schema, (type_name, impls))| {
settings.with_conversion(
schema,
type_name.to_token_stream(),
impls.into_iter(),
);
});
(spec.into_inner(), settings)
};

View File

@ -3,8 +3,11 @@ name = "progenitor"
version = "0.2.1-dev"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator"
repository = "https://github.com/oxidecomputer/progenitor.git"
readme = "../README.md"
keywords = ["openapi", "openapiv3", "sdk", "generator", "proc_macro"]
categories = ["api-bindings", "compilers"]
[dependencies]
progenitor-client = { version = "0.2.1-dev", path = "../progenitor-client" }