From a684688a81ac02650f44b81bfaa1bbf7b5567f1e Mon Sep 17 00:00:00 2001 From: realaravinth Date: Wed, 11 May 2022 03:43:01 +0530 Subject: [PATCH] feat: mv realaravinth/actix-web/actix-web-codegen . --- Cargo.lock | 1348 +++++++++++++++++ Cargo.toml | 33 + src/lib.rs | 195 +++ src/route.rs | 398 +++++ tests/test_macro.rs | 293 ++++ tests/trybuild.rs | 18 + tests/trybuild/docstring-ok.rs | 17 + tests/trybuild/route-duplicate-method-fail.rs | 17 + .../route-duplicate-method-fail.stderr | 11 + tests/trybuild/route-malformed-path-fail.rs | 33 + .../trybuild/route-malformed-path-fail.stderr | 42 + tests/trybuild/route-missing-method-fail.rs | 17 + .../trybuild/route-missing-method-fail.stderr | 13 + tests/trybuild/route-ok.rs | 17 + .../trybuild/route-unexpected-method-fail.rs | 17 + .../route-unexpected-method-fail.stderr | 11 + tests/trybuild/simple-fail.rs | 37 + tests/trybuild/simple-fail.stderr | 35 + tests/trybuild/simple.rs | 16 + tests/trybuild/test-runtime.rs | 6 + 20 files changed, 2574 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/route.rs create mode 100644 tests/test_macro.rs create mode 100644 tests/trybuild.rs create mode 100644 tests/trybuild/docstring-ok.rs create mode 100644 tests/trybuild/route-duplicate-method-fail.rs create mode 100644 tests/trybuild/route-duplicate-method-fail.stderr create mode 100644 tests/trybuild/route-malformed-path-fail.rs create mode 100644 tests/trybuild/route-malformed-path-fail.stderr create mode 100644 tests/trybuild/route-missing-method-fail.rs create mode 100644 tests/trybuild/route-missing-method-fail.stderr create mode 100644 tests/trybuild/route-ok.rs create mode 100644 tests/trybuild/route-unexpected-method-fail.rs create mode 100644 tests/trybuild/route-unexpected-method-fail.stderr create mode 100644 tests/trybuild/simple-fail.rs create mode 100644 tests/trybuild/simple-fail.stderr create mode 100644 tests/trybuild/simple.rs create mode 100644 tests/trybuild/test-runtime.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7492f5a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1348 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "log", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-http" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha-1", + "smallvec", + "zstd", +] + +[[package]] +name = "actix-http-test" +version = "3.0.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09594b8a7eaa15da5cfd0e25c779d4b1d6ded62d4a79904f1d91b0dcf97c78e7" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "awc", + "base64", + "bytes", + "futures-core", + "http", + "log", + "serde", + "serde_json", + "serde_urlencoded", + "slab", + "socket2", + "tokio", +] + +[[package]] +name = "actix-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" +dependencies = [ + "bytestring", + "firestorm", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-test" +version = "0.1.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7919a113ef74b5d52218ea0bd310d0d1b0d2be822994716754e265f20734d601" +dependencies = [ + "actix-codec", + "actix-http", + "actix-http-test", + "actix-rt", + "actix-service", + "actix-utils", + "actix-web", + "awc", + "futures-core", + "futures-util", + "log", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log", + "pin-project-lite", + "tokio-util", +] + +[[package]] +name = "actix-utils" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix-web-codegen-const-routes" +version = "4.0.0" +dependencies = [ + "actix-macros", + "actix-router", + "actix-rt", + "actix-test", + "actix-utils", + "actix-web", + "futures-core", + "proc-macro2", + "quote", + "rustversion", + "syn", + "trybuild", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "awc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c60c44fbf3c8cee365e86b97d706e513b733c4eeb16437b45b88d2fffe889a" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash", + "base64", + "bytes", + "cfg-if", + "cookie", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "bytestring" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "firestorm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d6188b8804df28032815ea256b6955c9625c24da7525f387a7af02fbb8f01" + +[[package]] +name = "flate2" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" + +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[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 = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "winapi", +] + +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "trybuild" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc92f558afb6d1d7c6f175eb8d615b8ef49c227543e68e19c123d4ee43d8a7d" +dependencies = [ + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "zstd" +version = "0.10.0+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.4+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..56e305a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "actix-web-codegen-const-routes" +version = "4.0.0" +description = "Routing and runtime macros for Actix Web with support for const routes" +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +authors = [ + "Nikolay Kim ", + "Rob Ede ", + "Aravinth Manivannan ", +] +license = "MIT OR Apache-2.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +actix-router = "0.5.0" +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full", "parsing"] } + +[dev-dependencies] +actix-macros = "0.2.3" +actix-rt = "2.2" +actix-test = "0.1.0-beta.13" +actix-utils = "3.0.0" +actix-web = "4.0.0" + +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +trybuild = "1" +rustversion = "1" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a89705b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,195 @@ +//! Routing and runtime macros for Actix Web. +//! +//! # Actix Web Re-exports +//! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to +//! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this +//! crate before the actix-web dependency is updated. Therefore, code examples here will show +//! explicit imports. Check the latest [actix-web attributes docs] to see which macros +//! are re-exported. +//! +//! # Runtime Setup +//! Used for setting up the actix async runtime. See [macro@main] macro docs. +//! +//! ``` +//! #[actix_web_codegen_const_routes::main] // or `#[actix_web::main]` in Actix Web apps +//! async fn main() { +//! async { println!("Hello world"); }.await +//! } +//! ``` +//! +//! # Single Method Handler +//! There is a macro to set up a handler for each of the most common HTTP methods that also define +//! additional guards and route-specific middleware. +//! +//! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE] +//! +//! ``` +//! # use actix_web::HttpResponse; +//! # use actix_web_codegen_const_routes::get; +//! #[get("/test")] +//! async fn get_handler() -> HttpResponse { +//! HttpResponse::Ok().finish() +//! } +//! ``` +//! +//! # Multiple Method Handlers +//! Similar to the single method handler macro but takes one or more arguments for the HTTP methods +//! it should respond to. See [macro@route] macro docs. +//! +//! ``` +//! # use actix_web::HttpResponse; +//! # use actix_web_codegen_const_routes::route; +//! #[route("/test", method = "GET", method = "HEAD")] +//! async fn get_and_head_handler() -> HttpResponse { +//! HttpResponse::Ok().finish() +//! } +//! ``` +//! +//! # Multiple Path Handlers +//! There are no macros to generate multi-path handlers. Let us know in [this issue]. +//! +//! [this issue]: https://github.com/actix/actix-web/issues/1709 +//! +//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes +//! [GET]: macro@get +//! [POST]: macro@post +//! [PUT]: macro@put +//! [HEAD]: macro@head +//! [CONNECT]: macro@macro@connect +//! [OPTIONS]: macro@options +//! [TRACE]: macro@trace +//! [PATCH]: macro@patch +//! [DELETE]: macro@delete + +#![recursion_limit = "512"] +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] + +use proc_macro::TokenStream; +use quote::quote; + +mod route; + +/// Creates resource handler, allowing multiple HTTP method guards. +/// +/// # Syntax +/// ```plain +/// #[route("path", method="HTTP_METHOD"[, attributes])] +/// ``` +/// +/// # Attributes +/// - `"path"`: Raw literal string with path for which to register handler. +/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function +/// name of handler is used. +/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string, +/// "GET", "POST" for example. +/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`. +/// - `wrap = "Middleware"`: Registers a resource middleware. +/// +/// # Notes +/// Function name can be specified as any expression that is going to be accessible to the generate +/// code, e.g `my_guard` or `my_module::my_guard`. +/// +/// # Examples +/// ``` +/// # use actix_web::HttpResponse; +/// # use actix_web_codegen_const_routes::route; +/// #[route("/test", method = "GET", method = "HEAD")] +/// async fn example() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// ``` +#[proc_macro_attribute] +pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { + route::with_method(None, args, input) +} + +macro_rules! method_macro { + ($variant:ident, $method:ident) => { +#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")] +/// +/// # Syntax +/// ```plain +#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)] +/// ``` +/// +/// # Attributes +/// - `"path"`: Raw literal string with path for which to register handler. +/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function +/// name of handler is used. +/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`. +/// - `wrap = "Middleware"`: Registers a resource middleware. +/// +/// # Notes +/// Function name can be specified as any expression that is going to be accessible to the +/// generate code, e.g `my_guard` or `my_module::my_guard`. +/// +/// # Examples +/// ``` +/// # use actix_web::HttpResponse; +#[doc = concat!("# use actix_web_codegen_const_routes::", stringify!($method), ";")] +#[doc = concat!("#[", stringify!($method), r#"("/")]"#)] +/// async fn example() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// ``` +#[proc_macro_attribute] +pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream { + route::with_method(Some(route::MethodType::$variant), args, input) +} + }; +} + +method_macro!(Get, get); +method_macro!(Post, post); +method_macro!(Put, put); +method_macro!(Delete, delete); +method_macro!(Head, head); +method_macro!(Connect, connect); +method_macro!(Options, options); +method_macro!(Trace, trace); +method_macro!(Patch, patch); + +/// Marks async main function as the Actix Web system entry-point. +/// +/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is +/// still necessary for actor support (since actors use a `System`). Read more in the +/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs. +/// +/// # Examples +/// ``` +/// #[actix_web::main] +/// async fn main() { +/// async { println!("Hello world"); }.await +/// } +/// ``` +#[proc_macro_attribute] +pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { + let mut output: TokenStream = (quote! { + #[::actix_web::rt::main(system = "::actix_web::rt::System")] + }) + .into(); + + output.extend(item); + output +} + +/// Marks async test functions to use the actix system entry-point. +/// +/// # Examples +/// ``` +/// #[actix_web::test] +/// async fn test() { +/// assert_eq!(async { "Hello world" }.await, "Hello world"); +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { + let mut output: TokenStream = (quote! { + #[::actix_web::rt::test(system = "::actix_web::rt::System")] + }) + .into(); + + output.extend(item); + output +} diff --git a/src/route.rs b/src/route.rs new file mode 100644 index 0000000..3af845f --- /dev/null +++ b/src/route.rs @@ -0,0 +1,398 @@ +use std::{collections::HashSet, convert::TryFrom}; + +use actix_router::ResourceDef; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; +use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta}; + +enum ResourceType { + Async, + Sync, +} + +impl ToTokens for ResourceType { + fn to_tokens(&self, stream: &mut TokenStream2) { + let ident = format_ident!("to"); + stream.append(ident); + } +} + +macro_rules! method_type { + ( + $($variant:ident, $upper:ident,)+ + ) => { + #[derive(Debug, PartialEq, Eq, Hash)] + pub enum MethodType { + $( + $variant, + )+ + } + + impl MethodType { + fn as_str(&self) -> &'static str { + match self { + $(Self::$variant => stringify!($variant),)+ + } + } + + fn parse(method: &str) -> Result { + match method { + $(stringify!($upper) => Ok(Self::$variant),)+ + _ => Err(format!("Unexpected HTTP method: `{}`", method)), + } + } + } + }; +} + +method_type! { + Get, GET, + Post, POST, + Put, PUT, + Delete, DELETE, + Head, HEAD, + Connect, CONNECT, + Options, OPTIONS, + Trace, TRACE, + Patch, PATCH, +} + +impl ToTokens for MethodType { + fn to_tokens(&self, stream: &mut TokenStream2) { + let ident = Ident::new(self.as_str(), Span::call_site()); + stream.append(ident); + } +} + +impl TryFrom<&syn::LitStr> for MethodType { + type Error = syn::Error; + + fn try_from(value: &syn::LitStr) -> Result { + Self::parse(value.value().as_str()) + .map_err(|message| syn::Error::new_spanned(value, message)) + } +} + +struct Args { + path: Box, + resource_name: Option, + guards: Vec, + wrappers: Vec, + methods: HashSet, +} + +trait PathMarker: quote::ToTokens {} + +impl PathMarker for syn::Ident {} +impl PathMarker for syn::LitStr {} +impl PathMarker for syn::Expr {} + +impl Args { + fn new(args: AttributeArgs, method: Option) -> syn::Result { + let mut path: Option> = None; + let mut resource_name = None; + let mut guards = Vec::new(); + let mut wrappers = Vec::new(); + let mut methods = HashSet::new(); + + let is_route_macro = method.is_none(); + if let Some(method) = method { + methods.insert(method); + } + + for arg in args { + match arg { + NestedMeta::Lit(syn::Lit::Str(lit)) => match path { + None => { + let _ = ResourceDef::new(lit.value()); + path = Some(Box::new(lit)); + } + + _ => { + return Err(syn::Error::new_spanned( + lit, + "Multiple paths specified! Should be only one!", + )); + } + }, + NestedMeta::Meta(syn::Meta::NameValue(nv)) => { + if nv.path.is_ident("name") { + if let syn::Lit::Str(lit) = nv.lit { + resource_name = Some(lit); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute name expects literal string!", + )); + } + } else if nv.path.is_ident("path") { + if let syn::Lit::Str(lit) = nv.lit { + match path { + None => { + path = Some(Box::new(lit.parse::()?)); + } + _ => { + return Err(syn::Error::new_spanned( + lit, + "Multiple paths specified! Should be only one!", + )); + } + } + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute path expects literal string!", + )); + } + } else if nv.path.is_ident("guard") { + if let syn::Lit::Str(lit) = nv.lit { + guards.push(Ident::new(&lit.value(), Span::call_site())); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute guard expects literal string!", + )); + } + } else if nv.path.is_ident("wrap") { + if let syn::Lit::Str(lit) = nv.lit { + wrappers.push(lit.parse()?); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute wrap expects type", + )); + } + } else if nv.path.is_ident("method") { + if !is_route_macro { + return Err(syn::Error::new_spanned( + &nv, + "HTTP method forbidden here. To handle multiple methods, use `route` instead", + )); + } else if let syn::Lit::Str(ref lit) = nv.lit { + let method = MethodType::try_from(lit)?; + if !methods.insert(method) { + return Err(syn::Error::new_spanned( + &nv.lit, + &format!( + "HTTP method defined more than once: `{}`", + lit.value() + ), + )); + } + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute method expects literal string!", + )); + } + } else { + return Err(syn::Error::new_spanned( + nv.path, + "Unknown attribute key is specified. Allowed: guard, method and wrap", + )); + } + } + arg => { + return Err(syn::Error::new_spanned(arg, "Unknown attribute.")); + } + } + } + + Ok(Args { + path: path.unwrap(), + resource_name, + guards, + wrappers, + methods, + }) + } +} + +pub struct Route { + name: syn::Ident, + args: Args, + ast: syn::ItemFn, + resource_type: ResourceType, + + /// The doc comment attributes to copy to generated struct, if any. + doc_attributes: Vec, +} + +fn guess_resource_type(typ: &syn::Type) -> ResourceType { + let mut guess = ResourceType::Sync; + + if let syn::Type::ImplTrait(typ) = typ { + for bound in typ.bounds.iter() { + if let syn::TypeParamBound::Trait(bound) = bound { + for bound in bound.path.segments.iter() { + if bound.ident == "Future" { + guess = ResourceType::Async; + break; + } else if bound.ident == "Responder" { + guess = ResourceType::Sync; + break; + } + } + } + } + } + + guess +} + +impl Route { + pub fn new( + args: AttributeArgs, + ast: syn::ItemFn, + method: Option, + ) -> syn::Result { + if args.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + format!( + r#"invalid service definition, expected #[{}("")]"#, + method + .map_or("route", |it| it.as_str()) + .to_ascii_lowercase() + ), + )); + } + + let name = ast.sig.ident.clone(); + + // Try and pull out the doc comments so that we can reapply them to the generated struct. + // Note that multi line doc comments are converted to multiple doc attributes. + let doc_attributes = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .cloned() + .collect(); + + let args = Args::new(args, method)?; + if args.methods.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + "The #[route(..)] macro requires at least one `method` attribute", + )); + } + + let resource_type = if ast.sig.asyncness.is_some() { + ResourceType::Async + } else { + match ast.sig.output { + syn::ReturnType::Default => { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } + syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), + } + }; + + Ok(Self { + name, + args, + ast, + resource_type, + doc_attributes, + }) + } +} + +impl ToTokens for Route { + fn to_tokens(&self, output: &mut TokenStream2) { + let Self { + name, + ast, + args: + Args { + path, + resource_name, + guards, + wrappers, + methods, + }, + resource_type, + doc_attributes, + } = self; + let resource_name = resource_name + .as_ref() + .map_or_else(|| name.to_string(), LitStr::value); + let method_guards = { + let mut others = methods.iter(); + // unwrapping since length is checked to be at least one + let first = others.next().unwrap(); + + if methods.len() > 1 { + quote! { + .guard( + ::actix_web::guard::Any(::actix_web::guard::#first()) + #(.or(::actix_web::guard::#others()))* + ) + } + } else { + quote! { + .guard(::actix_web::guard::#first()) + } + } + }; + + let path = path.as_ref(); + + let stream = quote! { + #(#doc_attributes)* + #[allow(non_camel_case_types, missing_docs)] + pub struct #name; + + impl ::actix_web::dev::HttpServiceFactory for #name { + fn register(self, __config: &mut actix_web::dev::AppService) { + #ast + let __resource = ::actix_web::Resource::new(#path) + .name(#resource_name) + #method_guards + #(.guard(::actix_web::guard::fn_guard(#guards)))* + #(.wrap(#wrappers))* + .#resource_type(#name); + + ::actix_web::dev::HttpServiceFactory::register(__resource, __config) + } + } + }; + output.extend(stream); + } +} + +pub(crate) fn with_method( + method: Option, + args: TokenStream, + input: TokenStream, +) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + + let ast = match syn::parse::(input.clone()) { + Ok(ast) => ast, + // on parse error, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(input, err), + }; + + match Route::new(args, ast, method) { + Ok(route) => route.into_token_stream().into(), + // on macro related error, make IDEs happy; see fn docs + Err(err) => input_and_compile_error(input, err), + } +} + +/// Converts the error to a token stream and appends it to the original input. +/// +/// Returning the original input in addition to the error is good for IDEs which can gracefully +/// recover and show more precise errors within the macro body. +/// +/// See for more info. +fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream { + let compile_err = TokenStream::from(err.to_compile_error()); + item.extend(compile_err); + item +} diff --git a/tests/test_macro.rs b/tests/test_macro.rs new file mode 100644 index 0000000..9cb83c6 --- /dev/null +++ b/tests/test_macro.rs @@ -0,0 +1,293 @@ +use std::future::Future; + +use actix_utils::future::{ok, Ready}; +use actix_web::{ + dev::{Service, ServiceRequest, ServiceResponse, Transform}, + http::{ + self, + header::{HeaderName, HeaderValue}, + StatusCode, + }, + web, App, Error, HttpResponse, Responder, +}; +use actix_web_codegen_const_routes::{connect, delete, get, head, options, patch, post, put, route, trace}; +use futures_core::future::LocalBoxFuture; + +// Make sure that we can name function as 'config' +#[get("/config")] +async fn config() -> impl Responder { + HttpResponse::Ok() +} + +const PATH: &str = "/path"; +#[get(path = "PATH")] +async fn path() -> impl Responder { + HttpResponse::Ok() +} + +struct FieldPath(&'static str); + +const FIELD_PATH: FieldPath = FieldPath("/path/new/"); +#[get(path = "FIELD_PATH.0")] +async fn field_path() -> impl Responder { + HttpResponse::Ok() +} + +#[get("/test")] +async fn test_handler() -> impl Responder { + HttpResponse::Ok() +} + +#[put("/test")] +async fn put_test() -> impl Responder { + HttpResponse::Created() +} + +#[patch("/test")] +async fn patch_test() -> impl Responder { + HttpResponse::Ok() +} + +#[post("/test")] +async fn post_test() -> impl Responder { + HttpResponse::NoContent() +} + +#[head("/test")] +async fn head_test() -> impl Responder { + HttpResponse::Ok() +} + +#[connect("/test")] +async fn connect_test() -> impl Responder { + HttpResponse::Ok() +} + +#[options("/test")] +async fn options_test() -> impl Responder { + HttpResponse::Ok() +} + +#[trace("/test")] +async fn trace_test() -> impl Responder { + HttpResponse::Ok() +} + +#[get("/test")] +fn auto_async() -> impl Future> { + ok(HttpResponse::Ok().finish()) +} + +#[get("/test")] +fn auto_sync() -> impl Future> { + ok(HttpResponse::Ok().finish()) +} + +#[put("/test/{param}")] +async fn put_param_test(_: web::Path) -> impl Responder { + HttpResponse::Created() +} + +#[delete("/test/{param}")] +async fn delete_param_test(_: web::Path) -> impl Responder { + HttpResponse::NoContent() +} + +#[get("/test/{param}")] +async fn get_param_test(_: web::Path) -> impl Responder { + HttpResponse::Ok() +} + +#[route("/multi", method = "GET", method = "POST", method = "HEAD")] +async fn route_test() -> impl Responder { + HttpResponse::Ok() +} + +#[get("/custom_resource_name", name = "custom")] +async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder { + assert!(req.url_for_static("custom").is_ok()); + assert!(req.url_for_static("custom_resource_name_test").is_err()); + HttpResponse::Ok() +} + +pub struct ChangeStatusCode; + +impl Transform for ChangeStatusCode +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Transform = ChangeStatusCodeMiddleware; + type InitError = (); + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(ChangeStatusCodeMiddleware { service }) + } +} + +pub struct ChangeStatusCodeMiddleware { + service: S, +} + +impl Service for ChangeStatusCodeMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + + actix_web::dev::forward_ready!(service); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let fut = self.service.call(req); + + Box::pin(async move { + let mut res = fut.await?; + let headers = res.headers_mut(); + let header_name = HeaderName::from_lowercase(b"custom-header").unwrap(); + let header_value = HeaderValue::from_str("hello").unwrap(); + headers.insert(header_name, header_value); + Ok(res) + }) + } +} + +#[get("/test/wrap", wrap = "ChangeStatusCode")] +async fn get_wrap(_: web::Path) -> impl Responder { + // panic!("actually never gets called because path failed to extract"); + HttpResponse::Ok() +} + +#[actix_rt::test] +async fn test_params() { + let srv = actix_test::start(|| { + App::new() + .service(get_param_test) + .service(put_param_test) + .service(delete_param_test) + .service(path) + .service(field_path) + }); + let request = srv.request(http::Method::GET, srv.url(FIELD_PATH.0)); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); + + let request = srv.request(http::Method::GET, srv.url(PATH)); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); + + let request = srv.request(http::Method::GET, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); + + let request = srv.request(http::Method::PUT, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::CREATED); + + let request = srv.request(http::Method::DELETE, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); +} + +#[actix_rt::test] +async fn test_body() { + let srv = actix_test::start(|| { + App::new() + .service(post_test) + .service(put_test) + .service(head_test) + .service(connect_test) + .service(options_test) + .service(trace_test) + .service(patch_test) + .service(test_handler) + .service(route_test) + .service(custom_resource_name_test) + }); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::HEAD, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::CONNECT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::OPTIONS, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::TRACE, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::PATCH, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::PUT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::CREATED); + + let request = srv.request(http::Method::POST, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/multi")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::POST, srv.url("/multi")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::HEAD, srv.url("/multi")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::PATCH, srv.url("/multi")); + let response = request.send().await.unwrap(); + assert!(!response.status().is_success()); + + let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} + +#[actix_rt::test] +async fn test_auto_async() { + let srv = actix_test::start(|| App::new().service(auto_async)); + + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} + +#[actix_web::test] +async fn test_wrap() { + let srv = actix_test::start(|| App::new().service(get_wrap)); + + let request = srv.request(http::Method::GET, srv.url("/test/wrap")); + let mut response = request.send().await.unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + assert!(response.headers().contains_key("custom-header")); + let body = response.body().await.unwrap(); + let body = String::from_utf8(body.to_vec()).unwrap(); + assert!(body.contains("wrong number of parameters")); +} diff --git a/tests/trybuild.rs b/tests/trybuild.rs new file mode 100644 index 0000000..b2d9ce1 --- /dev/null +++ b/tests/trybuild.rs @@ -0,0 +1,18 @@ +#[rustversion::stable(1.54)] // MSRV +#[test] +fn compile_macros() { + let t = trybuild::TestCases::new(); + + t.pass("tests/trybuild/simple.rs"); + t.compile_fail("tests/trybuild/simple-fail.rs"); + + t.pass("tests/trybuild/route-ok.rs"); + t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); + t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); + t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); + t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); + + t.pass("tests/trybuild/docstring-ok.rs"); + + t.pass("tests/trybuild/test-runtime.rs"); +} diff --git a/tests/trybuild/docstring-ok.rs b/tests/trybuild/docstring-ok.rs new file mode 100644 index 0000000..74543b3 --- /dev/null +++ b/tests/trybuild/docstring-ok.rs @@ -0,0 +1,17 @@ +use actix_web::{Responder, HttpResponse, App}; +use actix_web_codegen_const_routes::*; + +/// doc comments shouldn't break anything +#[get("/")] +async fn index() -> impl Responder { + HttpResponse::Ok() +} + +#[actix_web::main] +async fn main() { + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/route-duplicate-method-fail.rs b/tests/trybuild/route-duplicate-method-fail.rs new file mode 100644 index 0000000..8281711 --- /dev/null +++ b/tests/trybuild/route-duplicate-method-fail.rs @@ -0,0 +1,17 @@ +use actix_web_codegen_const_routes::*; + +#[route("/", method="GET", method="GET")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/route-duplicate-method-fail.stderr b/tests/trybuild/route-duplicate-method-fail.stderr new file mode 100644 index 0000000..90cff1b --- /dev/null +++ b/tests/trybuild/route-duplicate-method-fail.stderr @@ -0,0 +1,11 @@ +error: HTTP method defined more than once: `GET` + --> $DIR/route-duplicate-method-fail.rs:3:35 + | +3 | #[route("/", method="GET", method="GET")] + | ^^^^^ + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> $DIR/route-duplicate-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/tests/trybuild/route-malformed-path-fail.rs b/tests/trybuild/route-malformed-path-fail.rs new file mode 100644 index 0000000..9a0e47f --- /dev/null +++ b/tests/trybuild/route-malformed-path-fail.rs @@ -0,0 +1,33 @@ +use actix_web_codegen_const_routes::get; + +#[get("/{")] +async fn zero() -> &'static str { + "malformed resource def" +} + +#[get("/{foo")] +async fn one() -> &'static str { + "malformed resource def" +} + +#[get("/{}")] +async fn two() -> &'static str { + "malformed resource def" +} + +#[get("/*")] +async fn three() -> &'static str { + "malformed resource def" +} + +#[get("/{tail:\\d+}*")] +async fn four() -> &'static str { + "malformed resource def" +} + +#[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] +async fn five() -> &'static str { + "malformed resource def" +} + +fn main() {} diff --git a/tests/trybuild/route-malformed-path-fail.stderr b/tests/trybuild/route-malformed-path-fail.stderr new file mode 100644 index 0000000..93c5101 --- /dev/null +++ b/tests/trybuild/route-malformed-path-fail.stderr @@ -0,0 +1,42 @@ +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:3:1 + | +3 | #[get("/{")] + | ^^^^^^^^^^^^ + | + = help: message: pattern "{" contains malformed dynamic segment + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:8:1 + | +8 | #[get("/{foo")] + | ^^^^^^^^^^^^^^^ + | + = help: message: pattern "{foo" contains malformed dynamic segment + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:13:1 + | +13 | #[get("/{}")] + | ^^^^^^^^^^^^^ + | + = help: message: Wrong path pattern: "/{}" regex parse error: + ((?s-m)^/(?P<>[^/]+))$ + ^ + error: empty capture group name + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:23:1 + | +23 | #[get("/{tail:\\d+}*")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: custom regex is not supported for tail match + +error: custom attribute panicked + --> $DIR/route-malformed-path-fail.rs:28:1 + | +28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Only 16 dynamic segments are allowed, provided: 17 diff --git a/tests/trybuild/route-missing-method-fail.rs b/tests/trybuild/route-missing-method-fail.rs new file mode 100644 index 0000000..d444d87 --- /dev/null +++ b/tests/trybuild/route-missing-method-fail.rs @@ -0,0 +1,17 @@ +use actix_web_codegen_const_routes::*; + +#[route("/")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/route-missing-method-fail.stderr b/tests/trybuild/route-missing-method-fail.stderr new file mode 100644 index 0000000..b1cefaf --- /dev/null +++ b/tests/trybuild/route-missing-method-fail.stderr @@ -0,0 +1,13 @@ +error: The #[route(..)] macro requires at least one `method` attribute + --> tests/trybuild/route-missing-method-fail.rs:3:1 + | +3 | #[route("/")] + | ^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> tests/trybuild/route-missing-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/tests/trybuild/route-ok.rs b/tests/trybuild/route-ok.rs new file mode 100644 index 0000000..d51aa84 --- /dev/null +++ b/tests/trybuild/route-ok.rs @@ -0,0 +1,17 @@ +use actix_web_codegen_const_routes::*; + +#[route("/", method="GET", method="HEAD")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/route-unexpected-method-fail.rs b/tests/trybuild/route-unexpected-method-fail.rs new file mode 100644 index 0000000..99e6b59 --- /dev/null +++ b/tests/trybuild/route-unexpected-method-fail.rs @@ -0,0 +1,17 @@ +use actix_web_codegen_const_routes::*; + +#[route("/", method="UNEXPECTED")] +async fn index() -> String { + "Hello World!".to_owned() +} + +#[actix_web::main] +async fn main() { + use actix_web::App; + + let srv = actix_test::start(|| App::new().service(index)); + + let request = srv.get("/"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/route-unexpected-method-fail.stderr b/tests/trybuild/route-unexpected-method-fail.stderr new file mode 100644 index 0000000..dda3660 --- /dev/null +++ b/tests/trybuild/route-unexpected-method-fail.stderr @@ -0,0 +1,11 @@ +error: Unexpected HTTP method: `UNEXPECTED` + --> $DIR/route-unexpected-method-fail.rs:3:21 + | +3 | #[route("/", method="UNEXPECTED")] + | ^^^^^^^^^^^^ + +error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied + --> $DIR/route-unexpected-method-fail.rs:12:55 + | +12 | let srv = actix_test::start(|| App::new().service(index)); + | ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` diff --git a/tests/trybuild/simple-fail.rs b/tests/trybuild/simple-fail.rs new file mode 100644 index 0000000..61c09d9 --- /dev/null +++ b/tests/trybuild/simple-fail.rs @@ -0,0 +1,37 @@ +use actix_web_codegen_const_routes::*; + +#[get("/one", other)] +async fn one() -> String { + "Hello World!".to_owned() +} + +#[post(/two)] +async fn two() -> String { + "Hello World!".to_owned() +} + +static PATCH_PATH: &str = "/three"; + +#[patch(PATCH_PATH)] +async fn three() -> String { + "Hello World!".to_owned() +} + +#[delete("/four", "/five")] +async fn four() -> String { + "Hello World!".to_owned() +} + +#[delete("/five", method="GET")] +async fn five() -> String { + "Hello World!".to_owned() +} + +const SIX: &str = "/six"; + +#[get("/six", path="SIX")] +async fn six() -> String { + "Hello World!".to_owned() +} + +fn main() {} diff --git a/tests/trybuild/simple-fail.stderr b/tests/trybuild/simple-fail.stderr new file mode 100644 index 0000000..a091528 --- /dev/null +++ b/tests/trybuild/simple-fail.stderr @@ -0,0 +1,35 @@ +error: Unknown attribute. + --> $DIR/simple-fail.rs:3:15 + | +3 | #[get("/one", other)] + | ^^^^^ + +error: expected identifier or literal + --> $DIR/simple-fail.rs:8:8 + | +8 | #[post(/two)] + | ^ + +error: Unknown attribute. + --> $DIR/simple-fail.rs:15:9 + | +15 | #[patch(PATCH_PATH)] + | ^^^^^^^^^^ + +error: Multiple paths specified! Should be only one! + --> $DIR/simple-fail.rs:20:19 + | +20 | #[delete("/four", "/five")] + | ^^^^^^^ + +error: HTTP method forbidden here. To handle multiple methods, use `route` instead + --> $DIR/simple-fail.rs:25:19 + | +25 | #[delete("/five", method="GET")] + | ^^^^^^^^^^^^ + +error: Multiple paths specified! Should be only one! + --> $DIR/simple-fail.rs:32:20 + | +32 | #[get("/six", path="SIX")] + | ^^^^^ diff --git a/tests/trybuild/simple.rs b/tests/trybuild/simple.rs new file mode 100644 index 0000000..4db4500 --- /dev/null +++ b/tests/trybuild/simple.rs @@ -0,0 +1,16 @@ +use actix_web::{Responder, HttpResponse, App}; +use actix_web_codegen_const_routes::*; + +#[get("/config")] +async fn config() -> impl Responder { + HttpResponse::Ok() +} + +#[actix_web::main] +async fn main() { + let srv = actix_test::start(|| App::new().service(config)); + + let request = srv.get("/config"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); +} diff --git a/tests/trybuild/test-runtime.rs b/tests/trybuild/test-runtime.rs new file mode 100644 index 0000000..0b901b2 --- /dev/null +++ b/tests/trybuild/test-runtime.rs @@ -0,0 +1,6 @@ +#[actix_web::test] +async fn my_test() { + assert!(async { 1 }.await, 1); +} + +fn main() {}