Skip to content

Commit

Permalink
feat: support additional serde attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
murar8 committed Feb 3, 2024
1 parent 1f396f9 commit ee3dce3
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ keywords = ["serde"]
license = "MIT"
name = "serde_nested_with"
repository = "https://github.com/murar8/serde_nested_with"
version = "0.2.2"
version = "0.2.3"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ mod example {
#[serde_nested(sub = "OffsetDateTime", serde(with = "rfc3339"))]
pub bar: Option<Option<OffsetDateTime>>,
#[serde_nested(sub = "OffsetDateTime", serde(with = "rfc3339"))]
pub bar5: Vec<(OffsetDateTime, OffsetDateTime)>,
#[serde(rename = "other")]
pub baz: Vec<(OffsetDateTime, OffsetDateTime)>,
}
}
```
Expand Down
51 changes: 36 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use std::collections::HashMap;
use std::hash::{Hash as _, Hasher};
use syn::spanned::Spanned;

const WRAPPER_NAME: &str = "__Wrapper";
const ATTRIBUTE_NAME: &str = "serde_nested";

#[derive(Debug, FromField)]
#[derive(Debug, Clone, FromField)]
#[darling(attributes(serde_nested))]
struct Field {
ty: syn::Type,
Expand All @@ -23,6 +20,9 @@ struct Field {
}

impl Field {
/// Generate a unique name for the module that will contain the wrapper type and the
/// (de)serialization functions. The name is based on the field type, the substitute type,
/// and the serde attributes. In this way, we do not generate duplicate modules.
fn module_name(&self) -> String {
let hasher = &mut DefaultHasher::new();
self.ty.hash(hasher);
Expand All @@ -31,19 +31,22 @@ impl Field {
format!("__serde_nested_{}", hasher.finish())
}

fn wrapper_type(&self) -> String {
/// Get the ident of the wrapper type that will be used to (de)serialize the field.
///
/// Example: `Option<OffsetDateTime>` -> `Option<__Wrapper>`
fn wrapper_type_ident(&self) -> syn::Path {
let ty = self.ty.to_token_stream().to_string();
let sub = self.substitute.to_token_stream().to_string();
ty.replace(&sub, WRAPPER_NAME)
}

fn wrapper_type_ident(&self) -> syn::Path {
let ty = self.wrapper_type();
let ty = ty.replace(&sub, "__Wrapper");
syn::parse_str(&ty).unwrap()
}

/// Get the ident of the wrapper type that will be used to (de)serialize the field, with
/// turbofish syntax.
///
/// Example: `Option<OffsetDateTime>` -> `Option::<__Wrapper>`
fn wrapper_type_turbofish(&self) -> syn::Path {
let ty = self.wrapper_type();
let ty = self.wrapper_type_ident().to_token_stream().to_string();
let ty = ty.replacen('<', " :: <", 1);
syn::parse_str(&ty).unwrap()
}
Expand All @@ -53,22 +56,40 @@ impl Field {
#[proc_macro_attribute]
pub fn serde_nested(_: TokenStream, input: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ItemStruct);

// Contains the field information for each module.
let mut fields = HashMap::new();

for field in input.fields.iter_mut() {
let info = match Field::from_field(field) {
Ok(field) => field,
Err(_) => continue,
};

// Remove all #[serde(...)] attributes from the field so we can merge them with our own.
let mut serde_attributes = Vec::new();
field.attrs.retain(|attr| {
if let syn::Meta::List(ref list) = attr.meta {
if let Some(syn::PathSegment { ident, .. }) = list.path.segments.first() {
if ident == "serde" {
serde_attributes.push(list.tokens.clone());
return false;
}
}
}
true
});

// Replace #[serde_nested] with #[serde(with = "module_name", ...)] and store the field
// information for later use.
for attr in field.attrs.iter_mut() {
if let syn::Meta::List(ref mut list) = attr.meta {
if let Some(syn::PathSegment { ident, .. }) = list.path.segments.first_mut() {
if ident == ATTRIBUTE_NAME {
if ident == "serde_nested" {
*ident = syn::parse_quote!(serde);
let module_name = info.module_name();
list.tokens = quote! { with = #module_name };
fields.insert(module_name, info);
break;
list.tokens = quote! { with = #module_name, #(#serde_attributes),* };
fields.insert(module_name, info.clone());
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/test_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct Foo {
#[serde_nested(sub = "time::OffsetDateTime", serde(with = "rfc3339"))]
pub bar4: BTreeMap<i32, time::OffsetDateTime>,
#[serde_nested(sub = "time::OffsetDateTime", serde(with = "rfc3339"))]
#[serde(rename = "baz")]
#[serde(default)]
pub bar5: Vec<(time::OffsetDateTime, time::OffsetDateTime)>,
}

Expand Down Expand Up @@ -75,7 +77,7 @@ fn test_with() {
Token::I32(1),
Token::Str("2001-09-09T01:46:40Z"),
Token::MapEnd,
Token::Str("bar5"),
Token::Str("baz"),
Token::Seq { len: Some(1) },
Token::Tuple { len: 2 },
Token::Str("2001-09-09T01:46:40Z"),
Expand Down

0 comments on commit ee3dce3

Please sign in to comment.