diff --git a/Cargo.lock b/Cargo.lock index e883c78..18489ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,251 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fortifier" version = "0.0.1" +dependencies = [ + "fortifier-macros", +] + +[[package]] +name = "fortifier-macros" +version = "0.0.1" +dependencies = [ + "fortifier", + "proc-macro2", + "quote", + "syn", + "trybuild", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "trybuild" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" diff --git a/Cargo.toml b/Cargo.toml index ed96ca4..1c70cfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ version = "0.0.1" [workspace.dependencies] fortifier = { path = "./packages/fortifier", version = "0.0.1" } +fortifier-macros = { path = "./packages/fortifier-macros", version = "0.0.1" } diff --git a/deny.toml b/deny.toml index 522d8d0..13550b0 100644 --- a/deny.toml +++ b/deny.toml @@ -10,7 +10,7 @@ multiple-versions = "allow" wildcards = "deny" [licenses] -allow = ["MIT"] +allow = ["MIT", "Unicode-3.0"] confidence-threshold = 1.0 [sources] diff --git a/packages/fortifier-macros/Cargo.toml b/packages/fortifier-macros/Cargo.toml new file mode 100644 index 0000000..00b0eb8 --- /dev/null +++ b/packages/fortifier-macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fortifier-macros" +description = "Macros for Fortifier." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.103" +quote = "1.0.42" +syn = "2.0.110" + +[dev-dependencies] +fortifier.workspace = true +trybuild = "1.0.114" diff --git a/packages/fortifier-macros/README.md b/packages/fortifier-macros/README.md new file mode 100644 index 0000000..06d3961 --- /dev/null +++ b/packages/fortifier-macros/README.md @@ -0,0 +1,13 @@ +

Fortifier Macros

+ +Macros for Fortifier. + +## Documentation + +See [the Fortifier book](https://fortifier.rustforweb.org/) for documentation. + +## Rust for Web + +The Fortifier project is part of [Rust for Web](https://github.com/RustForWeb). + +[Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. diff --git a/packages/fortifier-macros/src/derive.rs b/packages/fortifier-macros/src/derive.rs new file mode 100644 index 0000000..645afdb --- /dev/null +++ b/packages/fortifier-macros/src/derive.rs @@ -0,0 +1,47 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{Data, DeriveInput, Fields}; + +pub fn validate_tokens(input: DeriveInput) -> TokenStream { + match input.data { + Data::Struct(data_struct) => match data_struct.fields { + Fields::Named(_fields_named) => { + // TODO + } + Fields::Unnamed(_fields_unnamed) => todo!("fields unamed"), + Fields::Unit => todo!("fields unit"), + }, + Data::Enum(_data_enum) => todo!("data enum"), + Data::Union(_data_union) => todo!("data union"), + } + + let ident = input.ident; + let error_ident = format_ident!("{ident}ValidationError"); + + quote! { + #[derive(Debug)] + struct #error_ident {} + + impl ::std::fmt::Display for #error_ident { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "") + } + } + + impl ::std::error::Error for #error_ident {} + + impl Validate for #ident { + type Error = #error_ident; + + fn validate_sync(&self) -> Result<(), Self::Error> { + Ok(()) + } + + fn validate_async(&self) -> ::std::pin::Pin>>> { + Box::pin(async { + Ok(()) + }) + } + } + } +} diff --git a/packages/fortifier-macros/src/lib.rs b/packages/fortifier-macros/src/lib.rs new file mode 100644 index 0000000..c80b31a --- /dev/null +++ b/packages/fortifier-macros/src/lib.rs @@ -0,0 +1,13 @@ +mod derive; + +use proc_macro::TokenStream; +use syn::{DeriveInput, parse_macro_input}; + +use crate::derive::validate_tokens; + +#[proc_macro_derive(Validate, attributes(validate))] +pub fn derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + validate_tokens(input).into() +} diff --git a/packages/fortifier-macros/tests/derive.rs b/packages/fortifier-macros/tests/derive.rs new file mode 100644 index 0000000..0838ef5 --- /dev/null +++ b/packages/fortifier-macros/tests/derive.rs @@ -0,0 +1,8 @@ +use trybuild::TestCases; + +#[test] +fn derive() { + let t = TestCases::new(); + t.pass("tests/derive/*_pass.rs"); + t.compile_fail("tests/derive/*_fail.rs"); +} diff --git a/packages/fortifier-macros/tests/derive/basic_pass.rs b/packages/fortifier-macros/tests/derive/basic_pass.rs new file mode 100644 index 0000000..abe2f6d --- /dev/null +++ b/packages/fortifier-macros/tests/derive/basic_pass.rs @@ -0,0 +1,23 @@ +use std::error::Error; + +use fortifier::Validate; + +#[derive(Validate)] +struct CreateUser { + #[validate(email)] + email: String, + + #[validate(length(min = 1, max = 256))] + name: String, +} + +fn main() -> Result<(), Box> { + let data = CreateUser { + email: "john@doe.com".to_owned(), + name: "John Doe".to_owned(), + }; + + data.validate_sync()?; + + Ok(()) +} diff --git a/packages/fortifier/Cargo.toml b/packages/fortifier/Cargo.toml index bbbc159..7ad814c 100644 --- a/packages/fortifier/Cargo.toml +++ b/packages/fortifier/Cargo.toml @@ -7,3 +7,10 @@ edition.workspace = true license.workspace = true repository.workspace = true version.workspace = true + +[features] +default = ["macros"] +macros = ["dep:fortifier-macros"] + +[dependencies] +fortifier-macros = { workspace = true, optional = true } diff --git a/packages/fortifier/src/lib.rs b/packages/fortifier/src/lib.rs index 8b13789..f1b88ef 100644 --- a/packages/fortifier/src/lib.rs +++ b/packages/fortifier/src/lib.rs @@ -1 +1,6 @@ +mod validate; +pub use validate::*; + +#[cfg(feature = "macros")] +pub use fortifier_macros::*; diff --git a/packages/fortifier/src/validate.rs b/packages/fortifier/src/validate.rs new file mode 100644 index 0000000..c6e528b --- /dev/null +++ b/packages/fortifier/src/validate.rs @@ -0,0 +1,19 @@ +use std::{error::Error, pin::Pin}; + +pub trait Validate { + type Error: Error; + + fn validate(&self) -> Pin> + Send>> + where + Self: Sync, + { + Box::pin(async { + self.validate_sync()?; + self.validate_async().await + }) + } + + fn validate_sync(&self) -> Result<(), Self::Error>; + + fn validate_async(&self) -> Pin> + Send>>; +}