Add deserialize_with attribute that is forwarded to serde

This commit is contained in:
Lukas Kalbertodt
2021-11-03 16:01:57 +01:00
parent a54042f8ca
commit a50f78e938
3 changed files with 93 additions and 15 deletions

View File

@@ -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::<Vec<_>>();
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<Option<#ty>, 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! {

View File

@@ -27,6 +27,7 @@ pub(crate) struct Field {
pub(crate) enum FieldKind {
Leaf {
env: Option<String>,
deserialize_with: Option<syn::Path>,
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<Expr>,
env: Option<String>
env: Option<String>,
deserialize_with: Option<syn::Path>,
}
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")),
}
}

View File

@@ -12,6 +12,13 @@ where
O::deserialize(src.into_deserializer())
}
pub fn into_deserializer<'de, T>(src: T) -> <T as serde::de::IntoDeserializer<'de>>::Deserializer
where
T: serde::de::IntoDeserializer<'de>,
{
src.into_deserializer()
}
pub fn missing_value_error(path: String) -> Error {
ErrorInner::MissingValue(path).into()
}