From b71bf8bd74b36380356be3445495b5f7aed52490 Mon Sep 17 00:00:00 2001 From: kanarus Date: Sat, 14 Sep 2024 07:13:26 +0900 Subject: [PATCH] feat: support `#[serdev(crate = "path::to::serdev")]` (#21) * support `#[serdev(crate = "path::to::serdev")]` * fix workflows --- .github/workflows/auto_approve.yml | 2 +- .gitignore | 4 +- README.md | 10 ++-- Taskfile.yml | 1 + examples/.gitignore | 2 - examples/Cargo.toml | 7 ++- examples/reexport/Cargo.toml | 12 +++++ examples/reexport/reexporter/Cargo.toml | 7 +++ examples/reexport/reexporter/src/lib.rs | 3 ++ examples/reexport/src/main.rs | 34 +++++++++++++ serdev/Cargo.toml | 4 +- serdev_derive/Cargo.toml | 2 +- serdev_derive/src/internal.rs | 64 +++++++++++++++++-------- serdev_derive/src/internal/reexport.rs | 60 +++++++++++++++++++++++ serdev_derive/src/internal/validate.rs | 6 +-- serdev_derive/src/lib.rs | 4 +- 16 files changed, 184 insertions(+), 38 deletions(-) delete mode 100644 examples/.gitignore create mode 100644 examples/reexport/Cargo.toml create mode 100644 examples/reexport/reexporter/Cargo.toml create mode 100644 examples/reexport/reexporter/src/lib.rs create mode 100644 examples/reexport/src/main.rs create mode 100644 serdev_derive/src/internal/reexport.rs diff --git a/.github/workflows/auto_approve.yml b/.github/workflows/auto_approve.yml index f2e7891..f6a4ca6 100644 --- a/.github/workflows/auto_approve.yml +++ b/.github/workflows/auto_approve.yml @@ -12,7 +12,7 @@ on: jobs: auto_approve: if: | - github.event.pull_request.user.login == 'kana-rus' && + github.event.pull_request.user.login == 'kanarus' && !github.event.pull_request.draft permissions: pull-requests: write diff --git a/.gitignore b/.gitignore index 2ebc5ea..2a6fee4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target -/Cargo.lock \ No newline at end of file +**/target +**/Cargo.lock \ No newline at end of file diff --git a/README.md b/README.md index ca55b98..fb9af25 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@
-- Just a wrapper of Serde and 100% compatible. -- Automatic validation in deserialization by `#[serde(validate = "...")]`, no boilerplate. +- Just a wrapper of Serde and 100% compatible +- Declarative validation in deserialization by `#[serde(validate = "...")]`
@@ -25,7 +25,7 @@ ```toml [dependencies] -serdev = "0.1" +serdev = "0.2" serde_json = "1.0" ``` @@ -81,7 +81,9 @@ Of course, you can use it in combination with some validation tools like Result<(), impl std::fmt::Display> { + if self.x * self.y > 100 { + return Err("x * y must not exceed 100") + } + Ok(()) + } +} + +fn main() { + let point = serde_json::from_str::(r#" + { "x" : 1, "y" : 2 } + "#).unwrap(); + + // Prints point = Point { x: 1, y: 2 } + println!("point = {point:?}"); + + let error = serde_json::from_str::(r#" + { "x" : 10, "y" : 20 } + "#).unwrap_err(); + + // Prints error = x * y must not exceed 100 + println!("error = {error}"); +} diff --git a/serdev/Cargo.toml b/serdev/Cargo.toml index c134f67..e2df8d7 100644 --- a/serdev/Cargo.toml +++ b/serdev/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serdev" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["kanarus "] documentation = "https://docs.rs/serdev" @@ -13,7 +13,7 @@ keywords = ["serde", "validation", "serialization"] categories = ["encoding", "rust-patterns", "no-std", "no-std::no-alloc"] [dependencies] -serdev_derive = { version = "=0.1.0", path = "../serdev_derive" } +serdev_derive = { version = "=0.2.0", path = "../serdev_derive" } serde = { version = "1", features = ["derive"] } [dev-dependencies] diff --git a/serdev_derive/Cargo.toml b/serdev_derive/Cargo.toml index 041da46..f316e87 100644 --- a/serdev_derive/Cargo.toml +++ b/serdev_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serdev_derive" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["kanarus "] documentation = "https://docs.rs/serdev_derive" diff --git a/serdev_derive/src/internal.rs b/serdev_derive/src/internal.rs index bd50355..a05ef0e 100644 --- a/serdev_derive/src/internal.rs +++ b/serdev_derive/src/internal.rs @@ -1,22 +1,36 @@ mod target; mod validate; +mod reexport; use self::target::Target; use self::validate::Validate; +use self::reexport::Reexport; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{Error, LitStr}; pub(super) fn Serialize(input: TokenStream) -> Result { let mut target = syn::parse2::(input.clone())?; - Validate::take(target.attrs_mut())?; + + let _ = Validate::take(target.attrs_mut())?; + + let (serdev, serde) = match Reexport::take(target.attrs_mut())? { + None => ( + quote! {::serdev}, + litstr("::serdev::__private__::serde") + ), + Some(r) => ( + r.path()?.into_token_stream(), + litstr(&format!("{}::__private__::serde", r.path_str())) + ) + }; Ok(quote! { - #[derive(::serdev::__private__::serde::Serialize)] - #[serde(crate = "::serdev::__private__::serde")] - #[::serdev::__private__::consume] + #[derive(#serdev::__private__::serde::Serialize)] + #[serde(crate = #serde)] + #[#serdev::__private__::consume] #target }) } @@ -27,6 +41,17 @@ pub(super) fn Deserialize(input: TokenStream) -> Result { let generics = target.generics().clone(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let (serdev, serde) = match Reexport::take(target.attrs_mut())? { + None => ( + quote! {::serdev}, + litstr("::serdev::__private__::serde") + ), + Some(r) => ( + r.path()?.into_token_stream(), + litstr(&format!("{}::__private__::serde", r.path_str())) + ) + }; + Ok(match Validate::take(target.attrs_mut())? { Some(validate) => { let proxy = target.create_proxy(format_ident!("serdev_proxy_{}", target.ident())); @@ -36,10 +61,7 @@ pub(super) fn Deserialize(input: TokenStream) -> Result { let transmute_from_proxy = proxy.transmute_expr("proxy", target_ident); - let proxy_type_lit = LitStr::new( - "e!(#proxy_ident #ty_generics).to_string(), - Span::call_site() - ); + let proxy_type_lit = litstr("e!(#proxy_ident #ty_generics).to_string()); let validate_fn = validate.function()?; let (error_ty, e_as_error_ty) = match validate.error()? { @@ -48,15 +70,15 @@ pub(super) fn Deserialize(input: TokenStream) -> Result { quote! {e} ), None => ( - quote! {::serdev::__private__::DefaultError}, - quote! {::serdev::__private__::default_error(e)} + quote! {#serdev::__private__::DefaultError}, + quote! {#serdev::__private__::default_error(e)} ) }; quote! { const _: () = { - #[derive(::serdev::__private__::serde::Deserialize)] - #[serde(crate = "::serdev::__private__::serde")] + #[derive(#serdev::__private__::serde::Deserialize)] + #[serde(crate = #serde)] #[allow(non_camel_case_types)] #proxy @@ -73,10 +95,10 @@ pub(super) fn Deserialize(input: TokenStream) -> Result { } } - #[derive(::serdev::__private__::serde::Deserialize)] - #[serde(crate = "::serdev::__private__::serde")] + #[derive(#serdev::__private__::serde::Deserialize)] + #[serde(crate = #serde)] #[serde(try_from = #proxy_type_lit)] - #[::serdev::__private__::consume] + #[#serdev::__private__::consume] #target }; } @@ -84,11 +106,15 @@ pub(super) fn Deserialize(input: TokenStream) -> Result { None => { quote! { - #[derive(::serdev::__private__::serde::Deserialize)] - #[serde(crate = "::serdev::__private__::serde")] - #[::serdev::__private__::consume] + #[derive(#serdev::__private__::serde::Deserialize)] + #[serde(crate = #serde)] + #[#serdev::__private__::consume] #target } } }) } + +fn litstr(value: &str) -> LitStr { + LitStr::new(value, Span::call_site()) +} diff --git a/serdev_derive/src/internal/reexport.rs b/serdev_derive/src/internal/reexport.rs new file mode 100644 index 0000000..2e4ab93 --- /dev/null +++ b/serdev_derive/src/internal/reexport.rs @@ -0,0 +1,60 @@ +use proc_macro2::TokenStream; +use syn::{parse::Parse, punctuated::Punctuated, token, Attribute, Error, LitStr, MacroDelimiter, Meta, MetaList, Path}; + + +pub(crate) struct Reexport { + path: LitStr, +} + +impl Parse for Reexport { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let _path: token::Crate = input.parse()?; + + let _eq: token::Eq = input.parse()?; + + let path: LitStr = input.parse()?; + + Ok(Self { path }) + } +} + +impl Reexport { + pub(crate) fn take(attrs: &mut Vec) -> Result, Error> { + for attr in attrs { + if attr.path().get_ident().is_some_and(|i| i == "serdev") { + let directives = attr.parse_args_with( + Punctuated::::parse_terminated + )?; + for (i, directive) in directives.iter().enumerate() { + if directive.to_string().starts_with("crate") { + attr.meta = Meta::List(MetaList { + path: syn::parse_str("serdev")?, + delimiter: MacroDelimiter::Paren(Default::default()), + tokens: syn::parse_str(&{ + let mut others = String::new(); + for (j, directive) in directives.iter().enumerate() { + if j != i { + others.push_str(&directive.to_string()); + others.push(',') + } + }; others.pop(); + others + })? + }); + return syn::parse2(directive.clone()).map(Some) + } + } + } + }; Ok(None) + } +} + +impl Reexport { + pub(crate) fn path(&self) -> Result { + syn::parse_str(&self.path.value()) + } + + pub(crate) fn path_str(&self) -> String { + self.path.value() + } +} diff --git a/serdev_derive/src/internal/validate.rs b/serdev_derive/src/internal/validate.rs index 604acad..b492f88 100644 --- a/serdev_derive/src/internal/validate.rs +++ b/serdev_derive/src/internal/validate.rs @@ -76,8 +76,7 @@ impl Validate { others.push_str(&directive.to_string()); others.push(',') } - } - others.pop(); + }; others.pop(); others })? }); @@ -85,8 +84,7 @@ impl Validate { } } } - } - Ok(None) + }; Ok(None) } pub(crate) fn function(&self) -> Result { diff --git a/serdev_derive/src/lib.rs b/serdev_derive/src/lib.rs index 95db011..d6805cc 100644 --- a/serdev_derive/src/lib.rs +++ b/serdev_derive/src/lib.rs @@ -2,14 +2,14 @@ mod internal; -#[proc_macro_derive(Serialize, attributes(serde))] +#[proc_macro_derive(Serialize, attributes(serde, serdev))] pub fn Serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream { internal::Serialize(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } -#[proc_macro_derive(Deserialize, attributes(serde))] +#[proc_macro_derive(Deserialize, attributes(serde, serdev))] pub fn Deserialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream { internal::Deserialize(input.into()) .unwrap_or_else(syn::Error::into_compile_error)