diff --git a/Cargo.lock b/Cargo.lock index c0976e5..e3ad79d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,82 @@ dependencies = [ "memchr", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "convert_case" version = "0.9.0" @@ -73,10 +149,11 @@ dependencies = [ "serde", "serde_json", "url", + "utoipa", ] [[package]] -name = "fortifier-example" +name = "fortifier-example-basic" version = "0.0.1" dependencies = [ "fortifier", @@ -84,18 +161,79 @@ dependencies = [ "tokio", ] +[[package]] +name = "fortifier-example-server" +version = "0.0.1" +dependencies = [ + "axum", + "fortifier", + "serde", + "thiserror", + "tokio", + "utoipa", + "utoipa-axum", + "utoipa-scalar", + "uuid", +] + [[package]] name = "fortifier-macros" version = "0.0.1" dependencies = [ "convert_case", "fortifier", + "proc-macro-crate", "proc-macro2", "quote", "syn", "trybuild", ] +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.3" @@ -108,6 +246,88 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -218,6 +438,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -226,18 +448,75 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + [[package]] name = "litemap" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -250,6 +529,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "potential_utf" version = "0.1.4" @@ -269,6 +554,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -287,6 +581,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "regex" version = "1.12.2" @@ -316,6 +616,12 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -365,6 +671,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "1.0.3" @@ -374,12 +691,34 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -397,6 +736,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.13.2" @@ -423,6 +768,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -439,8 +804,12 @@ version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ + "libc", + "mio", "pin-project-lite", + "socket2", "tokio-macros", + "windows-sys 0.61.2", ] [[package]] @@ -478,6 +847,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + [[package]] name = "toml_parser" version = "1.0.4" @@ -493,6 +874,54 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", +] + [[package]] name = "trybuild" version = "1.0.114" @@ -538,13 +967,135 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", + "uuid", +] + +[[package]] +name = "utoipa-scalar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719" +dependencies = [ + "axum", + "serde", + "serde_json", + "utoipa", +] + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -553,6 +1104,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -562,11 +1122,85 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" diff --git a/Cargo.toml b/Cargo.toml index a2a1e60..f817d0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["example", "packages/*"] +members = ["examples/*", "packages/*"] resolver = "2" [workspace.package] @@ -16,6 +16,7 @@ regex = "1.12.2" serde = "1.0.228" serde_json = "1.0.145" tokio = "1.48.0" +utoipa = "5.4.0" [workspace.lints.rust] unsafe_code = "deny" diff --git a/deny.toml b/deny.toml index 13550b0..5b4584e 100644 --- a/deny.toml +++ b/deny.toml @@ -2,7 +2,9 @@ all-features = true [advisories] -ignore = [] +ignore = [ + { id = "RUSTSEC-2024-0436", reason = "No maintained version available for `paste`." }, +] [bans] allow-wildcard-paths = true @@ -10,7 +12,7 @@ multiple-versions = "allow" wildcards = "deny" [licenses] -allow = ["MIT", "Unicode-3.0"] +allow = ["Apache-2.0", "BSD-3-Clause", "MIT", "Unicode-3.0"] confidence-threshold = 1.0 [sources] diff --git a/example/Cargo.toml b/examples/basic/Cargo.toml similarity index 82% rename from example/Cargo.toml rename to examples/basic/Cargo.toml index f513946..9678b55 100644 --- a/example/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "fortifier-example" -description = "Fortifier example." +name = "fortifier-example-basic" +description = "Fortifier basic example." authors.workspace = true edition.workspace = true diff --git a/example/src/email_address.rs b/examples/basic/src/email_address.rs similarity index 100% rename from example/src/email_address.rs rename to examples/basic/src/email_address.rs diff --git a/example/src/main.rs b/examples/basic/src/main.rs similarity index 100% rename from example/src/main.rs rename to examples/basic/src/main.rs diff --git a/example/src/user.rs b/examples/basic/src/user.rs similarity index 100% rename from example/src/user.rs rename to examples/basic/src/user.rs diff --git a/examples/server/Cargo.toml b/examples/server/Cargo.toml new file mode 100644 index 0000000..d54ff8d --- /dev/null +++ b/examples/server/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "fortifier-example-server" +description = "Fortifier server example." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +axum = "0.8.7" +fortifier = { workspace = true, features = [ + "email", + "regex", + "serde", + "url", + "utoipa", +] } +serde = { workspace = true, features = ["derive"] } +thiserror = "2.0.17" +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +utoipa = { workspace = true, features = ["axum_extras", "uuid"] } +utoipa-axum = "0.2.0" +utoipa-scalar = { version = "0.3.0", features = ["axum"] } +uuid = { version = "1.19.0", features = ["serde", "v7"] } + +[lints] +workspace = true diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs new file mode 100644 index 0000000..cd3f4b3 --- /dev/null +++ b/examples/server/src/main.rs @@ -0,0 +1,24 @@ +mod routes; +mod user; + +use std::{ + error::Error, + net::{IpAddr, Ipv4Addr, SocketAddr}, +}; + +use tokio::net::TcpListener; + +use crate::routes::Routes; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let router = Routes::router(); + + let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let listener = TcpListener::bind(&address).await?; + + println!("listening on http://{}", &address); + axum::serve(listener, router).await?; + + Ok(()) +} diff --git a/examples/server/src/routes.rs b/examples/server/src/routes.rs new file mode 100644 index 0000000..27bdc35 --- /dev/null +++ b/examples/server/src/routes.rs @@ -0,0 +1,30 @@ +use axum::{Json, Router, routing::get}; +use utoipa::OpenApi; +use utoipa_axum::router::OpenApiRouter; +use utoipa_scalar::{Scalar, Servable}; + +use crate::user::routes::UserRoutes; + +#[derive(OpenApi)] +#[openapi(info( + title = "Fortifier Example API", + description = "Example to showcase Fortifier validation.", +))] +pub struct Routes; + +impl Routes { + pub fn router() -> Router + where + S: Clone + Send + Sync + 'static, + { + let router = OpenApiRouter::new().merge(UserRoutes::router()); + + let (router, openapi) = OpenApiRouter::with_openapi(Routes::openapi()) + .nest("/api/v1", router) + .split_for_parts(); + + router + .merge(Scalar::with_url("/api/reference", openapi.clone())) + .route("/api/v1/openapi.json", get(|| async { Json(openapi) })) + } +} diff --git a/examples/server/src/user.rs b/examples/server/src/user.rs new file mode 100644 index 0000000..1739d09 --- /dev/null +++ b/examples/server/src/user.rs @@ -0,0 +1,3 @@ +pub mod entities; +pub mod routes; +pub mod schemas; diff --git a/examples/server/src/user/entities.rs b/examples/server/src/user/entities.rs new file mode 100644 index 0000000..d9870e6 --- /dev/null +++ b/examples/server/src/user/entities.rs @@ -0,0 +1,12 @@ +pub mod user { + use serde::Serialize; + use utoipa::ToSchema; + use uuid::Uuid; + + #[derive(Serialize, ToSchema)] + pub struct Model { + pub id: Uuid, + pub email_address: String, + pub name: String, + } +} diff --git a/examples/server/src/user/routes.rs b/examples/server/src/user/routes.rs new file mode 100644 index 0000000..c8a0a32 --- /dev/null +++ b/examples/server/src/user/routes.rs @@ -0,0 +1,61 @@ +use axum::{Json, http::StatusCode, response::IntoResponse}; +use fortifier::{Validate, ValidationErrors}; +use thiserror::Error; +use utoipa_axum::{router::OpenApiRouter, routes}; +use uuid::Uuid; + +use crate::user::{ + entities::user, + schemas::{CreateUser, CreateUserValidationError}, +}; + +pub struct UserRoutes; + +impl UserRoutes { + pub fn router() -> OpenApiRouter + where + S: Clone + Send + Sync + 'static, + { + OpenApiRouter::new().routes(routes!(create_user)) + } +} + +#[derive(Debug, Error)] +enum CreateUserError { + #[error(transparent)] + UnprocessableContent(#[from] ValidationErrors), +} + +impl IntoResponse for CreateUserError { + fn into_response(self) -> axum::response::Response { + todo!() + } +} + +#[utoipa::path( + post, + path = "/users", + operation_id = "createUser", + summary = "Create user", + description = "Create a user.", + tags = ["User"], + request_body = CreateUser, + responses( + (status = 201, description = "The created user.", body = user::Model), + (status = 400, description = "Validation error.", body = ValidationErrors), + // (status = 500, description = "Internal server error.", body = ErrorBody), + ) +)] +async fn create_user( + Json(data): Json, +) -> Result<(StatusCode, Json), CreateUserError> { + data.validate().await?; + + let user = user::Model { + id: Uuid::now_v7(), + email_address: data.email_address, + name: data.name, + }; + + Ok((StatusCode::CREATED, Json(user))) +} diff --git a/examples/server/src/user/schemas.rs b/examples/server/src/user/schemas.rs new file mode 100644 index 0000000..6876fe3 --- /dev/null +++ b/examples/server/src/user/schemas.rs @@ -0,0 +1,12 @@ +use fortifier::Validate; +use serde::Deserialize; +use utoipa::ToSchema; + +#[derive(Deserialize, ToSchema, Validate)] +pub struct CreateUser { + #[validate(email)] + pub email_address: String, + + #[validate(length(min = 1, max = 256))] + pub name: String, +} diff --git a/packages/fortifier-macros/Cargo.toml b/packages/fortifier-macros/Cargo.toml index 2c3a850..79ee1db 100644 --- a/packages/fortifier-macros/Cargo.toml +++ b/packages/fortifier-macros/Cargo.toml @@ -11,8 +11,14 @@ version.workspace = true [lib] proc-macro = true +[features] +default = [] +serde = [] +utoipa = [] + [dependencies] convert_case = "0.9.0" +proc-macro-crate = "3.4.0" proc-macro2 = "1.0.103" quote = "1.0.42" syn = "2.0.110" diff --git a/packages/fortifier-macros/src/validate.rs b/packages/fortifier-macros/src/validate.rs index 3ea26fd..d065469 100644 --- a/packages/fortifier-macros/src/validate.rs +++ b/packages/fortifier-macros/src/validate.rs @@ -1,3 +1,4 @@ +mod attributes; mod r#enum; mod field; mod fields; diff --git a/packages/fortifier-macros/src/validate/attributes.rs b/packages/fortifier-macros/src/validate/attributes.rs new file mode 100644 index 0000000..a195a81 --- /dev/null +++ b/packages/fortifier-macros/src/validate/attributes.rs @@ -0,0 +1,38 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub fn enum_attributes() -> TokenStream { + let mut attributes: Vec = vec![]; + + #[cfg(feature = "serde")] + { + use proc_macro_crate::crate_name; + + if crate_name("serde").is_ok() { + attributes.push(quote! { + #[derive(serde::Deserialize, serde::Serialize)] + #[serde( + // TODO: Tag? + tag = "path", + rename_all = "camelCase", + rename_all_fields = "camelCase" + )] + }); + } + } + + #[cfg(feature = "utoipa")] + { + use proc_macro_crate::crate_name; + + if crate_name("utoipa").is_ok() { + attributes.push(quote! { + #[derive(utoipa::ToSchema)] + }); + } + } + + quote! { + #( #attributes )* + } +} diff --git a/packages/fortifier-macros/src/validate/enum.rs b/packages/fortifier-macros/src/validate/enum.rs index 77cfac6..fb93119 100644 --- a/packages/fortifier-macros/src/validate/enum.rs +++ b/packages/fortifier-macros/src/validate/enum.rs @@ -4,6 +4,7 @@ use syn::{DataEnum, DeriveInput, Generics, Ident, Result, Variant, Visibility}; use crate::{ validate::{ + attributes::enum_attributes, field::{LiteralOrIdent, ValidateFieldPrefix}, fields::ValidateFields, }, @@ -44,6 +45,7 @@ impl ValidateEnum { let visibility = &self.visibility; let error_ident = &self.error_ident; + let attributes = enum_attributes(); let error_variant_idents = self .variants .iter() @@ -60,6 +62,7 @@ impl ValidateEnum { quote! { #[allow(dead_code)] #[derive(Debug)] + #attributes #visibility enum #error_ident { #( #error_variant_idents(#error_variant_types) ),* } diff --git a/packages/fortifier-macros/src/validate/field.rs b/packages/fortifier-macros/src/validate/field.rs index 0cff6d0..6af6d96 100644 --- a/packages/fortifier-macros/src/validate/field.rs +++ b/packages/fortifier-macros/src/validate/field.rs @@ -4,6 +4,7 @@ use quote::{ToTokens, format_ident, quote}; use syn::{Field, Ident, Result, Visibility}; use crate::{ + validate::attributes::enum_attributes, validation::{Execution, Validation}, validations::{Custom, Email, Length, Regex, Url}, }; @@ -103,6 +104,7 @@ impl ValidateField { pub fn error_type(&self, ident: &Ident) -> (TokenStream, Option) { if self.validations.len() > 1 { + let attributes = enum_attributes(); let visibility = &self.visibility; let ident = format_ident!("{}{}ValidationError", ident, self.error_ident); let variant_ident = self.validations.iter().map(|validation| validation.ident()); @@ -115,6 +117,7 @@ impl ValidateField { ident.to_token_stream(), Some(quote! { #[derive(Debug)] + #attributes #visibility enum #ident { #( #variant_ident(#variant_type) ),* } diff --git a/packages/fortifier-macros/src/validate/fields.rs b/packages/fortifier-macros/src/validate/fields.rs index 7cb4551..9f789bc 100644 --- a/packages/fortifier-macros/src/validate/fields.rs +++ b/packages/fortifier-macros/src/validate/fields.rs @@ -3,7 +3,10 @@ use quote::{ToTokens, format_ident, quote}; use syn::{Fields, FieldsNamed, FieldsUnnamed, Ident, Result, Visibility}; use crate::{ - validate::field::{LiteralOrIdent, ValidateField, ValidateFieldPrefix}, + validate::{ + attributes::enum_attributes, + field::{LiteralOrIdent, ValidateField, ValidateFieldPrefix}, + }, validation::Execution, }; @@ -194,6 +197,8 @@ fn error_type<'a>( error_ident: &Ident, fields: impl Iterator, ) -> (TokenStream, TokenStream) { + let attributes = enum_attributes(); + let mut error_field_idents = vec![]; let mut error_field_types = vec![]; let mut error_field_enums = vec![]; @@ -214,6 +219,7 @@ fn error_type<'a>( quote! { #[allow(dead_code)] #[derive(Debug)] + #attributes #visibility enum #error_ident { #( #error_field_idents(#error_field_types) ),* } diff --git a/packages/fortifier/Cargo.toml b/packages/fortifier/Cargo.toml index 58bf4bc..03ac0e1 100644 --- a/packages/fortifier/Cargo.toml +++ b/packages/fortifier/Cargo.toml @@ -15,8 +15,9 @@ indexmap = ["dep:indexmap"] macros = ["dep:fortifier-macros"] message = [] regex = ["dep:regex"] -serde = ["dep:serde", "email_address/serde_support"] +serde = ["dep:serde", "email_address?/serde_support", "fortifier-macros?/serde"] url = ["dep:url"] +utoipa = ["dep:utoipa", "fortifier-macros?/utoipa"] [dependencies] email_address = { version = "0.2.9", default-features = false, optional = true } @@ -25,6 +26,7 @@ indexmap = { version = "2.12.0", optional = true } regex = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } url = { version = "2.5.7", optional = true } +utoipa = { workspace = true, optional = true } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/packages/fortifier/src/lib.rs b/packages/fortifier/src/lib.rs index 89b06b0..d9a6264 100644 --- a/packages/fortifier/src/lib.rs +++ b/packages/fortifier/src/lib.rs @@ -10,3 +10,12 @@ pub use validations::*; #[cfg(feature = "macros")] pub use fortifier_macros::*; + +#[doc(hidden)] +pub mod external { + #[cfg(feature = "serde")] + pub use serde; + + #[cfg(feature = "utoipa")] + pub use utoipa; +} diff --git a/packages/fortifier/src/validate.rs b/packages/fortifier/src/validate.rs index f3a51e6..b381796 100644 --- a/packages/fortifier/src/validate.rs +++ b/packages/fortifier/src/validate.rs @@ -7,6 +7,7 @@ use std::{ /// Validation errors. #[derive(Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct ValidationErrors(Vec); impl Display for ValidationErrors { diff --git a/packages/fortifier/src/validations/email.rs b/packages/fortifier/src/validations/email.rs index dd28ebe..ad27091 100644 --- a/packages/fortifier/src/validations/email.rs +++ b/packages/fortifier/src/validations/email.rs @@ -19,6 +19,7 @@ pub use email_address::Options as EmailOptions; rename_all_fields = "camelCase" ) )] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub enum EmailError { /// Invalid character error. InvalidCharacter { diff --git a/packages/fortifier/src/validations/length.rs b/packages/fortifier/src/validations/length.rs index 82adfd5..36f8f23 100644 --- a/packages/fortifier/src/validations/length.rs +++ b/packages/fortifier/src/validations/length.rs @@ -21,6 +21,7 @@ use indexmap::{IndexMap, IndexSet}; rename_all_fields = "camelCase" ) )] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub enum LengthError { /// Length is not equal to the required length. Equal { diff --git a/packages/fortifier/src/validations/regex.rs b/packages/fortifier/src/validations/regex.rs index 434b720..9105ec9 100644 --- a/packages/fortifier/src/validations/regex.rs +++ b/packages/fortifier/src/validations/regex.rs @@ -41,6 +41,7 @@ where derive(serde::Deserialize, serde::Serialize), serde(rename_all = "camelCase") )] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct RegexError { /// A human-readable error message. #[cfg(feature = "message")] diff --git a/packages/fortifier/src/validations/url.rs b/packages/fortifier/src/validations/url.rs index a7dc615..3d36caa 100644 --- a/packages/fortifier/src/validations/url.rs +++ b/packages/fortifier/src/validations/url.rs @@ -18,6 +18,7 @@ use url::{ParseError, Url}; rename_all_fields = "camelCase" ) )] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub enum UrlError { /// Empty host error. EmptyHost { diff --git a/packages/fortifier/tests/serde.rs b/packages/fortifier/tests/serde.rs index 52c502e..41dab99 100644 --- a/packages/fortifier/tests/serde.rs +++ b/packages/fortifier/tests/serde.rs @@ -22,6 +22,7 @@ fn setup() -> (ValidationErrors, Value) { TestError::Length(LengthError::Equal { equal: 1, length: 2, + #[cfg(feature = "message")] message: "length 2 is not equal to required length 1".to_owned(), }), TestError::Regex(RegexError::default()),