├── .github
└── workflows
│ ├── CI.yml
│ └── auto_approve.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── Taskfile.yml
├── examples
├── Cargo.toml
├── examples
│ ├── readme.rs
│ ├── validator.rs
│ └── various_users.rs
└── reexport
│ ├── Cargo.toml
│ ├── reexporter
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
│ └── src
│ └── main.rs
├── serdev
├── Cargo.toml
├── benches
│ └── transfer.rs
└── src
│ └── lib.rs
└── serdev_derive
├── Cargo.toml
└── src
├── internal.rs
├── internal
├── reexport.rs
├── target.rs
└── validate.rs
└── lib.rs
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | CI:
10 | runs-on: ubuntu-latest
11 |
12 | strategy:
13 | matrix:
14 | toolchain: ['stable', 'nightly']
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - uses: actions-rs/toolchain@v1
20 | with:
21 | toolchain: ${{ matrix.toolchain }}
22 | profile: minimal
23 | override: true
24 |
25 | - name: Run tasks
26 | run: |
27 | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
28 | ${{ matrix.toolchain == 'nightly' && 'task CI' || 'task CI:nobench' }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/auto_approve.yml:
--------------------------------------------------------------------------------
1 | # This will be removed when serdev has more than one maintainers
2 |
3 | name: auto_approve
4 | on:
5 | pull_request:
6 | types:
7 | - opened
8 | - reopened
9 | - synchronize
10 | - ready_for_review
11 |
12 | jobs:
13 | auto_approve:
14 | if: |
15 | github.event.pull_request.user.login == 'kanarus' &&
16 | !github.event.pull_request.draft
17 | permissions:
18 | pull-requests: write
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: hmarr/auto-approve-action@v4
22 | with:
23 | github-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target
2 | **/Cargo.lock
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["serdev", "serdev_derive"]
4 | exclude = ["examples"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2025 kanarus
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
SerdeV
3 | SerdeV - Serde with Validation
4 |
5 |
6 |
7 |
8 | - Just a wrapper of Serde and 100% compatible
9 | - Declarative validation in deserialization by `#[serde(validate = "...")]`
10 |
11 |
22 |
23 |
24 | ## Example
25 |
26 | ```toml
27 | [dependencies]
28 | serdev = "0.2"
29 | serde_json = "1.0"
30 | ```
31 |
32 | ```rust
33 | use serdev::{Serialize, Deserialize};
34 |
35 | #[derive(Serialize, Deserialize, Debug)]
36 | #[serde(validate = "Self::validate")]
37 | struct Point {
38 | x: i32,
39 | y: i32,
40 | }
41 |
42 | impl Point {
43 | fn validate(&self) -> Result<(), impl std::fmt::Display> {
44 | if self.x * self.y > 100 {
45 | return Err("x * y must not exceed 100")
46 | }
47 | Ok(())
48 | }
49 | }
50 |
51 | fn main() {
52 | let point = serde_json::from_str::(r#"
53 | { "x" : 1, "y" : 2 }
54 | "#).unwrap();
55 |
56 | // Prints point = Point { x: 1, y: 2 }
57 | println!("point = {point:?}");
58 |
59 | let error = serde_json::from_str::(r#"
60 | { "x" : 10, "y" : 20 }
61 | "#).unwrap_err();
62 |
63 | // Prints error = x * y must not exceed 100
64 | println!("error = {error}");
65 | }
66 | ```
67 |
68 | Of course, you can use it in combination with some validation tools like validator! ( full example )
69 |
70 |
71 | ## Attribute
72 |
73 | - `#[serde(validate = "function")]`
74 |
75 | Automatically validate by the `function` in deserialization. The `function` must be callable as `fn(&self) -> Result<(), impl Display>`.\
76 | Errors are converted to a `String` internally and passed to `serde::de::Error::custom`.
77 |
78 | - `#[serde(validate(by = "function", error = "Type"))]`
79 |
80 | Using given `Type` for validation error without internal conversion. The `function` must explicitly return `Result<(), Type>`.\
81 | This may be preferred when you need better performance _even in error cases_.\
82 | For **no-std** use, this is the only way supported.
83 |
84 | Both `"function"` and `"Type"` accept path like `"crate::util::validate"`.
85 |
86 | Additionally, `#[serdev(crate = "path::to::serdev")]` is supported for reexport from another crate.
87 |
88 |
89 | ## License
90 |
91 | Licensed under MIT LICENSE ( [LICENSE](https://github.com/ohkami-rs/serdev/blob/main/LICENSE) or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) ).
92 |
--------------------------------------------------------------------------------
/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | tasks:
4 | CI:
5 | deps:
6 | - test
7 | - check
8 | - bench-dryrun
9 |
10 | CI:nobench:
11 | deps:
12 | - test
13 | - check
14 |
15 | test:
16 | deps:
17 | - test:doc
18 | - test:lib
19 | - test:examples
20 |
21 | check:
22 | deps:
23 | - check:lib
24 | - check:examples
25 |
26 | bench:
27 | deps:
28 | - bench:all
29 |
30 | bench-dryrun:
31 | deps:
32 | - bench:dryrun
33 |
34 | ##### test #####
35 |
36 | test:doc:
37 | cmds:
38 | - cargo test --doc --features DEBUG
39 |
40 | test:lib:
41 | cmds:
42 | - cargo test --lib --features DEBUG
43 |
44 | test:examples:
45 | dir: examples
46 | cmds:
47 | - cargo run --example readme
48 | - cargo run --example validator
49 | - cargo run --example various_users
50 | - cd reexport && cargo run
51 |
52 | ##### check #####
53 |
54 | check:lib:
55 | cmds:
56 | - cargo check
57 |
58 | check:examples:
59 | dir: examples
60 | cmds:
61 | - cargo check --examples
62 |
63 | ##### bench #####
64 |
65 | bench:all:
66 | cmds:
67 | - cargo bench
68 |
69 | bench:dryrun:
70 | cmds:
71 | - cargo bench --no-run
72 |
--------------------------------------------------------------------------------
/examples/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["."]
4 | exclude = ["reexport"]
5 |
6 | [package]
7 | name = "examples"
8 | version = "0.0.0"
9 | edition = "2021"
10 |
11 | [dev-dependencies]
12 | serdev = { path = "../serdev" }
13 | serde_json = { version = "1.0" }
14 | validator = { version = "0.16", features = ["derive"] }
--------------------------------------------------------------------------------
/examples/examples/readme.rs:
--------------------------------------------------------------------------------
1 | use serdev::{Serialize, Deserialize};
2 |
3 | #[derive(Serialize, Deserialize, Debug)]
4 | #[serde(validate = "Self::validate")]
5 | struct Point {
6 | x: i32,
7 | y: i32,
8 | }
9 |
10 | impl Point {
11 | fn validate(&self) -> Result<(), impl std::fmt::Display> {
12 | if self.x * self.y > 100 {
13 | return Err("x * y must not exceed 100")
14 | }
15 | Ok(())
16 | }
17 | }
18 |
19 | fn main() {
20 | let point = serde_json::from_str::(r#"
21 | { "x" : 1, "y" : 2 }
22 | "#).unwrap();
23 |
24 | // Prints point = Point { x: 1, y: 2 }
25 | println!("point = {point:?}");
26 |
27 | let error = serde_json::from_str::(r#"
28 | { "x" : 10, "y" : 20 }
29 | "#).unwrap_err();
30 |
31 | // Prints error = x * y must not exceed 100
32 | println!("error = {error}");
33 | }
34 |
--------------------------------------------------------------------------------
/examples/examples/validator.rs:
--------------------------------------------------------------------------------
1 | use serdev::Deserialize;
2 | use validator::{Validate, ValidationError};
3 |
4 | #[derive(Deserialize, Debug, PartialEq, Validate)]
5 | #[serde(validate = "Validate::validate")]
6 | struct SignupData {
7 | #[validate(email)]
8 | mail: String,
9 | #[validate(url)]
10 | site: String,
11 | #[validate(length(min = 1), custom(function = "validate_unique_username"))]
12 | #[serde(rename = "firstName")]
13 | first_name: String,
14 | #[validate(range(min = 18, max = 20))]
15 | age: u32,
16 | #[validate(range(min = 0.0, max = 100.0))]
17 | height: f32,
18 | }
19 |
20 | fn validate_unique_username(username: &str) -> Result<(), ValidationError> {
21 | if username == "xXxShad0wxXx" {
22 | // the value of the username will automatically be added later
23 | return Err(ValidationError::new("terrible_username"));
24 | }
25 |
26 | Ok(())
27 | }
28 |
29 | fn main() {
30 | let signupdata = serde_json::from_str::(r#"
31 | {
32 | "mail": "serdev@ohkami.rs",
33 | "site": "https://ohkami.rs",
34 | "firstName": "serdev",
35 | "age": 20,
36 | "height": 0.0
37 | }
38 | "#).unwrap();
39 | assert_eq!(signupdata, SignupData {
40 | mail: String::from("serdev@ohkami.rs"),
41 | site: String::from("https://ohkami.rs"),
42 | first_name: String::from("serdev"),
43 | age: 20,
44 | height: 0.0
45 | });
46 |
47 | let error = serde_json::from_str::(r#"
48 | {
49 | "mail": "serdev@ohkami.rs",
50 | "site": "https://ohkami.rs",
51 | "firstName": "serdev",
52 | "age": 0,
53 | "height": 0.0
54 | }
55 | "#).unwrap_err();
56 | println!("error: {error}");
57 | }
58 |
--------------------------------------------------------------------------------
/examples/examples/various_users.rs:
--------------------------------------------------------------------------------
1 | use serdev::{Serialize, Deserialize};
2 |
3 |
4 | #[derive(Debug, PartialEq, Serialize, Deserialize)]
5 | struct User {
6 | name: String,
7 | age: usize,
8 | }
9 |
10 | #[derive(Debug, PartialEq, Deserialize)]
11 | #[serde(validate = "Self::validate")]
12 | struct VUser {
13 | name: String,
14 | age: usize,
15 | }
16 | impl VUser {
17 | fn validate(&self) -> Result<(), impl std::fmt::Display> {
18 | if self.name.is_empty() {
19 | return Err("`name` must not be empty")
20 | }
21 | Ok(())
22 | }
23 | }
24 |
25 | #[derive(Debug, PartialEq, Deserialize)]
26 | #[serde(validate(by = "Self::validate", error = "&'static str"))]
27 | struct EUser {
28 | name: String,
29 | age: usize,
30 | }
31 | impl EUser {
32 | fn validate(&self) -> Result<(), &'static str> {
33 | if self.name.is_empty() {
34 | return Err("`name` must not be empty")
35 | }
36 | Ok(())
37 | }
38 | }
39 |
40 | #[derive(Debug, PartialEq, Deserialize)]
41 | #[serde(validate = "Self::validate")]
42 | struct GUser<'n, Name: From+ToString, Age: From> {
43 | name: Name,
44 | age: Age,
45 | nickname: Option<&'n str>
46 | }
47 | impl<'n, Name: From+ToString, Age: From> GUser<'n, Name, Age> {
48 | fn validate(&self) -> Result<(), impl std::fmt::Display> {
49 | if self.name.to_string().is_empty() {
50 | return Err("`name` must not be empty")
51 | }
52 | Ok(())
53 | }
54 | }
55 |
56 | fn main() {
57 | assert_eq!(
58 | serde_json::to_string(&User {
59 | name: String::from("serdev"),
60 | age: 0
61 | }).unwrap(),
62 | r#"{"name":"serdev","age":0}"#
63 | );
64 | assert_eq!(
65 | serde_json::from_str::(
66 | r#"{"age":4,"name":"ohkami"}"#
67 | ).unwrap(),
68 | User {
69 | name: String::from("ohkami"),
70 | age: 4
71 | }
72 | );
73 |
74 | assert_eq!(
75 | serde_json::from_str::(
76 | r#"{"age":4,"name":"ohkami"}"#
77 | ).unwrap(),
78 | VUser {
79 | name: String::from("ohkami"),
80 | age: 4
81 | }
82 | );
83 | assert_eq!(
84 | serde_json::from_str::(
85 | r#"{"age":4,"name":""}"#
86 | ).unwrap_err().to_string(),
87 | "`name` must not be empty"
88 | );
89 |
90 | assert_eq!(
91 | serde_json::from_str::(
92 | r#"{"age":4,"name":"ohkami"}"#
93 | ).unwrap(),
94 | EUser {
95 | name: String::from("ohkami"),
96 | age: 4
97 | }
98 | );
99 | assert_eq!(
100 | serde_json::from_str::(
101 | r#"{"age":4,"name":""}"#
102 | ).unwrap_err().to_string(),
103 | "`name` must not be empty"
104 | );
105 |
106 | assert_eq!(
107 | serde_json::from_str::>(
108 | r#"{"age":4,"name":"ohkami"}"#
109 | ).unwrap(),
110 | GUser {
111 | name: String::from("ohkami"),
112 | age: 4,
113 | nickname: None
114 | }
115 | );
116 | assert_eq!(
117 | serde_json::from_str::>(
118 | r#"{"age":4,"nickname":"wolf","name":"ohkami"}"#
119 | ).unwrap(),
120 | GUser {
121 | name: String::from("ohkami"),
122 | age: 4,
123 | nickname: Some("wolf")
124 | }
125 | );
126 | assert_eq!(
127 | serde_json::from_str::>(
128 | r#"{"age":4,"nickname":"wolf","name":""}"#
129 | ).unwrap_err().to_string(),
130 | "`name` must not be empty"
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/examples/reexport/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["reexporter", "."]
4 |
5 | [package]
6 | name = "user"
7 | version = "0.0.0"
8 | edition = "2021"
9 |
10 | [dependencies]
11 | reexporter = { path = "./reexporter" }
12 | serde_json = { version = "1.0" }
--------------------------------------------------------------------------------
/examples/reexport/reexporter/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "reexporter"
3 | version = "0.0.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | serdev = { path = "../../../serdev" }
--------------------------------------------------------------------------------
/examples/reexport/reexporter/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod private {
2 | pub use ::serdev;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/reexport/src/main.rs:
--------------------------------------------------------------------------------
1 | use reexporter::private::serdev::{Serialize, Deserialize};
2 |
3 | #[derive(Serialize, Deserialize, Debug)]
4 | #[serdev(crate = "reexporter::private::serdev")]
5 | #[serde(validate = "Self::validate")]
6 | struct Point {
7 | x: i32,
8 | y: i32,
9 | }
10 |
11 | impl Point {
12 | fn validate(&self) -> Result<(), impl std::fmt::Display> {
13 | if self.x * self.y > 100 {
14 | return Err("x * y must not exceed 100")
15 | }
16 | Ok(())
17 | }
18 | }
19 |
20 | fn main() {
21 | let point = serde_json::from_str::(r#"
22 | { "x" : 1, "y" : 2 }
23 | "#).unwrap();
24 |
25 | // Prints point = Point { x: 1, y: 2 }
26 | println!("point = {point:?}");
27 |
28 | let error = serde_json::from_str::(r#"
29 | { "x" : 10, "y" : 20 }
30 | "#).unwrap_err();
31 |
32 | // Prints error = x * y must not exceed 100
33 | println!("error = {error}");
34 | }
35 |
--------------------------------------------------------------------------------
/serdev/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "serdev"
3 | version = "0.2.0"
4 | edition = "2021"
5 | authors = ["kanarus "]
6 | documentation = "https://docs.rs/serdev"
7 | homepage = "https://crates.io/crates/serdev"
8 | repository = "https://github.com/ohkami-rs/serdev"
9 | readme = "../README.md"
10 | license = "MIT"
11 | description = "SerdeV - Serde with Validation"
12 | keywords = ["serde", "validation", "serialization"]
13 | categories = ["encoding", "rust-patterns", "no-std", "no-std::no-alloc"]
14 |
15 | [dependencies]
16 | serdev_derive = { version = "=0.2.0", path = "../serdev_derive" }
17 | serde = { version = "1", features = ["derive"] }
18 |
19 | [dev-dependencies]
20 | serde_json = "1.0" # for README doc test
21 | rand = "0.8" # for bench
22 |
23 | [features]
24 | nightly = []
25 | DEBUG = []
26 |
27 | ### DEBUG ###
28 | #default = ["DEBUG"]
--------------------------------------------------------------------------------
/serdev/benches/transfer.rs:
--------------------------------------------------------------------------------
1 | #![feature(test)]
2 |
3 | extern crate test;
4 |
5 | use std::sync::LazyLock;
6 | use test::bench::Bencher;
7 |
8 | #[allow(unused)]
9 | struct S {
10 | a: String,
11 | b: usize,
12 | c: Vec,
13 | }
14 |
15 | #[derive(Clone)]
16 | struct SP {
17 | a: String,
18 | b: usize,
19 | c: Vec,
20 | }
21 |
22 | #[allow(unused)]
23 | #[derive(Clone)]
24 | struct T {
25 | d: usize,
26 | e: String,
27 | }
28 |
29 | static CASES: LazyLock<[SP; 100]> = LazyLock::new(|| {
30 | use rand::{thread_rng, Rng};
31 |
32 | fn random_string() -> String {
33 | use rand::distributions::{DistString, Alphanumeric};
34 | let len = thread_rng().gen_range(0..100);
35 | Alphanumeric.sample_string(&mut thread_rng(), len)
36 | }
37 |
38 | fn random_uint() -> usize {
39 | thread_rng().gen::()
40 | }
41 |
42 | (0..100).map(|_| SP {
43 | a: random_string(),
44 | b: random_uint(),
45 | c: (0..100).map(|_| T {
46 | d: random_uint(),
47 | e: random_string()
48 | }).collect()
49 | }).collect::>().try_into().ok().unwrap()
50 | });
51 |
52 | #[bench]
53 | fn transfer_by_hand(b: &mut Bencher) {
54 | test::black_box(&*CASES);
55 | b.iter(|| -> [S; 100] {
56 | CASES.clone().map(|sp| test::black_box(
57 | S { a: sp.a, b: sp.b, c: sp.c }
58 | ))
59 | })
60 | }
61 |
62 | #[bench]
63 | fn transfer_by_mem_transmute(b: &mut Bencher) {
64 | test::black_box(&*CASES);
65 | b.iter(|| -> [S; 100] {
66 | CASES.clone().map(|sp| test::black_box(
67 | unsafe {std::mem::transmute(sp)}
68 | ))
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/serdev/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(feature="DEBUG", doc = include_str!("../../README.md"))]
2 |
3 | pub use serdev_derive::{Serialize, Deserialize};
4 | pub use ::serde::ser::{self, Serialize, Serializer};
5 | pub use ::serde::de::{self, Deserialize, Deserializer};
6 |
7 | #[doc(hidden)]
8 | pub mod __private__ {
9 | pub use serdev_derive::consume;
10 | pub use ::serde;
11 | pub type DefaultError = ::std::string::String;
12 | pub fn default_error(e: impl std::fmt::Display) -> DefaultError {e.to_string()}
13 | }
14 |
--------------------------------------------------------------------------------
/serdev_derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "serdev_derive"
3 | version = "0.2.0"
4 | edition = "2021"
5 | authors = ["kanarus "]
6 | documentation = "https://docs.rs/serdev_derive"
7 | homepage = "https://crates.io/crates/serdev_derive"
8 | repository = "https://github.com/ohkami-rs/serdev_derive"
9 | readme = "../README.md"
10 | license = "MIT"
11 | description = "SerdeV - Serde with Validation"
12 | keywords = ["serde", "validation", "serialization"]
13 | categories = ["encoding", "rust-patterns", "no-std", "no-std::no-alloc"]
14 |
15 | [lib]
16 | proc-macro = true
17 |
18 | [dependencies]
19 | proc-macro2 = { version = "1.0" }
20 | quote = { version = "1.0" }
21 | syn = { version = "2.0", features = ["full"] }
--------------------------------------------------------------------------------
/serdev_derive/src/internal.rs:
--------------------------------------------------------------------------------
1 | mod target;
2 | mod validate;
3 | mod reexport;
4 |
5 | use self::target::Target;
6 | use self::validate::Validate;
7 | use self::reexport::Reexport;
8 |
9 | use proc_macro2::{Span, TokenStream};
10 | use quote::{format_ident, quote, ToTokens};
11 | use syn::{Error, LitStr};
12 |
13 |
14 | pub(super) fn Serialize(input: TokenStream) -> Result {
15 | let mut target = syn::parse2::(input.clone())?;
16 |
17 | let _ = Validate::take(target.attrs_mut())?;
18 |
19 | let (serdev, serde) = match Reexport::take(target.attrs_mut())? {
20 | None => (
21 | quote! {::serdev},
22 | litstr("::serdev::__private__::serde")
23 | ),
24 | Some(r) => (
25 | r.path()?.into_token_stream(),
26 | litstr(&format!("{}::__private__::serde", r.path_str()))
27 | )
28 | };
29 |
30 | Ok(quote! {
31 | #[derive(#serdev::__private__::serde::Serialize)]
32 | #[serde(crate = #serde)]
33 | #[#serdev::__private__::consume]
34 | #target
35 | })
36 | }
37 |
38 | pub(super) fn Deserialize(input: TokenStream) -> Result {
39 | let mut target = syn::parse2::(input.clone())?;
40 |
41 | let generics = target.generics().clone();
42 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
43 |
44 | let (serdev, serde) = match Reexport::take(target.attrs_mut())? {
45 | None => (
46 | quote! {::serdev},
47 | litstr("::serdev::__private__::serde")
48 | ),
49 | Some(r) => (
50 | r.path()?.into_token_stream(),
51 | litstr(&format!("{}::__private__::serde", r.path_str()))
52 | )
53 | };
54 |
55 | Ok(match Validate::take(target.attrs_mut())? {
56 | Some(validate) => {
57 | let proxy = target.create_proxy(format_ident!("serdev_proxy_{}", target.ident()));
58 |
59 | let target_ident = target.ident();
60 | let proxy_ident = proxy.ident();
61 |
62 | let transmute_from_proxy = proxy.transmute_expr("proxy", target_ident);
63 |
64 | let proxy_type_lit = litstr("e!(#proxy_ident #ty_generics).to_string());
65 |
66 | let validate_fn = validate.function()?;
67 | let (error_ty, e_as_error_ty) = match validate.error()? {
68 | Some(ty) => (
69 | quote! {#ty},
70 | quote! {e}
71 | ),
72 | None => (
73 | quote! {#serdev::__private__::DefaultError},
74 | quote! {#serdev::__private__::default_error(e)}
75 | )
76 | };
77 |
78 | quote! {
79 | const _: () = {
80 | #[derive(#serdev::__private__::serde::Deserialize)]
81 | #[serde(crate = #serde)]
82 | #[allow(non_camel_case_types)]
83 | #proxy
84 |
85 | impl #impl_generics ::core::convert::TryFrom<#proxy_ident #ty_generics> for #target_ident #ty_generics
86 | #where_clause
87 | {
88 | type Error = #error_ty;
89 |
90 | #[inline]
91 | fn try_from(proxy: #proxy_ident #ty_generics) -> ::core::result::Result {
92 | let this = #transmute_from_proxy;
93 | let _: () = #validate_fn(&this).map_err(|e| #e_as_error_ty)?;
94 | Ok(this)
95 | }
96 | }
97 |
98 | #[derive(#serdev::__private__::serde::Deserialize)]
99 | #[serde(crate = #serde)]
100 | #[serde(try_from = #proxy_type_lit)]
101 | #[#serdev::__private__::consume]
102 | #target
103 | };
104 | }
105 | }
106 |
107 | None => {
108 | quote! {
109 | #[derive(#serdev::__private__::serde::Deserialize)]
110 | #[serde(crate = #serde)]
111 | #[#serdev::__private__::consume]
112 | #target
113 | }
114 | }
115 | })
116 | }
117 |
118 | fn litstr(value: &str) -> LitStr {
119 | LitStr::new(value, Span::call_site())
120 | }
121 |
--------------------------------------------------------------------------------
/serdev_derive/src/internal/reexport.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::TokenStream;
2 | use syn::{parse::Parse, punctuated::Punctuated, token, Attribute, Error, LitStr, MacroDelimiter, Meta, MetaList, Path};
3 |
4 |
5 | pub(crate) struct Reexport {
6 | path: LitStr,
7 | }
8 |
9 | impl Parse for Reexport {
10 | fn parse(input: syn::parse::ParseStream) -> syn::Result {
11 | let _path: token::Crate = input.parse()?;
12 |
13 | let _eq: token::Eq = input.parse()?;
14 |
15 | let path: LitStr = input.parse()?;
16 |
17 | Ok(Self { path })
18 | }
19 | }
20 |
21 | impl Reexport {
22 | pub(crate) fn take(attrs: &mut Vec) -> Result