Let consumers replace types by name (#317)
This commit is contained in:
parent
35eab2f017
commit
9f0a9910ab
|
@ -2025,7 +2025,7 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify"
|
name = "typify"
|
||||||
version = "0.0.11-dev"
|
version = "0.0.11-dev"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typify-impl",
|
"typify-impl",
|
||||||
"typify-macro",
|
"typify-macro",
|
||||||
|
@ -2034,7 +2034,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify-impl"
|
name = "typify-impl"
|
||||||
version = "0.0.11-dev"
|
version = "0.0.11-dev"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2052,7 +2052,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify-macro"
|
name = "typify-macro"
|
||||||
version = "0.0.11-dev"
|
version = "0.0.11-dev"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -51,7 +51,9 @@ pub struct GenerationSettings {
|
||||||
pre_hook: Option<TokenStream>,
|
pre_hook: Option<TokenStream>,
|
||||||
post_hook: Option<TokenStream>,
|
post_hook: Option<TokenStream>,
|
||||||
extra_derives: Vec<String>,
|
extra_derives: Vec<String>,
|
||||||
|
|
||||||
patch: HashMap<String, TypePatch>,
|
patch: HashMap<String, TypePatch>,
|
||||||
|
replace: HashMap<String, (String, Vec<String>)>,
|
||||||
convert: Vec<(schemars::schema::SchemaObject, String, Vec<String>)>,
|
convert: Vec<(schemars::schema::SchemaObject, String, Vec<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +126,26 @@ impl GenerationSettings {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_replacement<
|
||||||
|
TS: ToString,
|
||||||
|
RS: ToString,
|
||||||
|
I: Iterator<Item = impl ToString>,
|
||||||
|
>(
|
||||||
|
&mut self,
|
||||||
|
type_name: TS,
|
||||||
|
replace_name: RS,
|
||||||
|
impls: I,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.replace.insert(
|
||||||
|
type_name.to_string(),
|
||||||
|
(
|
||||||
|
replace_name.to_string(),
|
||||||
|
impls.map(|x| x.to_string()).collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_conversion<S: ToString, I: Iterator<Item = impl ToString>>(
|
pub fn with_conversion<S: ToString, I: Iterator<Item = impl ToString>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
schema: schemars::schema::SchemaObject,
|
schema: schemars::schema::SchemaObject,
|
||||||
|
@ -164,6 +186,15 @@ impl Generator {
|
||||||
settings.patch.iter().for_each(|(type_name, patch)| {
|
settings.patch.iter().for_each(|(type_name, patch)| {
|
||||||
type_settings.with_patch(type_name, patch);
|
type_settings.with_patch(type_name, patch);
|
||||||
});
|
});
|
||||||
|
settings.replace.iter().for_each(
|
||||||
|
|(type_name, (replace_name, impls))| {
|
||||||
|
type_settings.with_replacement(
|
||||||
|
type_name,
|
||||||
|
replace_name,
|
||||||
|
impls.iter(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
settings
|
settings
|
||||||
.convert
|
.convert
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -17,6 +17,9 @@ use schemars::schema::SchemaObject;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_tokenstream::{OrderedMap, ParseWrapper};
|
use serde_tokenstream::{OrderedMap, ParseWrapper};
|
||||||
use syn::LitStr;
|
use syn::LitStr;
|
||||||
|
use token_utils::TypeAndImpls;
|
||||||
|
|
||||||
|
mod token_utils;
|
||||||
|
|
||||||
/// Generates a client from the given OpenAPI document
|
/// Generates a client from the given OpenAPI document
|
||||||
///
|
///
|
||||||
|
@ -81,18 +84,20 @@ struct MacroSettings {
|
||||||
interface: InterfaceStyle,
|
interface: InterfaceStyle,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
tags: TagStyle,
|
tags: TagStyle,
|
||||||
|
|
||||||
inner_type: Option<ParseWrapper<syn::Type>>,
|
inner_type: Option<ParseWrapper<syn::Type>>,
|
||||||
pre_hook: Option<ParseWrapper<ClosureOrPath>>,
|
pre_hook: Option<ParseWrapper<ClosureOrPath>>,
|
||||||
post_hook: Option<ParseWrapper<ClosureOrPath>>,
|
post_hook: Option<ParseWrapper<ClosureOrPath>>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
derives: Vec<ParseWrapper<syn::Path>>,
|
derives: Vec<ParseWrapper<syn::Path>>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
patch: HashMap<ParseWrapper<syn::Type>, MacroPatch>,
|
patch: HashMap<ParseWrapper<syn::Ident>, MacroPatch>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
convert: OrderedMap<
|
replace: HashMap<ParseWrapper<syn::Ident>, ParseWrapper<TypeAndImpls>>,
|
||||||
SchemaObject,
|
#[serde(default)]
|
||||||
(ParseWrapper<syn::Path>, Vec<MacroSettingsImpl>),
|
convert: OrderedMap<SchemaObject, ParseWrapper<TypeAndImpls>>,
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -187,6 +192,7 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
||||||
post_hook,
|
post_hook,
|
||||||
derives,
|
derives,
|
||||||
patch,
|
patch,
|
||||||
|
replace,
|
||||||
convert,
|
convert,
|
||||||
} = serde_tokenstream::from_tokenstream(&item.into())?;
|
} = serde_tokenstream::from_tokenstream(&item.into())?;
|
||||||
let mut settings = GenerationSettings::default();
|
let mut settings = GenerationSettings::default();
|
||||||
|
@ -209,14 +215,24 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
||||||
&patch.into(),
|
&patch.into(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
convert
|
replace.into_iter().for_each(|(type_name, type_and_impls)| {
|
||||||
|
let type_name = type_name.to_token_stream();
|
||||||
|
let type_and_impls = type_and_impls.into_inner();
|
||||||
|
let replace_name = type_and_impls.type_name.to_token_stream();
|
||||||
|
let impls = type_and_impls
|
||||||
|
.impls
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(schema, (type_name, impls))| {
|
.map(|x| x.to_token_stream());
|
||||||
settings.with_conversion(
|
settings.with_replacement(type_name, replace_name, impls);
|
||||||
schema,
|
});
|
||||||
type_name.to_token_stream(),
|
convert.into_iter().for_each(|(schema, type_and_impls)| {
|
||||||
impls.into_iter(),
|
let type_and_impls = type_and_impls.into_inner();
|
||||||
);
|
let type_name = type_and_impls.type_name.to_token_stream();
|
||||||
|
let impls = type_and_impls
|
||||||
|
.impls
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.to_token_stream());
|
||||||
|
settings.with_conversion(schema, type_name, impls);
|
||||||
});
|
});
|
||||||
(spec.into_inner(), settings)
|
(spec.into_inner(), settings)
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright 2023 Oxide Computer Company
|
||||||
|
|
||||||
|
use quote::ToTokens;
|
||||||
|
use syn::{
|
||||||
|
parse::Parse,
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::{Add, Colon},
|
||||||
|
Ident, Path, Token, TraitBoundModifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypeAndImpls {
|
||||||
|
pub type_name: Path,
|
||||||
|
pub colon_token: Option<Colon>,
|
||||||
|
pub impls: Punctuated<ImplTrait, Add>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for TypeAndImpls {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let type_name: Path = input.parse()?;
|
||||||
|
let colon_token: Option<Colon> = input.parse()?;
|
||||||
|
let mut impls = Punctuated::default();
|
||||||
|
|
||||||
|
if colon_token.is_some() {
|
||||||
|
loop {
|
||||||
|
let value: ImplTrait = input.parse()?;
|
||||||
|
impls.push_value(value);
|
||||||
|
if !input.peek(Token![+]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let punct: Token![+] = input.parse()?;
|
||||||
|
impls.push_punct(punct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
type_name,
|
||||||
|
colon_token,
|
||||||
|
impls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for TypeAndImpls {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
self.type_name.to_tokens(tokens);
|
||||||
|
self.colon_token.to_tokens(tokens);
|
||||||
|
self.impls.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ImplTrait {
|
||||||
|
pub modifier: TraitBoundModifier,
|
||||||
|
pub impl_name: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ImplTrait {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let modifier: TraitBoundModifier = input.parse()?;
|
||||||
|
let impl_name: Ident = input.parse()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
modifier,
|
||||||
|
impl_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ImplTrait {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
self.modifier.to_tokens(tokens);
|
||||||
|
self.impl_name.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::TypeAndImpls;
|
||||||
|
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_type_and_impls() {
|
||||||
|
let input = quote! { my_crate::MyType };
|
||||||
|
let value = syn::parse2::<TypeAndImpls>(input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
value.type_name.to_token_stream().to_string(),
|
||||||
|
"my_crate :: MyType",
|
||||||
|
);
|
||||||
|
assert_eq!(value.impls.len(), 0);
|
||||||
|
|
||||||
|
let input = quote! { my_crate::MyType: ?Display + Hash };
|
||||||
|
let value = syn::parse2::<TypeAndImpls>(input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
value.type_name.to_token_stream().to_string(),
|
||||||
|
"my_crate :: MyType",
|
||||||
|
);
|
||||||
|
assert_eq!(value.impls.len(), 2);
|
||||||
|
let mut ii = value.impls.into_iter();
|
||||||
|
assert_eq!(
|
||||||
|
ii.next().unwrap().to_token_stream().to_string(),
|
||||||
|
"? Display",
|
||||||
|
);
|
||||||
|
assert_eq!(ii.next().unwrap().to_token_stream().to_string(), "Hash",);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ mod builder_untagged {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
mod nexus_client {
|
mod nexus_client {
|
||||||
|
pub type MyIpv4Net = String;
|
||||||
progenitor::generate_api!(
|
progenitor::generate_api!(
|
||||||
spec = "../sample_openapi/nexus.json",
|
spec = "../sample_openapi/nexus.json",
|
||||||
interface = Builder,
|
interface = Builder,
|
||||||
|
@ -35,6 +36,9 @@ mod builder_untagged {
|
||||||
Name = {
|
Name = {
|
||||||
derives = [Hash],
|
derives = [Hash],
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
replace = {
|
||||||
|
Ipv4Net = crate::builder_untagged::nexus_client::MyIpv4Net,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +46,9 @@ mod builder_untagged {
|
||||||
use nexus_client::Client;
|
use nexus_client::Client;
|
||||||
|
|
||||||
pub fn _ignore() {
|
pub fn _ignore() {
|
||||||
|
// Verify the replacement above.
|
||||||
|
let _ignore = nexus_client::types::IpNet::V4(String::new());
|
||||||
|
|
||||||
let client = Client::new("");
|
let client = Client::new("");
|
||||||
let stream = client
|
let stream = client
|
||||||
.instance_disk_list()
|
.instance_disk_list()
|
||||||
|
|
Loading…
Reference in New Issue