Skip to content

Commit

Permalink
feat: add support for serialize and deserialize at the same time
Browse files Browse the repository at this point in the history
  • Loading branch information
murar8 committed Feb 2, 2024
1 parent fe8205c commit 0cb0b74
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 10 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ mod example {

- Only one of the fields can be substituted in case multiple generics are present.

- Currently only one attribute between `with`, `serialize_with` and `deserialize_with` is
allowed per field. This can be easily extended in the future if needed.

## Release process

When a [SemVer](https://semver.org/) compatible git tag is pushed to the repo a new version of the package will be published to [crates.io](https://crates.io/crates/serde_nested_with).
Expand Down
48 changes: 41 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ impl Field {
/// Returns the name of the argument that should be passed to the serde operation.
fn serde_operation(&self) -> &str {
match self {
Field { serialize_with: Some(_), deserialize_with: Some(_), .. } => "with",
Field { with: Some(_), .. } => "with",
Field { serialize_with: Some(_), .. } => "serialize_with",
Field { deserialize_with: Some(_), .. } => "deserialize_with",
Expand All @@ -37,6 +38,11 @@ impl Field {
syn::Ident::new(self.serde_operation(), self.ty.span())
}

fn helper_module_name(&self) -> String {
let base_name = self.outer_module_base_name();
base_name + "_helper"
}

/// Returns the name of the generated module that will be used for (de)serialization.
fn outer_module_base_name(&self) -> String {
let mut hasher = DefaultHasher::new();
Expand All @@ -52,6 +58,7 @@ impl Field {
fn outer_module_name_with_op(&self) -> String {
let base_name = self.outer_module_base_name();
match self {
Field { serialize_with: Some(_), deserialize_with: Some(_), .. } => base_name,
Field { with: Some(_), .. } => base_name,
Field { serialize_with: Some(_), .. } => base_name + "::serialize",
Field { deserialize_with: Some(_), .. } => base_name + "::deserialize",
Expand All @@ -61,11 +68,13 @@ impl Field {

/// Returns the path to the user provided module or function that will be used for
/// (de)serialization.
fn inner_module_name_with_op(&self) -> &str {
fn inner_module_name_with_op(&self) -> String {
let helper_name = self.helper_module_name();
match self {
Field { with: Some(path), .. } => path,
Field { serialize_with: Some(path), .. } => path,
Field { deserialize_with: Some(path), .. } => path,
Field { serialize_with: Some(_), deserialize_with: Some(_), .. } => helper_name,
Field { with: Some(path), .. } => path.clone(),
Field { serialize_with: Some(path), .. } => path.clone(),
Field { deserialize_with: Some(path), .. } => path.clone(),
_ => abort!(self.ty.span(), "missing serde operation"),
}
}
Expand Down Expand Up @@ -139,8 +148,8 @@ pub fn serde_nested_with(_: TokenStream, input: TokenStream) -> TokenStream {
}
}

let modules = modules.into_iter().map(|(outer_module_name, attrs)| {
let outer_module_name = syn::Ident::new(&outer_module_name, attrs.ty.span());
let convert_modules = modules.iter().map(|(outer_module_name, attrs)| {
let outer_module_name = syn::Ident::new(outer_module_name, attrs.ty.span());
let inner_module_name = &attrs.inner_module_name_with_op();
let field_ty = &attrs.ty;
let operation = attrs.serde_operation_ident();
Expand Down Expand Up @@ -182,8 +191,33 @@ pub fn serde_nested_with(_: TokenStream, input: TokenStream) -> TokenStream {
}
});

let helper_modules = modules.values().map(|attrs| {
let helper_module_name = syn::Ident::new(&attrs.helper_module_name(), attrs.ty.span());
let serialize_with =
match attrs.serialize_with.as_ref().map(|s| syn::parse_str::<syn::Expr>(s)) {
Some(Ok(s)) => quote! { pub use super::#s; },
None => quote! {},
Some(Err(_)) => abort!(attrs.ty, "failed to parse serialize_with"),
};
let deserialize_with =
match attrs.deserialize_with.as_ref().map(|s| syn::parse_str::<syn::Expr>(s)) {
Some(Ok(s)) => quote! { pub use super::#s; },
None => quote! {},
Some(Err(_)) => abort!(attrs.ty, "failed to parse deserialize_with"),
};

quote! {
mod #helper_module_name {
use super::*;
#serialize_with
#deserialize_with
}
}
});

let output = quote! {
#(#modules)*
#(#helper_modules)*
#(#convert_modules)*
#input
};
output.into()
Expand Down
32 changes: 32 additions & 0 deletions tests/test_both.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};
use serde_nested_with::serde_nested_with;
use serde_test::{assert_tokens, Token};
use time::serde::rfc3339;
use time::OffsetDateTime;

#[serde_nested_with]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Foo {
#[serde_nested_with(
substitute = "Option<_>",
serialize_with = "rfc3339::serialize",
deserialize_with = "rfc3339::deserialize"
)]
pub bar: Option<OffsetDateTime>,
}

#[test]
fn test_with() {
let item = Foo { bar: OffsetDateTime::from_unix_timestamp(1000000000).ok() };

assert_tokens(
&item,
&[
Token::Struct { name: "Foo", len: 1 },
Token::Str("bar"),
Token::Some,
Token::Str("2001-09-09T01:46:40Z"),
Token::StructEnd,
],
);
}

0 comments on commit 0cb0b74

Please sign in to comment.