Skip to content

Commit

Permalink
feat: support #[serdev(crate = "path::to::serdev")] (#21)
Browse files Browse the repository at this point in the history
* support `#[serdev(crate = "path::to::serdev")]`

* fix workflows
  • Loading branch information
kanarus authored Sep 13, 2024
1 parent 60e31db commit b71bf8b
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/auto_approve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/target
/Cargo.lock
**/target
**/Cargo.lock
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

<br>

- Just a wrapper of <a href="https://github.com/serde-rs/serde" target="_blank">Serde</a> and 100% compatible.
- Automatic validation in deserialization by `#[serde(validate = "...")]`, no boilerplate.
- Just a wrapper of <a href="https://github.com/serde-rs/serde" target="_blank">Serde</a> and 100% compatible
- Declarative validation in deserialization by `#[serde(validate = "...")]`

<div align="right">
<a href="https://github.com/ohkami-rs/serdev/blob/main/LICENSE" target="_blank">
Expand All @@ -25,7 +25,7 @@

```toml
[dependencies]
serdev = "0.1"
serdev = "0.2"
serde_json = "1.0"
```

Expand Down Expand Up @@ -81,7 +81,9 @@ Of course, you can use it in combination with some validation tools like <a href
This may be preferred when you need better performance _even in error cases_.\
For **no-std** use, this is the only way supported.

Both `"function"` and `"Type"` accept path like `"crate::utils::validate"`.
Both `"function"` and `"Type"` accept path like `"crate::util::validate"`.

Additionally, `#[serdev(crate = "path::to::serdev")]` is supported for reexport from another crate.


## License
Expand Down
1 change: 1 addition & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ tasks:
- cargo run --example readme
- cargo run --example validator
- cargo run --example various_users
- cd reexport && cargo run

##### check #####

Expand Down
2 changes: 0 additions & 2 deletions examples/.gitignore

This file was deleted.

7 changes: 6 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[workspace]
resolver = "2"
members = ["."]
exclude = ["reexport"]

[package]
name = "examples"
version = "0.1.0"
version = "0.0.0"
edition = "2021"

[dev-dependencies]
Expand Down
12 changes: 12 additions & 0 deletions examples/reexport/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[workspace]
resolver = "2"
members = ["reexporter", "."]

[package]
name = "user"
version = "0.0.0"
edition = "2021"

[dependencies]
reexporter = { path = "./reexporter" }
serde_json = { version = "1.0" }
7 changes: 7 additions & 0 deletions examples/reexport/reexporter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "reexporter"
version = "0.0.0"
edition = "2021"

[dependencies]
serdev = { path = "../../../serdev" }
3 changes: 3 additions & 0 deletions examples/reexport/reexporter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod private {
pub use ::serdev;
}
34 changes: 34 additions & 0 deletions examples/reexport/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use reexporter::private::serdev::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
#[serdev(crate = "reexporter::private::serdev")]
#[serde(validate = "Self::validate")]
struct Point {
x: i32,
y: i32,
}

impl Point {
fn validate(&self) -> 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::<Point>(r#"
{ "x" : 1, "y" : 2 }
"#).unwrap();

// Prints point = Point { x: 1, y: 2 }
println!("point = {point:?}");

let error = serde_json::from_str::<Point>(r#"
{ "x" : 10, "y" : 20 }
"#).unwrap_err();

// Prints error = x * y must not exceed 100
println!("error = {error}");
}
4 changes: 2 additions & 2 deletions serdev/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serdev"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = ["kanarus <kanarus786@gmail.com>"]
documentation = "https://docs.rs/serdev"
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion serdev_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serdev_derive"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = ["kanarus <kanarus786@gmail.com>"]
documentation = "https://docs.rs/serdev_derive"
Expand Down
64 changes: 45 additions & 19 deletions serdev_derive/src/internal.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream, Error> {
let mut target = syn::parse2::<Target>(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
})
}
Expand All @@ -27,6 +41,17 @@ pub(super) fn Deserialize(input: TokenStream) -> Result<TokenStream, Error> {
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()));
Expand All @@ -36,10 +61,7 @@ pub(super) fn Deserialize(input: TokenStream) -> Result<TokenStream, Error> {

let transmute_from_proxy = proxy.transmute_expr("proxy", target_ident);

let proxy_type_lit = LitStr::new(
&quote!(#proxy_ident #ty_generics).to_string(),
Span::call_site()
);
let proxy_type_lit = litstr(&quote!(#proxy_ident #ty_generics).to_string());

let validate_fn = validate.function()?;
let (error_ty, e_as_error_ty) = match validate.error()? {
Expand All @@ -48,15 +70,15 @@ pub(super) fn Deserialize(input: TokenStream) -> Result<TokenStream, Error> {
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

Expand All @@ -73,22 +95,26 @@ pub(super) fn Deserialize(input: TokenStream) -> Result<TokenStream, Error> {
}
}

#[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
};
}
}

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())
}
60 changes: 60 additions & 0 deletions serdev_derive/src/internal/reexport.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<Attribute>) -> Result<Option<Self>, Error> {
for attr in attrs {
if attr.path().get_ident().is_some_and(|i| i == "serdev") {
let directives = attr.parse_args_with(
Punctuated::<TokenStream, token::Comma>::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<Path, Error> {
syn::parse_str(&self.path.value())
}

pub(crate) fn path_str(&self) -> String {
self.path.value()
}
}
6 changes: 2 additions & 4 deletions serdev_derive/src/internal/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,15 @@ impl Validate {
others.push_str(&directive.to_string());
others.push(',')
}
}
others.pop();
}; others.pop();
others
})?
});
return syn::parse2(directive.clone()).map(Some)
}
}
}
}
Ok(None)
}; Ok(None)
}

pub(crate) fn function(&self) -> Result<Path, Error> {
Expand Down
4 changes: 2 additions & 2 deletions serdev_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit b71bf8b

Please sign in to comment.