Skip to content

Commit afb392a

Browse files
feat: add serde support (#13)
1 parent 4ab10ec commit afb392a

File tree

15 files changed

+1072
-162
lines changed

15 files changed

+1072
-162
lines changed

Cargo.lock

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ version = "0.0.1"
1313
fortifier = { path = "./packages/fortifier", version = "0.0.1" }
1414
fortifier-macros = { path = "./packages/fortifier-macros", version = "0.0.1" }
1515
regex = "1.12.2"
16+
serde = "1.0.228"
17+
serde_json = "1.0.145"
1618
tokio = "1.48.0"
1719

1820
[workspace.lints.rust]

example/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repository.workspace = true
99
version.workspace = true
1010

1111
[dependencies]
12-
fortifier.workspace = true
12+
fortifier = { workspace = true, features = ["email", "regex", "url"] }
1313
regex.workspace = true
1414
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
1515

packages/fortifier-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ quote = "1.0.42"
1818
syn = "2.0.110"
1919

2020
[dev-dependencies]
21-
fortifier.workspace = true
21+
fortifier = { workspace = true, features = ["email"] }
2222
trybuild = "1.0.114"
2323

2424
[lints]

packages/fortifier-macros/src/validations/email.rs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
11
use proc_macro2::TokenStream;
22
use quote::{format_ident, quote};
3-
use syn::{Ident, Result, meta::ParseNestedMeta};
3+
use syn::{Ident, LitBool, LitInt, Result, meta::ParseNestedMeta};
44

55
use crate::validation::Validation;
66

7-
#[derive(Default)]
8-
pub struct Email {}
7+
pub struct Email {
8+
allow_display_text: bool,
9+
allow_domain_literal: bool,
10+
minimum_sub_domains: usize,
11+
}
12+
13+
impl Default for Email {
14+
fn default() -> Self {
15+
Self {
16+
allow_display_text: false,
17+
allow_domain_literal: true,
18+
minimum_sub_domains: 0,
19+
}
20+
}
21+
}
922

1023
impl Validation for Email {
11-
fn parse(_meta: &ParseNestedMeta<'_>) -> Result<Self> {
12-
Ok(Email::default())
24+
fn parse(meta: &ParseNestedMeta<'_>) -> Result<Self> {
25+
let mut result = Email::default();
26+
27+
if !meta.input.is_empty() {
28+
meta.parse_nested_meta(|meta| {
29+
if meta.path.is_ident("allow_display_text") {
30+
let lit: LitBool = meta.value()?.parse()?;
31+
result.allow_display_text = lit.value;
32+
33+
Ok(())
34+
} else if meta.path.is_ident("allow_domain_literal") {
35+
let lit: LitBool = meta.value()?.parse()?;
36+
result.allow_domain_literal = lit.value;
37+
38+
Ok(())
39+
} else if meta.path.is_ident("minimum_sub_domains") {
40+
let lit: LitInt = meta.value()?.parse()?;
41+
result.minimum_sub_domains = lit.base10_parse()?;
42+
43+
Ok(())
44+
} else {
45+
Err(meta.error("unknown parameter"))
46+
}
47+
})?;
48+
}
49+
50+
Ok(result)
1351
}
1452

1553
fn is_async(&self) -> bool {
@@ -25,8 +63,20 @@ impl Validation for Email {
2563
}
2664

2765
fn tokens(&self, expr: &TokenStream) -> TokenStream {
66+
let allow_display_text = self.allow_display_text;
67+
let allow_domain_literal = self.allow_domain_literal;
68+
let minimum_sub_domains = self.minimum_sub_domains;
69+
2870
quote! {
29-
#expr.validate_email()
71+
{
72+
const EMAIL_ADDRESS_OPTIONS: EmailOptions = EmailOptions {
73+
allow_display_text: #allow_display_text,
74+
allow_domain_literal: #allow_domain_literal,
75+
minimum_sub_domains: #minimum_sub_domains,
76+
};
77+
78+
#expr.validate_email(EMAIL_ADDRESS_OPTIONS)
79+
}
3080
}
3181
}
3282
}

packages/fortifier-macros/src/validations/length.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::validation::Validation;
66

77
#[derive(Default)]
88
pub struct Length {
9-
pub equal: Option<Expr>,
10-
pub min: Option<Expr>,
11-
pub max: Option<Expr>,
9+
equal: Option<Expr>,
10+
min: Option<Expr>,
11+
max: Option<Expr>,
1212
}
1313

1414
impl Validation for Length {

packages/fortifier-macros/src/validations/regex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use syn::{Expr, Ident, Result, meta::ParseNestedMeta};
55
use crate::validation::Validation;
66

77
pub struct Regex {
8-
pub expression: Expr,
8+
expression: Expr,
99
}
1010

1111
impl Validation for Regex {

packages/fortifier/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,26 @@ repository.workspace = true
99
version.workspace = true
1010

1111
[features]
12-
default = ["macros", "regex", "url"]
12+
default = ["macros"]
13+
email = ["dep:email_address"]
1314
indexmap = ["dep:indexmap"]
1415
macros = ["dep:fortifier-macros"]
16+
message = []
1517
regex = ["dep:regex"]
18+
serde = ["dep:serde", "email_address/serde_support"]
1619
url = ["dep:url"]
1720

1821
[dependencies]
22+
email_address = { version = "0.2.9", default-features = false, optional = true }
1923
fortifier-macros = { workspace = true, optional = true }
2024
indexmap = { version = "2.12.0", optional = true }
2125
regex = { workspace = true, optional = true }
26+
serde = { workspace = true, features = ["derive"], optional = true }
2227
url = { version = "2.5.7", optional = true }
2328

29+
[dev-dependencies]
30+
pretty_assertions = "1.4.1"
31+
serde_json.workspace = true
32+
2433
[lints]
2534
workspace = true

packages/fortifier/src/validate.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
use std::{
22
error::Error,
3-
fmt::{self, Display},
3+
fmt::{self, Debug, Display},
44
pin::Pin,
55
};
66

77
/// Validation errors.
8-
#[derive(Debug)]
9-
pub struct ValidationErrors<E: Error>(Vec<E>);
8+
#[derive(Debug, Eq, PartialEq)]
9+
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
10+
pub struct ValidationErrors<E>(Vec<E>);
1011

11-
impl<E: Error> Display for ValidationErrors<E> {
12+
impl<E: Debug> Display for ValidationErrors<E> {
1213
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1314
write!(f, "{:?}", self.0)
1415
}
1516
}
1617

1718
impl<E: Error> Error for ValidationErrors<E> {}
1819

19-
impl<E: Error> From<Vec<E>> for ValidationErrors<E> {
20+
impl<E> FromIterator<E> for ValidationErrors<E> {
21+
fn from_iter<T: IntoIterator<Item = E>>(iter: T) -> Self {
22+
Self(Vec::from_iter(iter))
23+
}
24+
}
25+
26+
impl<E> From<Vec<E>> for ValidationErrors<E> {
2027
fn from(value: Vec<E>) -> Self {
2128
Self(value)
2229
}

packages/fortifier/src/validations.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
#[cfg(feature = "email")]
12
mod email;
23
mod length;
34
#[cfg(feature = "regex")]
45
mod regex;
56
#[cfg(feature = "url")]
67
mod url;
78

9+
#[cfg(feature = "email")]
810
pub use email::*;
911
pub use length::*;
1012
#[cfg(feature = "regex")]

0 commit comments

Comments
 (0)