Let consumers replace types by name (#317)
This commit is contained in:
parent
35eab2f017
commit
9f0a9910ab
|
@ -2025,7 +2025,7 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
|||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
||||
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
|
@ -2034,7 +2034,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
||||
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
|
@ -2052,7 +2052,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#aa5e5a6c413920385af6b2c74252ea7d55d3d138"
|
||||
source = "git+https://github.com/oxidecomputer/typify#2790d798ea650c08686d014fdd87c25d942806b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -51,7 +51,9 @@ pub struct GenerationSettings {
|
|||
pre_hook: Option<TokenStream>,
|
||||
post_hook: Option<TokenStream>,
|
||||
extra_derives: Vec<String>,
|
||||
|
||||
patch: HashMap<String, TypePatch>,
|
||||
replace: HashMap<String, (String, Vec<String>)>,
|
||||
convert: Vec<(schemars::schema::SchemaObject, String, Vec<String>)>,
|
||||
}
|
||||
|
||||
|
@ -124,6 +126,26 @@ impl GenerationSettings {
|
|||
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>>(
|
||||
&mut self,
|
||||
schema: schemars::schema::SchemaObject,
|
||||
|
@ -164,6 +186,15 @@ impl Generator {
|
|||
settings.patch.iter().for_each(|(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
|
||||
.convert
|
||||
.iter()
|
||||
|
|
|
@ -17,6 +17,9 @@ use schemars::schema::SchemaObject;
|
|||
use serde::Deserialize;
|
||||
use serde_tokenstream::{OrderedMap, ParseWrapper};
|
||||
use syn::LitStr;
|
||||
use token_utils::TypeAndImpls;
|
||||
|
||||
mod token_utils;
|
||||
|
||||
/// Generates a client from the given OpenAPI document
|
||||
///
|
||||
|
@ -81,18 +84,20 @@ struct MacroSettings {
|
|||
interface: InterfaceStyle,
|
||||
#[serde(default)]
|
||||
tags: TagStyle,
|
||||
|
||||
inner_type: Option<ParseWrapper<syn::Type>>,
|
||||
pre_hook: Option<ParseWrapper<ClosureOrPath>>,
|
||||
post_hook: Option<ParseWrapper<ClosureOrPath>>,
|
||||
|
||||
#[serde(default)]
|
||||
derives: Vec<ParseWrapper<syn::Path>>,
|
||||
|
||||
#[serde(default)]
|
||||
patch: HashMap<ParseWrapper<syn::Type>, MacroPatch>,
|
||||
patch: HashMap<ParseWrapper<syn::Ident>, MacroPatch>,
|
||||
#[serde(default)]
|
||||
convert: OrderedMap<
|
||||
SchemaObject,
|
||||
(ParseWrapper<syn::Path>, Vec<MacroSettingsImpl>),
|
||||
>,
|
||||
replace: HashMap<ParseWrapper<syn::Ident>, ParseWrapper<TypeAndImpls>>,
|
||||
#[serde(default)]
|
||||
convert: OrderedMap<SchemaObject, ParseWrapper<TypeAndImpls>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -187,6 +192,7 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
|||
post_hook,
|
||||
derives,
|
||||
patch,
|
||||
replace,
|
||||
convert,
|
||||
} = serde_tokenstream::from_tokenstream(&item.into())?;
|
||||
let mut settings = GenerationSettings::default();
|
||||
|
@ -209,15 +215,25 @@ 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(),
|
||||
);
|
||||
});
|
||||
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()
|
||||
.map(|x| x.to_token_stream());
|
||||
settings.with_replacement(type_name, replace_name, impls);
|
||||
});
|
||||
convert.into_iter().for_each(|(schema, type_and_impls)| {
|
||||
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)
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
mod nexus_client {
|
||||
pub type MyIpv4Net = String;
|
||||
progenitor::generate_api!(
|
||||
spec = "../sample_openapi/nexus.json",
|
||||
interface = Builder,
|
||||
|
@ -35,6 +36,9 @@ mod builder_untagged {
|
|||
Name = {
|
||||
derives = [Hash],
|
||||
}
|
||||
},
|
||||
replace = {
|
||||
Ipv4Net = crate::builder_untagged::nexus_client::MyIpv4Net,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -42,6 +46,9 @@ mod builder_untagged {
|
|||
use nexus_client::Client;
|
||||
|
||||
pub fn _ignore() {
|
||||
// Verify the replacement above.
|
||||
let _ignore = nexus_client::types::IpNet::V4(String::new());
|
||||
|
||||
let client = Client::new("");
|
||||
let stream = client
|
||||
.instance_disk_list()
|
||||
|
|
Loading…
Reference in New Issue