mirror of
https://github.com/OMGeeky/confique.git
synced 2025-12-26 16:07:44 +01:00
Add deserialize_with attribute that is forwarded to serde
This commit is contained in:
@@ -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! {
|
||||
|
||||
@@ -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")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user