From a50f78e938a956fd3d3b232b539fbd766eb2014a Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 3 Nov 2021 16:01:57 +0100 Subject: [PATCH] Add `deserialize_with` attribute that is forwarded to `serde` --- macro/src/gen.rs | 68 ++++++++++++++++++++++++++++++++++++++---------- macro/src/ir.rs | 33 ++++++++++++++++++++++- src/internal.rs | 7 +++++ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/macro/src/gen.rs b/macro/src/gen.rs index f93329b..ccddbb4 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -76,6 +76,10 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { let (mod_name, struct_name) = partial_names(&input.name); let visibility = &input.visibility; + fn deserialize_fn_name(field_name: &Ident) -> Ident { + quote::format_ident!("deserialize_{}", field_name) + } + // Prepare some tokens per field. let field_names = input.fields.iter().map(|f| &f.name).collect::>(); let struct_fields = input.fields.iter().map(|f| { @@ -85,16 +89,22 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { // messages from the `derive(serde::Deserialize)` have the correct span. let inner_vis = inner_visibility(&input.visibility, name.span()); match &f.kind { - FieldKind::Leaf { kind: LeafKind::Required { ty, .. }, .. } => { - quote_spanned! {name.span()=> - #inner_vis #name: Option<#ty> - } + FieldKind::Leaf { kind, deserialize_with, .. } => { + let ty = kind.inner_ty(); + let attr = match deserialize_with { + None => quote! {}, + Some(p) => { + // let s = p.to_token_stream().to_string(); + let fn_name = deserialize_fn_name(&f.name).to_string(); + quote_spanned! {p.span()=> + #[serde(default, deserialize_with = #fn_name)] + } + } + }; + + let main = quote_spanned! {name.span()=> #inner_vis #name: Option<#ty> }; + quote! { #attr #main } } - FieldKind::Leaf { kind: LeafKind::Optional { inner_ty }, .. } => { - quote_spanned! {name.span()=> - #inner_vis #name: Option<#inner_ty> - } - }, FieldKind::Nested { ty } => { let ty_span = ty.span(); let field_ty = quote_spanned! {ty_span=> <#ty as confique::Config>::Partial }; @@ -116,15 +126,24 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { let defaults = input.fields.iter().map(|f| { match &f.kind { - FieldKind::Leaf { kind: LeafKind::Required { default: Some(default), .. }, .. } => { + FieldKind::Leaf { + kind: LeafKind::Required { default: Some(default), .. }, + deserialize_with, + .. + } => { let msg = format!( "default config value for `{}::{}` cannot be deserialized", input.name, f.name, ); - quote! { - Some(confique::internal::deserialize_default(#default).expect(#msg)) + match deserialize_with { + None => quote! { + Some(confique::internal::deserialize_default(#default).expect(#msg)) + }, + Some(p) => quote! { + Some(#p(confique::internal::into_deserializer(#default)).expect(#msg)) + }, } } FieldKind::Leaf { .. } => quote! { None }, @@ -177,6 +196,25 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { } }); + let deserialize_fns = input.fields.iter().filter_map(|f| { + match &f.kind { + FieldKind::Leaf { kind, deserialize_with: Some(p), .. } => { + let fn_name = deserialize_fn_name(&f.name); + let ty = kind.inner_ty(); + + Some(quote! { + fn #fn_name<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + #p(deserializer).map(Some) + } + }) + } + _ => None, + } + }); + let nested_bounds = input.fields.iter().filter_map(|f| { match &f.kind { FieldKind::Nested { ty } => Some(quote! { #ty: confique::Config }), @@ -228,6 +266,8 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { true #(&& #is_complete_expr)* } } + + #(#deserialize_fns)* } } } @@ -253,7 +293,7 @@ fn gen_meta(input: &ir::Input) -> TokenStream { confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META } } } - FieldKind::Leaf { env, kind: LeafKind::Optional { .. }} => { + FieldKind::Leaf { env, kind: LeafKind::Optional { .. }, ..} => { let env = env_tokens(env); quote! { confique::meta::FieldKind::Leaf { @@ -262,7 +302,7 @@ fn gen_meta(input: &ir::Input) -> TokenStream { } } } - FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }} => { + FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }, ..} => { let env = env_tokens(env); let default_value = gen_meta_default(default, &ty); quote! { diff --git a/macro/src/ir.rs b/macro/src/ir.rs index e6ee088..a1e9a4b 100644 --- a/macro/src/ir.rs +++ b/macro/src/ir.rs @@ -27,6 +27,7 @@ pub(crate) struct Field { pub(crate) enum FieldKind { Leaf { env: Option, + deserialize_with: Option, kind: LeafKind, }, @@ -53,6 +54,13 @@ impl LeafKind { pub(crate) fn is_required(&self) -> bool { matches!(self, Self::Required { .. }) } + + pub(crate) fn inner_ty(&self) -> &syn::Type { + match self { + Self::Required { ty, .. } => ty, + Self::Optional { inner_ty } => inner_ty, + } + } } /// The kinds of expressions (just literals) we allow for default or example @@ -115,12 +123,19 @@ impl Field { "cannot specify `nested` and `env` attributes at the same time", )); } + if attrs.deserialize_with.is_some() { + return Err(Error::new( + field.ident.span(), + "cannot specify `nested` and `deserialize_with` attributes at the same time", + )); + } FieldKind::Nested { ty: field.ty } } else { match unwrap_option(&field.ty) { None => FieldKind::Leaf { env: attrs.env, + deserialize_with: attrs.deserialize_with, kind: LeafKind::Required { default: attrs.default, ty: field.ty, @@ -137,6 +152,7 @@ impl Field { FieldKind::Leaf { env: attrs.env, + deserialize_with: attrs.deserialize_with, kind: LeafKind::Optional { inner_ty: inner.clone(), }, @@ -251,6 +267,10 @@ fn extract_internal_attrs( duplicate_if!(out.env.is_some()); out.env = Some(key); } + InternalAttr::DeserializeWith(path) => { + duplicate_if!(out.deserialize_with.is_some()); + out.deserialize_with = Some(path); + } } } } @@ -262,13 +282,15 @@ fn extract_internal_attrs( struct InternalAttrs { nested: bool, default: Option, - env: Option + env: Option, + deserialize_with: Option, } enum InternalAttr { Nested, Default(Expr), Env(String), + DeserializeWith(syn::Path), } impl InternalAttr { @@ -277,6 +299,7 @@ impl InternalAttr { Self::Nested => "nested", Self::Default(_) => "default", Self::Env(_) => "env", + Self::DeserializeWith(_) => "deserialize_with", } } } @@ -312,6 +335,14 @@ impl Parse for InternalAttr { } } + "deserialize_with" => { + let _: Token![=] = input.parse()?; + let path: syn::Path = input.parse()?; + assert_empty_or_comma(input)?; + + Ok(Self::DeserializeWith(path)) + } + _ => Err(syn::Error::new(ident.span(), "unknown confique attribute")), } } diff --git a/src/internal.rs b/src/internal.rs index 33816bf..4db4b1b 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -12,6 +12,13 @@ where O::deserialize(src.into_deserializer()) } +pub fn into_deserializer<'de, T>(src: T) -> >::Deserializer +where + T: serde::de::IntoDeserializer<'de>, +{ + src.into_deserializer() +} + pub fn missing_value_error(path: String) -> Error { ErrorInner::MissingValue(path).into() }