From 131b6233af575223690d93a34b3cdcd1b574d713 Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:27:48 +0100 Subject: [PATCH] Routing docs: core concepts (#122) --- .../code_samples/guide/routing/Cargo.lock | 857 ++++++++++++++++++ .../code_samples/guide/routing/Cargo.toml | 18 + .../guide/routing/core_concepts/Cargo.toml | 7 + .../routing/core_concepts/src/blueprint.rs | 8 + .../guide/routing/core_concepts/src/lib.rs | 2 + .../guide/routing/core_concepts/src/routes.rs | 5 + .../routing/method_guards/any/Cargo.toml | 7 + .../method_guards/any/src/blueprint.rs | 8 + .../routing/method_guards/any/src/lib.rs | 2 + .../routing/method_guards/any/src/routes.rs | 5 + .../method_guards/multi_method/Cargo.toml | 7 + .../multi_method/src/blueprint.rs | 9 + .../method_guards/multi_method/src/lib.rs | 2 + .../method_guards/multi_method/src/routes.rs | 5 + .../method_guards/single_method/Cargo.toml | 7 + .../single_method/src/blueprint.rs | 8 + .../method_guards/single_method/src/lib.rs | 2 + .../method_guards/single_method/src/routes.rs | 5 + .../catch_all_parameter/Cargo.toml | 7 + .../catch_all_parameter/src/blueprint.rs | 8 + .../catch_all_parameter/src/lib.rs | 2 + .../catch_all_parameter/src/routes.rs | 5 + .../multi_named_parameter/Cargo.toml | 7 + .../multi_named_parameter/src/blueprint.rs | 12 + .../multi_named_parameter/src/lib.rs | 2 + .../multi_named_parameter/src/routes.rs | 5 + .../path_patterns/named_parameter/Cargo.toml | 7 + .../named_parameter/src/blueprint.rs | 8 + .../path_patterns/named_parameter/src/lib.rs | 2 + .../named_parameter/src/routes.rs | 5 + .../routing/path_patterns/static/Cargo.toml | 7 + .../path_patterns/static/src/blueprint.rs | 8 + .../routing/path_patterns/static/src/lib.rs | 2 + .../path_patterns/static/src/routes.rs | 5 + .../request_handlers/error_handler/Cargo.toml | 7 + .../error_handler/src/blueprint.rs | 9 + .../error_handler/src/error_handler.rs | 10 + .../request_handlers/error_handler/src/lib.rs | 3 + .../error_handler/src/routes.rs | 10 + .../routing/request_handlers/intro/Cargo.toml | 7 + .../request_handlers/intro/src/blueprint.rs | 8 + .../routing/request_handlers/intro/src/lib.rs | 2 + .../request_handlers/intro/src/routes.rs | 5 + .../request_handlers/sync_or_async/Cargo.toml | 7 + .../sync_or_async/src/blueprint.rs | 9 + .../request_handlers/sync_or_async/src/lib.rs | 2 + .../sync_or_async/src/routes.rs | 9 + docs/concepts/index.md | 3 - docs/getting_started/.pages | 4 - docs/getting_started/index.md | 2 +- docs/getting_started/learning_paths.md | 2 +- docs/getting_started/quickstart.md | 390 -------- docs/getting_started/quickstart/blueprint.md | 11 + .../quickstart/dependency_injection.md | 98 ++ .../quickstart/error_handling.md | 49 + .../quickstart/going_further.md | 29 + docs/getting_started/quickstart/index.md | 93 ++ docs/getting_started/quickstart/routing.md | 99 ++ docs/getting_started/quickstart/testing.md | 38 + docs/guide/dependency_injection/index.md | 1 + docs/guide/index.md | 16 + docs/guide/request_data/index.md | 1 + docs/guide/routing/index.md | 14 + docs/guide/routing/method_guards.md | 44 + docs/guide/routing/path_patterns.md | 54 ++ docs/guide/routing/request_handlers.md | 122 +++ docs/guides/index.md | 3 - docs/overview/index.md | 39 +- .../src/blueprint/reflection/callable/mod.rs | 1 + libs/pavex_cli/src/main.rs | 13 +- mkdocs.yml | 30 +- 71 files changed, 1862 insertions(+), 438 deletions(-) create mode 100644 doc_examples/code_samples/guide/routing/Cargo.lock create mode 100644 doc_examples/code_samples/guide/routing/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/core_concepts/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/core_concepts/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/core_concepts/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/core_concepts/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/any/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/method_guards/any/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/any/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/any/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/multi_method/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/method_guards/multi_method/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/multi_method/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/multi_method/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/single_method/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/method_guards/single_method/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/single_method/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/method_guards/single_method/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/named_parameter/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/static/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/static/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/static/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/path_patterns/static/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/error_handler/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/error_handler.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/intro/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/intro/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/intro/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/intro/src/routes.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/Cargo.toml create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/blueprint.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/lib.rs create mode 100644 doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/routes.rs delete mode 100644 docs/concepts/index.md delete mode 100644 docs/getting_started/.pages delete mode 100644 docs/getting_started/quickstart.md create mode 100644 docs/getting_started/quickstart/blueprint.md create mode 100644 docs/getting_started/quickstart/dependency_injection.md create mode 100644 docs/getting_started/quickstart/error_handling.md create mode 100644 docs/getting_started/quickstart/going_further.md create mode 100644 docs/getting_started/quickstart/index.md create mode 100644 docs/getting_started/quickstart/routing.md create mode 100644 docs/getting_started/quickstart/testing.md create mode 100644 docs/guide/dependency_injection/index.md create mode 100644 docs/guide/index.md create mode 100644 docs/guide/request_data/index.md create mode 100644 docs/guide/routing/index.md create mode 100644 docs/guide/routing/method_guards.md create mode 100644 docs/guide/routing/path_patterns.md create mode 100644 docs/guide/routing/request_handlers.md delete mode 100644 docs/guides/index.md diff --git a/doc_examples/code_samples/guide/routing/Cargo.lock b/doc_examples/code_samples/guide/routing/Cargo.lock new file mode 100644 index 000000000..3ee11a5fc --- /dev/null +++ b/doc_examples/code_samples/guide/routing/Cargo.lock @@ -0,0 +1,857 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "any" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "catch_all_parameter" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core_concepts" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "error_handler" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f9214f3e703236b221f1a9cd88ec8b4adfa5296de01ab96216361f4692f56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca339002caeb0d159cc6e023dff48e199f081e42fa039895c7c6f38b37f2e9d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "intro" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "git+https://github.com/ibraheemdev/matchit?branch=master#7766d457ee20826497b5061581fa6cef682d05b7" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "multi_method" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "multi_named_parameter" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "named_parameter" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pavex" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "fs-err", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "indexmap", + "matchit", + "mime", + "paste", + "pavex_macros", + "percent-encoding", + "pin-project-lite", + "ron", + "serde", + "serde_html_form", + "serde_json", + "serde_path_to_error", + "smallvec", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "pavex_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_html_form" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224e6a14f315852940f3ec103125aa6482f0e224732ed91ed3330ed633077c34" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "single_method" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_or_async" +version = "0.1.0" +dependencies = [ + "pavex", +] + +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/doc_examples/code_samples/guide/routing/Cargo.toml b/doc_examples/code_samples/guide/routing/Cargo.toml new file mode 100644 index 000000000..a239d0d28 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/Cargo.toml @@ -0,0 +1,18 @@ +[workspace] +members = [ + "core_concepts", + "method_guards/any", + "method_guards/multi_method", + "method_guards/single_method", + "path_patterns/static", + "path_patterns/catch_all_parameter", + "path_patterns/multi_named_parameter", + "path_patterns/named_parameter", + "request_handlers/intro", + "request_handlers/error_handler", + "request_handlers/sync_or_async", +] +resolver = "2" + +[workspace.dependencies] +pavex = { path = "../../../../libs/pavex" } \ No newline at end of file diff --git a/doc_examples/code_samples/guide/routing/core_concepts/Cargo.toml b/doc_examples/code_samples/guide/routing/core_concepts/Cargo.toml new file mode 100644 index 000000000..f7afb1aca --- /dev/null +++ b/doc_examples/code_samples/guide/routing/core_concepts/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "core_concepts" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/core_concepts/src/blueprint.rs b/doc_examples/code_samples/guide/routing/core_concepts/src/blueprint.rs new file mode 100644 index 000000000..4dc005917 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/core_concepts/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/core_concepts/src/lib.rs b/doc_examples/code_samples/guide/routing/core_concepts/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/core_concepts/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/core_concepts/src/routes.rs b/doc_examples/code_samples/guide/routing/core_concepts/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/core_concepts/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/any/Cargo.toml b/doc_examples/code_samples/guide/routing/method_guards/any/Cargo.toml new file mode 100644 index 000000000..7ae5b5fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/any/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "any" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/method_guards/any/src/blueprint.rs b/doc_examples/code_samples/guide/routing/method_guards/any/src/blueprint.rs new file mode 100644 index 000000000..029441fc9 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/any/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::ANY, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(ANY, "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/any/src/lib.rs b/doc_examples/code_samples/guide/routing/method_guards/any/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/any/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/method_guards/any/src/routes.rs b/doc_examples/code_samples/guide/routing/method_guards/any/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/any/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/multi_method/Cargo.toml b/doc_examples/code_samples/guide/routing/method_guards/multi_method/Cargo.toml new file mode 100644 index 000000000..4ff29842c --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/multi_method/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "multi_method" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/blueprint.rs b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/blueprint.rs new file mode 100644 index 000000000..23f81f714 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/blueprint.rs @@ -0,0 +1,9 @@ +use pavex::blueprint::router::{GET, HEAD}; +use pavex::blueprint::Blueprint; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET.or(HEAD), "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/lib.rs b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/routes.rs b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/multi_method/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/single_method/Cargo.toml b/doc_examples/code_samples/guide/routing/method_guards/single_method/Cargo.toml new file mode 100644 index 000000000..19ca6ff09 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/single_method/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "single_method" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/method_guards/single_method/src/blueprint.rs b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/blueprint.rs new file mode 100644 index 000000000..52d976f27 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET /* (1)! */, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/method_guards/single_method/src/lib.rs b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/method_guards/single_method/src/routes.rs b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/method_guards/single_method/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/Cargo.toml b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/Cargo.toml new file mode 100644 index 000000000..abaa3be3f --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "catch_all_parameter" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/blueprint.rs b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/blueprint.rs new file mode 100644 index 000000000..29578462d --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet/*details", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/lib.rs b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/routes.rs b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/Cargo.toml b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/Cargo.toml new file mode 100644 index 000000000..7a0d6aea9 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "multi_named_parameter" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/blueprint.rs b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/blueprint.rs new file mode 100644 index 000000000..544eddfd3 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/blueprint.rs @@ -0,0 +1,12 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route( + GET, + "/greet/:first_name/:last_name", + f!(crate::routes::greet), + ); + bp +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/lib.rs b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/routes.rs b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/Cargo.toml b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/Cargo.toml new file mode 100644 index 000000000..8c9a63263 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "named_parameter" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/blueprint.rs b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/blueprint.rs new file mode 100644 index 000000000..cd83b6516 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet/:name", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/lib.rs b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/routes.rs b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/static/Cargo.toml b/doc_examples/code_samples/guide/routing/path_patterns/static/Cargo.toml new file mode 100644 index 000000000..e88cf95b5 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/static/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "static" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/path_patterns/static/src/blueprint.rs b/doc_examples/code_samples/guide/routing/path_patterns/static/src/blueprint.rs new file mode 100644 index 000000000..4dc005917 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/static/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/path_patterns/static/src/lib.rs b/doc_examples/code_samples/guide/routing/path_patterns/static/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/static/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/path_patterns/static/src/routes.rs b/doc_examples/code_samples/guide/routing/path_patterns/static/src/routes.rs new file mode 100644 index 000000000..ffdce2fc2 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/path_patterns/static/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + todo!() +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/error_handler/Cargo.toml b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/Cargo.toml new file mode 100644 index 000000000..7704bd79e --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "error_handler" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/blueprint.rs b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/blueprint.rs new file mode 100644 index 000000000..2947b126a --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/blueprint.rs @@ -0,0 +1,9 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet", f!(crate::routes::greet)) + .error_handler(f!(crate::error_handler::greet_error_handler)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/error_handler.rs b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/error_handler.rs new file mode 100644 index 000000000..bdb7c7b4f --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/error_handler.rs @@ -0,0 +1,10 @@ +use pavex::response::Response; + +pub fn greet_error_handler(e: &GreetError) -> Response { + match e { + GreetError::InvalidName => Response::bad_request().set_typed_body("Invalid name."), + GreetError::DatabaseError => Response::internal_server_error() + .set_typed_body("Something went wrong, please retry later."), + } + .box_body() +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/lib.rs b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/lib.rs new file mode 100644 index 000000000..b021aecd0 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/lib.rs @@ -0,0 +1,3 @@ +pub mod blueprint; +pub mod error_handler; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/routes.rs b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/routes.rs new file mode 100644 index 000000000..8678951c7 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/routes.rs @@ -0,0 +1,10 @@ +use pavex::http::StatusCode; + +pub fn greet() -> Result { + StatusCode::OK +} + +pub enum GreetError { + DatabaseError, + InvalidName, +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/intro/Cargo.toml b/doc_examples/code_samples/guide/routing/request_handlers/intro/Cargo.toml new file mode 100644 index 000000000..75fb18895 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/intro/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "intro" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/request_handlers/intro/src/blueprint.rs b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/blueprint.rs new file mode 100644 index 000000000..4dc005917 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/blueprint.rs @@ -0,0 +1,8 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/greet", f!(crate::routes::greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/intro/src/lib.rs b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/request_handlers/intro/src/routes.rs b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/routes.rs new file mode 100644 index 000000000..ecfec4ca1 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/intro/src/routes.rs @@ -0,0 +1,5 @@ +use pavex::http::StatusCode; + +pub fn greet() -> StatusCode { + StatusCode::OK +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/Cargo.toml b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/Cargo.toml new file mode 100644 index 000000000..be54be2bf --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sync_or_async" +version = "0.1.0" +edition = "2021" + +[dependencies] +pavex = { workspace = true } diff --git a/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/blueprint.rs b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/blueprint.rs new file mode 100644 index 000000000..e3417f70b --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/blueprint.rs @@ -0,0 +1,9 @@ +use pavex::blueprint::{router::GET, Blueprint}; +use pavex::f; + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.route(GET, "/async_greet", f!(crate::routes::async_greet)); + bp.route(GET, "/sync_greet", f!(crate::routes::sync_greet)); + bp +} diff --git a/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/lib.rs b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/lib.rs new file mode 100644 index 000000000..83a519d05 --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/lib.rs @@ -0,0 +1,2 @@ +pub mod blueprint; +pub mod routes; diff --git a/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/routes.rs b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/routes.rs new file mode 100644 index 000000000..5f6fe4dfa --- /dev/null +++ b/doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/routes.rs @@ -0,0 +1,9 @@ +use pavex::http::StatusCode; + +pub async fn async_greet() -> StatusCode { + StatusCode::OK +} + +pub fn sync_greet() -> StatusCode { + StatusCode::OK +} diff --git a/docs/concepts/index.md b/docs/concepts/index.md deleted file mode 100644 index e102ac3a9..000000000 --- a/docs/concepts/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Concepts - -Yo \ No newline at end of file diff --git a/docs/getting_started/.pages b/docs/getting_started/.pages deleted file mode 100644 index 0b9b53930..000000000 --- a/docs/getting_started/.pages +++ /dev/null @@ -1,4 +0,0 @@ -nav: - - "Installation": index.md - - learning_paths.md - - quickstart.md \ No newline at end of file diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md index 1551e4909..a8a33d728 100644 --- a/docs/getting_started/index.md +++ b/docs/getting_started/index.md @@ -40,7 +40,7 @@ rustup component add --toolchain nightly rust-docs-json All the code you'll be running (in production or otherwise) will be compiled with the stable toolchain. Pavex relies on `nightly` to perform code generation and compile-time reflection—nothing else. -## Pavex +## Pavex CLI Pavex provides a command-line interface to scaffold new projects and work with existing ones. To install it, execute the following command: diff --git a/docs/getting_started/learning_paths.md b/docs/getting_started/learning_paths.md index 8c31994b0..b1feaff75 100644 --- a/docs/getting_started/learning_paths.md +++ b/docs/getting_started/learning_paths.md @@ -27,4 +27,4 @@ Once you feel comfortable enough with the language, you can start learning about ## I know some Rust, but I'm new to Pavex If you're already familiar with Rust, you can start learning about Pavex right away: head over to our -[Quickstart tutorial](quickstart.md) to learn about the core concepts of the framework. \ No newline at end of file +[Quickstart tutorial](quickstart/index.md) to learn about the core concepts of the framework. \ No newline at end of file diff --git a/docs/getting_started/quickstart.md b/docs/getting_started/quickstart.md deleted file mode 100644 index 3b907591e..000000000 --- a/docs/getting_started/quickstart.md +++ /dev/null @@ -1,390 +0,0 @@ -# Quickstart - -!!! note "Estimated time: 20 minutes" - -!!! warning "Prerequisites" - - Make sure you've installed all the [required tools](../getting_started/index.md) before starting - this tutorial. - -## Create a new Pavex project - -The `pavex` CLI provides a `new` subcommand to scaffold a new Pavex project. -Let's use it to create a new project called `demo`: - -```bash -pavex new demo && cd demo -``` - -## Commands - -### Build a Pavex project - -`cargo` is not enough, on its own, to build a Pavex project: -you need to use the [`cargo-px`](https://github.com/LukeMathWalker/cargo-px) subcommand instead(1). -From a usage perspective, it's a **drop-in replacement for `cargo`**: -you can use it to build, test, run, etc. your project just like you would with `cargo` itself. -{ .annotate } - -1. `cargo-px` is a thin wrapper around `cargo` that adds support for more powerful code generation, - overcoming some limitations of `cargo`'s build scripts. - -Let's use it to check that your project compiles successfully: - -```bash -cargo px check # (1)! -``` - -1. `cargo px check` is faster than `cargo px build` because it doesn't produce an executable binary. - It's the quickest way to check that your project compiles while you're working on it. - -If everything went well, try to execute the test suite: - -```bash -cargo px test -``` - -### Run a Pavex project - -Now launch your application: - -```bash -cargo px run -``` - -Once the application is running, you should start seeing JSON logs in your terminal: - -```json -{ - "name": "demo", - "msg": "Starting to listen for incoming requests at 127.0.0.1:8000", - "level": 30, - "target": "api" - // [...] -} -``` - -Leave it running in the background and open a new terminal window. - -### Issue your first request - -Let's issue your first request to a Pavex application! -The template project comes with a `GET /api/ping` endpoint to be used as health check. -Let's hit it: - -```bash -curl -v http://localhost:8000/api/ping # (1)! -``` - -1. We are using curl here, but you can replace it with your favourite HTTP client! - -If all goes according to plan, you'll receive a `200 OK` response with an empty body: - -```text -> GET /api/ping HTTP/1.1 -> Host: localhost:8000 -> User-Agent: [...] -> Accept: */* -> -< HTTP/1.1 200 OK -< content-length: 0 -< date: [...] -``` - -You've just created a new Pavex project, built it, launched it and verified that it accepts requests correctly. -It's a good time to start exploring the codebase! - -## Blueprint - -The core of a Pavex project is its [`Blueprint`][Blueprint]. -It's the type you'll use to define your API: routes, middlewares, error handlers, etc. - -You can find the [`Blueprint`][Blueprint] for the `demo` project in the `demo/src/blueprint.rs` file: - ---8<-- "doc_examples/quickstart/demo-blueprint_definition.snap" - -## Routing - -### Route registration - -All the routes exposed by your API must be registered with its [`Blueprint`][Blueprint]. -In the snippet below you can see the registration of the `GET /api/ping` route, the one you targeted with your `curl` -request. - ---8<-- "doc_examples/quickstart/demo-route_registration.snap" - -It specifies: - -- The HTTP method (`GET`) -- The path (`/api/ping`) -- The fully qualified path to the handler function (`crate::routes::status::ping`), wrapped in a macro (`f!`) - -### Request handlers - -The `ping` function is the handler for the `GET /api/ping` route: - ---8<-- "doc_examples/quickstart/demo-ping_handler.snap" - -It's a public function that returns a [`StatusCode`][StatusCode]. -[`StatusCode`][StatusCode] is a valid response type for a Pavex handler since it implements -the [`IntoResponse`][IntoResponse] trait: -the framework -knows how to convert it into a "full" [`Response`][Response] object. - -### Add a new route - -The `ping` function is fairly boring: it doesn't take any arguments, and it always returns the same response. -Let's spice things up with a new route: `GET /api/greet/:name`. -It takes a dynamic **route parameter** (`name`) and we want it to return a success response with `Hello, {name}` as its -body. - -Create a new module, `greet.rs`, in the `demo/src/routes` folder: - - ---8<-- "doc_examples/quickstart/02-new_submodule.snap" - ---8<-- "doc_examples/quickstart/02-route_def.snap" - -The body of the `greet` handler is stubbed out with `todo!()` for now, but we'll fix that soon enough. -Let's register the new route with the [`Blueprint`][Blueprint] in the meantime: - ---8<-- "doc_examples/quickstart/02-register_new_route.snap" - -1. Dynamic route parameters are prefixed with a colon (`:`). - -### Extract route parameters - -To access the `name` route parameter from your new handler you must use the [`RouteParams`][RouteParams] extractor: - - ---8<-- "doc_examples/quickstart/03-route_def.snap" - -1. The name of the field must match the name of the route parameter as it appears in the path we registered with - the [`Blueprint`][Blueprint]. -2. The [`RouteParams`][RouteParams] extractor is generic over the type of the route parameters. - In this case, we're using the `GreetParams` type we just defined. - -You can now return the expected response from the `greet` handler: - ---8<-- "doc_examples/quickstart/04-route_def.snap" - -1. This is an example of - Rust's [destructuring syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#destructuring-to-break-apart-values). -2. [`Response`][Response] has a convenient constructor for each HTTP status code: [`Response::ok`][Response::ok] starts - building a [`Response`][Response] with - a `200 OK` status code. -3. [`set_typed_body`][set_typed_body] sets the body of the response and automatically infers a suitable value for - the `Content-Type` header - based on the response body type. - -Does it work? Only one way to find out! -Re-launch the application and issue a new request: (1) -{ .annotate } - -1. Remember to use `cargo px run` instead of `cargo run`! - -```bash -curl http://localhost:8000/api/greet/Ursula -``` - -You should see `Hello, Ursula!` in your terminal if everything went well. - -## Dependency injection - -You just added a new input parameter to your `greet` handler and, somehow, the framework was able to provide its value -at runtime without you having to do anything. -How does that work? - -It's all thanks to **dependency injection**. -Pavex automatically injects the expected input parameters when invoking your handler functions as long as -it knows how to construct them. - -### Constructor registration - -Let's zoom in on [`RouteParams`][RouteParams]: how does the framework know how to construct it? -You need to go back to the [`Blueprint`][Blueprint] to find out: - ---8<-- "doc_examples/quickstart/04-register_common_invocation.snap" - -The `register_common_constructors` function takes care of registering constructors for a set of types that -are defined in the `pavex` crate itself and commonly used in Pavex applications. -If you check out its definition, you'll see that it registers a constructor for [`RouteParams`][RouteParams]: - ---8<-- "doc_examples/quickstart/04-route_params_constructor.snap" - -It specifies: - -- The fully qualified path to the constructor method, wrapped in a macro (`f!`) -- The constructor's lifecycle ([`Lifecycle::RequestScoped`](Lifecycle::RequestScoped)): the framework will invoke this - constructor at most once per - request - -### A new extractor: `UserAgent` - -There's no substitute for hands-on experience, so let's design a brand-new constructor for our demo project to -get a better understanding of how they work. -We only want to greet people who include a `User-Agent` header in their request(1). -{ .annotate } - -1. It's an arbitrary requirement, follow along for the sake of the example! - -Let's start by defining a new `UserAgent` type: - ---8<-- "doc_examples/quickstart/05-new_submodule.snap" - ---8<-- "doc_examples/quickstart/05-user_agent.snap" - -### Missing constructor - -What if you tried to inject `UserAgent` into your `greet` handler straight away? Would it work? -Let's find out! - ---8<-- "doc_examples/quickstart/05-inject.snap" - -1. New input parameter! - -If you try to build the project now, you'll get an error from Pavex: - -```ansi-color ---8<-- "doc_examples/quickstart/05-error.snap" -``` - -Pavex cannot do miracles, nor does it want to: it only knows how to construct a type if you tell it how to do so. - -By the way: this is also your first encounter with Pavex's error messages! -We strive to make them as helpful as possible. If you find them confusing, report it as a bug! - -### Add a new constructor - -To inject `UserAgent` into our `greet` handler, you need to define a constructor for it. -Constructors, just like request handlers, can take advantage of dependency injection: they can request input parameters -that will be injected by the framework at runtime. -Since you need to look at headers, ask for [`RequestHead`][RequestHead] as input parameter: the incoming request data, -minus the body. - ---8<-- "doc_examples/quickstart/06-extract.snap" - -Now register the new constructor with the [`Blueprint`][Blueprint]: - ---8<-- "doc_examples/quickstart/06-register.snap" - -[`Lifecycle::RequestScoped`][Lifecycle::RequestScoped] is the right choice for this type: the data in `UserAgent` is -request-specific. -You don't want to share it across requests ([`Lifecycle::Singleton`][Lifecycle::Singleton]) nor do you want to recompute -it multiple times for -the same request ([`Lifecycle::Transient`][Lifecycle::Transient]). - -Make sure that the project compiles successfully now. - -## Error handling - -In `UserAgent::extract` you're only handling the happy path: -the method panics if the `User-Agent` header is not valid UTF-8. -Panicking for bad user input is poor behavior: you should handle the issue gracefully and return an error instead. - -Let's change the signature of `UserAgent::extract` to return a `Result` instead: - ---8<-- "doc_examples/quickstart/07-extract.snap" - -1. `ToStrError` is the error type returned by `to_str` when the header value is not valid UTF-8. - -### All errors must be handled - -If you try to build the project now, you'll get an error from Pavex: - -```ansi-color ---8<-- "doc_examples/quickstart/07-error.snap" -``` - -Pavex is complaining: you can register a fallible constructor, but you must also register an error handler for it. - -### Add an error handler - -An error handler must convert a reference to the error type into a [`Response`][Response] (1). -It decouples the detection of an error from its representation on the wire: a constructor doesn't need to know how the -error will be represented in the response, it just needs to signal that something went wrong. -You can then change the representation of an error on the wire without touching the constructor: you only need to change -the -error handler. -{ .annotate } - -1. Error handlers, just like request handlers and constructors, can take advantage of dependency injection! - You could, for example, change the response representation according to the `Accept` header specified in the request. - -Define a new `invalid_user_agent` function in `demo/src/user_agent.rs`: - ---8<-- "doc_examples/quickstart/08-error_handler.snap" - -Then register the error handler with the [`Blueprint`][Blueprint]: - ---8<-- "doc_examples/quickstart/08-register.snap" - -The application should compile successfully now. - -## Testing - -All your testing, so far, has been manual: you've been launching the application and issuing requests to it with `curl`. -Let's move away from that: it's time to write some automated tests! - -### Black-box testing - -The preferred way to test a Pavex application is to treat it as a black box: you should only test the application -through its HTTP interface. This is the most realistic way to test your application: it's how your users will -interact with it, after all. - -The template project includes a reference example for the `/api/ping` endpoint: - ---8<-- "doc_examples/quickstart/09-ping_test.snap" - -1. `TestApi` is a helper struct that provides a convenient interface to interact with the application. - It's defined in `demo_server/tests/helpers.rs`. -2. `TestApi::spawn` starts a new instance of the application in the background. -3. `TestApi::get_ping` issues an actual `GET /api/ping` request to the application. - -### Add a new integration test - -Let's write a new integration test to verify the behaviour on the happy path for `GET /api/greet/:name`: - ---8<-- "doc_examples/quickstart/09-new_test_module.snap" - ---8<-- "doc_examples/quickstart/09-greet_test.snap" - -It follows the same pattern as the `ping` test: it spawns a new instance of the application, issues a request to it -and verifies that the response is correct. -Let's complement it with a test for the unhappy path as well: requests with a malformed `User-Agent` header should be -rejected. - ---8<-- "doc_examples/quickstart/10-greet_test.snap" - -`cargo px test` should report three passing tests now. As a bonus exercise, try to add a test for the case where the -`User-Agent` header is missing. - -## Going further - -Your first (guided) tour of Pavex ends here: you've touched the key concepts of the framework and got some hands-on -experience with a basic application. -From here onwards, you are free to carve out your own learning path: you can explore the rest of the documentation -to learn more about the framework, or you can start hacking on your own project, consulting the documentation on a -need-to-know basis. - -[Blueprint]: ../api_reference/pavex/blueprint/struct.Blueprint.html - -[StatusCode]: ../api_reference/pavex/http/struct.StatusCode.html - -[Response]: ../api_reference/pavex/response/struct.Response.html - -[IntoResponse]: ../api_reference/pavex/response/trait.IntoResponse.html - -[RouteParams]: ../api_reference/pavex/request/route/struct.RouteParams.html - -[Response::ok]: ../api_reference/pavex/response/struct.Response.html#method.ok - -[set_typed_body]: ../api_reference/pavex/response/struct.Response.html#method.set_typed_body - -[Lifecycle::Singleton]: ../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Singleton - -[Lifecycle::RequestScoped]: ../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.RequestScoped - -[Lifecycle::Transient]: ../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Transient - -[RequestHead]: ../api_reference/pavex/request/struct.RequestHead.html \ No newline at end of file diff --git a/docs/getting_started/quickstart/blueprint.md b/docs/getting_started/quickstart/blueprint.md new file mode 100644 index 000000000..bd6f15ae3 --- /dev/null +++ b/docs/getting_started/quickstart/blueprint.md @@ -0,0 +1,11 @@ +# Blueprint + +The core of a Pavex project is its [`Blueprint`][Blueprint]. +It's the type you'll use to define your API: routes, middlewares, error handlers, etc. + +You can find the [`Blueprint`][Blueprint] for the `demo` project in the `demo/src/blueprint.rs` file: + +--8<-- "doc_examples/quickstart/demo-blueprint_definition.snap" + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html + diff --git a/docs/getting_started/quickstart/dependency_injection.md b/docs/getting_started/quickstart/dependency_injection.md new file mode 100644 index 000000000..ed4256805 --- /dev/null +++ b/docs/getting_started/quickstart/dependency_injection.md @@ -0,0 +1,98 @@ +# Dependency injection + +You just added a new input parameter to your `greet` handler and, somehow, the framework was able to provide its value +at runtime without you having to do anything. +How does that work? + +It's all thanks to **dependency injection**. +Pavex automatically injects the expected input parameters when invoking your handler functions as long as +it knows how to construct them. + +## Constructor registration + +Let's zoom in on [`RouteParams`][RouteParams]: how does the framework know how to construct it? +You need to go back to the [`Blueprint`][Blueprint] to find out: + +--8<-- "doc_examples/quickstart/04-register_common_invocation.snap" + +The `register_common_constructors` function takes care of registering constructors for a set of types that +are defined in the `pavex` crate itself and commonly used in Pavex applications. +If you check out its definition, you'll see that it registers a constructor for [`RouteParams`][RouteParams]: + +--8<-- "doc_examples/quickstart/04-route_params_constructor.snap" + +It specifies: + +- The fully qualified path to the constructor method, wrapped in a macro (`f!`) +- The constructor's lifecycle ([`Lifecycle::RequestScoped`](Lifecycle::RequestScoped)): the framework will invoke this + constructor at most once per + request + +## A new extractor: `UserAgent` + +There's no substitute for hands-on experience, so let's design a brand-new constructor for our demo project to +get a better understanding of how they work. +We only want to greet people who include a `User-Agent` header in their request(1). +{ .annotate } + +1. It's an arbitrary requirement, follow along for the sake of the example! + +Let's start by defining a new `UserAgent` type: + +--8<-- "doc_examples/quickstart/05-new_submodule.snap" + +--8<-- "doc_examples/quickstart/05-user_agent.snap" + +## Missing constructor + +What if you tried to inject `UserAgent` into your `greet` handler straight away? Would it work? +Let's find out! + +--8<-- "doc_examples/quickstart/05-inject.snap" + +1. New input parameter! + +If you try to build the project now, you'll get an error from Pavex: + +```ansi-color +--8<-- "doc_examples/quickstart/05-error.snap" +``` + +Pavex cannot do miracles, nor does it want to: it only knows how to construct a type if you tell it how to do so. + +By the way: this is also your first encounter with Pavex's error messages! +We strive to make them as helpful as possible. If you find them confusing, report it as a bug! + +## Add a new constructor + +To inject `UserAgent` into our `greet` handler, you need to define a constructor for it. +Constructors, just like request handlers, can take advantage of dependency injection: they can request input parameters +that will be injected by the framework at runtime. +Since you need to look at headers, ask for [`RequestHead`][RequestHead] as input parameter: the incoming request data, +minus the body. + +--8<-- "doc_examples/quickstart/06-extract.snap" + +Now register the new constructor with the [`Blueprint`][Blueprint]: + +--8<-- "doc_examples/quickstart/06-register.snap" + +[`Lifecycle::RequestScoped`][Lifecycle::RequestScoped] is the right choice for this type: the data in `UserAgent` is +request-specific. +You don't want to share it across requests ([`Lifecycle::Singleton`][Lifecycle::Singleton]) nor do you want to recompute +it multiple times for +the same request ([`Lifecycle::Transient`][Lifecycle::Transient]). + +Make sure that the project compiles successfully now. + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html + +[RouteParams]: ../../api_reference/pavex/request/route/struct.RouteParams.html + +[Lifecycle::Singleton]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Singleton + +[Lifecycle::RequestScoped]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.RequestScoped + +[Lifecycle::Transient]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Transient + +[RequestHead]: ../../api_reference/pavex/request/struct.RequestHead.html diff --git a/docs/getting_started/quickstart/error_handling.md b/docs/getting_started/quickstart/error_handling.md new file mode 100644 index 000000000..1ab291ba2 --- /dev/null +++ b/docs/getting_started/quickstart/error_handling.md @@ -0,0 +1,49 @@ +# Error handling + +In `UserAgent::extract` you're only handling the happy path: +the method panics if the `User-Agent` header is not valid UTF-8. +Panicking for bad user input is poor behavior: you should handle the issue gracefully and return an error instead. + +Let's change the signature of `UserAgent::extract` to return a `Result` instead: + +--8<-- "doc_examples/quickstart/07-extract.snap" + +1. `ToStrError` is the error type returned by `to_str` when the header value is not valid UTF-8. + +## All errors must be handled + +If you try to build the project now, you'll get an error from Pavex: + +```ansi-color +--8<-- "doc_examples/quickstart/07-error.snap" +``` + +Pavex is complaining: you can register a fallible constructor, but you must also register an error handler for it. + +## Add an error handler + +An error handler must convert a reference to the error type into a [`Response`][Response] (1). +It decouples the detection of an error from its representation on the wire: a constructor doesn't need to know how the +error will be represented in the response, it just needs to signal that something went wrong. +You can then change the representation of an error on the wire without touching the constructor: you only need to change +the +error handler. +{ .annotate } + +1. Error handlers, just like request handlers and constructors, can take advantage of dependency injection! + You could, for example, change the response representation according to the `Accept` header specified in the request. + +Define a new `invalid_user_agent` function in `demo/src/user_agent.rs`: + +--8<-- "doc_examples/quickstart/08-error_handler.snap" + +Then register the error handler with the [`Blueprint`][Blueprint]: + +--8<-- "doc_examples/quickstart/08-register.snap" + +The application should compile successfully now. + + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html + +[Response]: ../../api_reference/pavex/response/struct.Response.html diff --git a/docs/getting_started/quickstart/going_further.md b/docs/getting_started/quickstart/going_further.md new file mode 100644 index 000000000..0f0b17f36 --- /dev/null +++ b/docs/getting_started/quickstart/going_further.md @@ -0,0 +1,29 @@ +# Going further + +Your first (guided) tour of Pavex ends here: you've touched the key concepts of the framework and got some hands-on +experience with a basic application. +From here onwards, you are free to carve out your own learning path: you can explore the rest of the documentation +to learn more about the framework, or you can start hacking on your own project, consulting the documentation on a +need-to-know basis. + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html + +[StatusCode]: ../../api_reference/pavex/http/struct.StatusCode.html + +[Response]: ../../api_reference/pavex/response/struct.Response.html + +[IntoResponse]: ../../api_reference/pavex/response/trait.IntoResponse.html + +[RouteParams]: ../../api_reference/pavex/request/route/struct.RouteParams.html + +[Response::ok]: ../../api_reference/pavex/response/struct.Response.html#method.ok + +[set_typed_body]: ../../api_reference/pavex/response/struct.Response.html#method.set_typed_body + +[Lifecycle::Singleton]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Singleton + +[Lifecycle::RequestScoped]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.RequestScoped + +[Lifecycle::Transient]: ../../api_reference/pavex/blueprint/constructor/enum.Lifecycle.html#variant.Transient + +[RequestHead]: ../../api_reference/pavex/request/struct.RequestHead.html diff --git a/docs/getting_started/quickstart/index.md b/docs/getting_started/quickstart/index.md new file mode 100644 index 000000000..3b3cb6008 --- /dev/null +++ b/docs/getting_started/quickstart/index.md @@ -0,0 +1,93 @@ +# Project setup + +!!! warning "Prerequisites" + + Make sure you've installed all the [required tools](../index.md) before starting + this tutorial. + +## Create a new Pavex project + +The `pavex` CLI provides a `new` subcommand to scaffold a new Pavex project. +Let's use it to create a new project called `demo`: + +```bash +pavex new demo && cd demo +``` + +## Commands + +### Build a Pavex project + +`cargo` is not enough, on its own, to build a Pavex project: +you need to use the [`cargo-px`](https://github.com/LukeMathWalker/cargo-px) subcommand instead(1). +From a usage perspective, it's a **drop-in replacement for `cargo`**: +you can use it to build, test, run, etc. your project just like you would with `cargo` itself. +{ .annotate } + +1. `cargo-px` is a thin wrapper around `cargo` that adds support for more powerful code generation, + overcoming some limitations of `cargo`'s build scripts. + +Let's use it to check that your project compiles successfully: + +```bash +cargo px check # (1)! +``` + +1. `cargo px check` is faster than `cargo px build` because it doesn't produce an executable binary. + It's the quickest way to check that your project compiles while you're working on it. + +If everything went well, try to execute the test suite: + +```bash +cargo px test +``` + +### Run a Pavex project + +Now launch your application: + +```bash +cargo px run +``` + +Once the application is running, you should start seeing JSON logs in your terminal: + +```json +{ + "name": "demo", + "msg": "Starting to listen for incoming requests at 127.0.0.1:8000", + "level": 30, + "target": "api" + // [...] +} +``` + +Leave it running in the background and open a new terminal window. + +### Issue your first request + +Let's issue your first request to a Pavex application! +The template project comes with a `GET /api/ping` endpoint to be used as health check. +Let's hit it: + +```bash +curl -v http://localhost:8000/api/ping # (1)! +``` + +1. We are using curl here, but you can replace it with your favourite HTTP client! + +If all goes according to plan, you'll receive a `200 OK` response with an empty body: + +```text +> GET /api/ping HTTP/1.1 +> Host: localhost:8000 +> User-Agent: [...] +> Accept: */* +> +< HTTP/1.1 200 OK +< content-length: 0 +< date: [...] +``` + +You've just created a new Pavex project, built it, launched it and verified that it accepts requests correctly. +It's a good time to start exploring the codebase! diff --git a/docs/getting_started/quickstart/routing.md b/docs/getting_started/quickstart/routing.md new file mode 100644 index 000000000..7ec645710 --- /dev/null +++ b/docs/getting_started/quickstart/routing.md @@ -0,0 +1,99 @@ +# Routing + +## Route registration + +All the routes exposed by your API must be registered with its [`Blueprint`][Blueprint]. +In the snippet below you can see the registration of the `GET /api/ping` route, the one you targeted with your `curl` +request. + +--8<-- "doc_examples/quickstart/demo-route_registration.snap" + +It specifies: + +- The HTTP method (`GET`) +- The path (`/api/ping`) +- The fully qualified path to the handler function (`crate::routes::status::ping`), wrapped in a macro (`f!`) + +## Request handlers + +The `ping` function is the handler for the `GET /api/ping` route: + +--8<-- "doc_examples/quickstart/demo-ping_handler.snap" + +It's a public function that returns a [`StatusCode`][StatusCode]. +[`StatusCode`][StatusCode] is a valid response type for a Pavex handler since it implements +the [`IntoResponse`][IntoResponse] trait: +the framework +knows how to convert it into a "full" [`Response`][Response] object. + +## Add a new route + +The `ping` function is fairly boring: it doesn't take any arguments, and it always returns the same response. +Let's spice things up with a new route: `GET /api/greet/:name`. +It takes a dynamic **route parameter** (`name`) and we want it to return a success response with `Hello, {name}` as its +body. + +Create a new module, `greet.rs`, in the `demo/src/routes` folder: + + +--8<-- "doc_examples/quickstart/02-new_submodule.snap" + +--8<-- "doc_examples/quickstart/02-route_def.snap" + +The body of the `greet` handler is stubbed out with `todo!()` for now, but we'll fix that soon enough. +Let's register the new route with the [`Blueprint`][Blueprint] in the meantime: + +--8<-- "doc_examples/quickstart/02-register_new_route.snap" + +1. Dynamic route parameters are prefixed with a colon (`:`). + +## Extract route parameters + +To access the `name` route parameter from your new handler you must use the [`RouteParams`][RouteParams] extractor: + + +--8<-- "doc_examples/quickstart/03-route_def.snap" + +1. The name of the field must match the name of the route parameter as it appears in the path we registered with + the [`Blueprint`][Blueprint]. +2. The [`RouteParams`][RouteParams] extractor is generic over the type of the route parameters. + In this case, we're using the `GreetParams` type we just defined. + +You can now return the expected response from the `greet` handler: + +--8<-- "doc_examples/quickstart/04-route_def.snap" + +1. This is an example of + Rust's [destructuring syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#destructuring-to-break-apart-values). +2. [`Response`][Response] has a convenient constructor for each HTTP status code: [`Response::ok`][Response::ok] starts + building a [`Response`][Response] with + a `200 OK` status code. +3. [`set_typed_body`][set_typed_body] sets the body of the response and automatically infers a suitable value for + the `Content-Type` header + based on the response body type. + +Does it work? Only one way to find out! +Re-launch the application and issue a new request: (1) +{ .annotate } + +1. Remember to use `cargo px run` instead of `cargo run`! + +```bash +curl http://localhost:8000/api/greet/Ursula +``` + +You should see `Hello, Ursula!` in your terminal if everything went well. + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html + +[StatusCode]: ../../api_reference/pavex/http/struct.StatusCode.html + +[Response]: ../../api_reference/pavex/response/struct.Response.html + +[IntoResponse]: ../../api_reference/pavex/response/trait.IntoResponse.html + +[RouteParams]: ../../api_reference/pavex/request/route/struct.RouteParams.html + +[Response::ok]: ../../api_reference/pavex/response/struct.Response.html#method.ok + +[set_typed_body]: ../../api_reference/pavex/response/struct.Response.html#method.set_typed_body diff --git a/docs/getting_started/quickstart/testing.md b/docs/getting_started/quickstart/testing.md new file mode 100644 index 000000000..81191b8f1 --- /dev/null +++ b/docs/getting_started/quickstart/testing.md @@ -0,0 +1,38 @@ +# Testing + +All your testing, so far, has been manual: you've been launching the application and issuing requests to it with `curl`. +Let's move away from that: it's time to write some automated tests! + +## Black-box testing + +The preferred way to test a Pavex application is to treat it as a black box: you should only test the application +through its HTTP interface. This is the most realistic way to test your application: it's how your users will +interact with it, after all. + +The template project includes a reference example for the `/api/ping` endpoint: + +--8<-- "doc_examples/quickstart/09-ping_test.snap" + +1. `TestApi` is a helper struct that provides a convenient interface to interact with the application. + It's defined in `demo_server/tests/helpers.rs`. +2. `TestApi::spawn` starts a new instance of the application in the background. +3. `TestApi::get_ping` issues an actual `GET /api/ping` request to the application. + +## Add a new integration test + +Let's write a new integration test to verify the behaviour on the happy path for `GET /api/greet/:name`: + +--8<-- "doc_examples/quickstart/09-new_test_module.snap" + +--8<-- "doc_examples/quickstart/09-greet_test.snap" + +It follows the same pattern as the `ping` test: it spawns a new instance of the application, issues a request to it +and verifies that the response is correct. +Let's complement it with a test for the unhappy path as well: requests with a malformed `User-Agent` header should be +rejected. + +--8<-- "doc_examples/quickstart/10-greet_test.snap" + +`cargo px test` should report three passing tests now. As a bonus exercise, try to add a test for the case where the +`User-Agent` header is missing. + diff --git a/docs/guide/dependency_injection/index.md b/docs/guide/dependency_injection/index.md new file mode 100644 index 000000000..4bba659eb --- /dev/null +++ b/docs/guide/dependency_injection/index.md @@ -0,0 +1 @@ +# Overview \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md new file mode 100644 index 000000000..66c0bcba6 --- /dev/null +++ b/docs/guide/index.md @@ -0,0 +1,16 @@ +# Overview + +This is the official guide for Pavex. +It is organised by topic. Each section focuses on one aspect of the framework +(e.g. routing, middlewares, etc.), with examples and code snippets. +It's the **best place to build a mental model of the framework** and learn how to wield it effectively. + +No time to waste: use the sidebar to jump to whatever topic you're interested in! +If you don't know where to start, head over to [Routing](routing/index.md) and go from there. + +## Other resources + +If you've never used Pavex before, +consider going through the ["Getting started"](../getting_started/index.md) section first. +If you're looking for the nitty-gritty details of all the types and functions exposed by the framework, +check out the ["API Reference"](../api_reference/pavex/index.html) section instead. diff --git a/docs/guide/request_data/index.md b/docs/guide/request_data/index.md new file mode 100644 index 000000000..4bba659eb --- /dev/null +++ b/docs/guide/request_data/index.md @@ -0,0 +1 @@ +# Overview \ No newline at end of file diff --git a/docs/guide/routing/index.md b/docs/guide/routing/index.md new file mode 100644 index 000000000..cf1ef3b48 --- /dev/null +++ b/docs/guide/routing/index.md @@ -0,0 +1,14 @@ +# Core concepts + +All **routes** in Pavex must be registered with the application [`Blueprint`][Blueprint] via +its [`route`][Blueprint::route] method: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/core_concepts/src/blueprint.rs" +``` + +[`Blueprint::route`][Blueprint::route] expects three arguments: a [**method guard**](method_guards.md), a [**path pattern**](path_patterns.md) and a [**request +handler**](request_handlers.md). + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html +[Blueprint::route]: ../../api_reference/pavex/blueprint/struct.Blueprint.html#method.route diff --git a/docs/guide/routing/method_guards.md b/docs/guide/routing/method_guards.md new file mode 100644 index 000000000..e74e1012a --- /dev/null +++ b/docs/guide/routing/method_guards.md @@ -0,0 +1,44 @@ +# Method guards + +A **method guard** determines which HTTP methods are allowed for a given route. +They are modelled by the [`MethodGuard`][MethodGuard] type. + +## Single-method guards + +The simplest case is a guard that allows a single HTTP method: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/method_guards/single_method/src/blueprint.rs" +``` + +This is by far the most common case and Pavex provides short-hands for it: in the +[`pavex::blueprint::router`][pavex::blueprint::router#constants] module there is +a pre-built guard for each well-known HTTP method (e.g. `GET` in the example above). + +## Multi-method guards + +You can build a guard that accepts multiple HTTP methods by combining single-method guards +with the [`or`][or] method: + +```rust hl_lines="7" +--8<-- "doc_examples/code_samples/guide/routing/method_guards/multi_method/src/blueprint.rs" +``` + +## Ignoring the method + +If you don't care about the HTTP method of the incoming request, use the [`ANY`][ANY] method guard: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/method_guards/any/src/blueprint.rs" +``` + +[`ANY`][ANY] matches all well-known HTTP methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, `CONNECT` and +`TRACE`. +It won't match, however, custom HTTP methods (e.g. `FOO`). +If you truly want to match _any_ HTTP method, use [`ANY_WITH_EXTENSIONS`][ANY_WITH_EXTENSIONS] instead. + +[MethodGuard]: ../../api_reference/pavex/blueprint/router/struct.MethodGuard.html +[pavex::blueprint::router#constants]: ../../api_reference/pavex/blueprint/router/index.html#constants +[or]: ../../api_reference/pavex/blueprint/router/struct.MethodGuard.html#method.or +[ANY]: ../../api_reference/pavex/blueprint/router/constant.ANY.html +[ANY_WITH_EXTENSIONS]: ../../api_reference/pavex/blueprint/router/constant.ANY_WITH_EXTENSIONS.html diff --git a/docs/guide/routing/path_patterns.md b/docs/guide/routing/path_patterns.md new file mode 100644 index 000000000..6a148fe3f --- /dev/null +++ b/docs/guide/routing/path_patterns.md @@ -0,0 +1,54 @@ +# Path patterns + +A **path pattern** is a string that determines which requests are matched by a given route based on their path. + +## Static paths + +The simplest case is a static path, a path pattern that matches a single, fixed path: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/path_patterns/static/src/blueprint.rs" +``` + +It will only match requests with a path that is **exactly equal** to `/greet`. + +## Route parameters + +Static paths are fairly limited. The real power of path patterns comes from their ability to match **dynamic paths**: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/path_patterns/named_parameter/src/blueprint.rs" +``` + +The `:name` segment is a **route parameter**. +It matches everything after `/greet/`, up to the next `/` or the end of the path. +It matches, for example, `/greet/Ursula` and `/greet/John`. It won't match `/greet/` though! + +You can have multiple route parameters in a single path pattern, as long as they are separated by a static segment: + +```rust hl_lines="8" +--8<-- "doc_examples/code_samples/guide/routing/path_patterns/multi_named_parameter/src/blueprint.rs" +``` + +## Accessing route parameters + +Route parameters are not discarded after a request has been routed. +You can access their value from your request handler or from a middleware using the [`RouteParams`][RouteParams] extractor. + +Check out the API reference for [`RouteParams`][RouteParams] for more details. + +## Catch-all parameters + +Route parameters prefixed with a `:` only match a single path segment—they stop at the next `/` or at the end of the path. +You can use the `*` character to craft a **catch-all** route parameter. It matches the rest of the path, regardless of its contents: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/path_patterns/catch_all_parameter/src/blueprint.rs" +``` + +`*details` matches everything after `/greet/`, even if it contains `/` characters. +`/greet/*details` matches, for example, `/greet/Ursula` and `/greet/John`, but it also matches `/greet/Ursula/Smith` and `/greet/John/Doe`. + +To avoid ambiguity, you can have **at most one catch-all parameter per path pattern**. + +[RouteParams]: ../../api_reference/pavex/request/route/struct.RouteParams.html diff --git a/docs/guide/routing/request_handlers.md b/docs/guide/routing/request_handlers.md new file mode 100644 index 000000000..212824c40 --- /dev/null +++ b/docs/guide/routing/request_handlers.md @@ -0,0 +1,122 @@ +# Request handlers + +A **request handler** is invoked when a request matches on the associated [method guard](method_guards.md) and +[path pattern](path_patterns.md). +The request handler is in charge of building the response that will be sent back to the client. + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/intro/src/routes.rs" +``` + +## Registration + +When registering a route, you must provide the **fully qualified path** to the request handler: + +```rust hl_lines="6" +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/intro/src/blueprint.rs" +``` + +The path must be wrapped in the [`f!` macro][f!]. + +## `IntoResponse` + +A request handler must return a type that can be converted into a [`Response`][Response] via the +[`IntoResponse`][IntoResponse] trait. +You only have three first-party types to choose from: + +- [`StatusCode`][StatusCode] +- [`ResponseHead`][ResponseHead] +- [`Response`][Response] + +This is an explicit design choice. Pavex implements the [`IntoResponse`][IntoResponse] trait exclusively for types +that don't require the framework to _assume_ or _infer_ a suitable status code. + +If you want to return a custom type from your request handler, you must implement [`IntoResponse`][IntoResponse] for it. + +## Dependency injection + +Request handlers are expected to take into account the incoming request when building the response. How does that +work in Pavex? How do you **extract** data from the request? + +You must take advantage of **dependency injection**. + +You must specificy the dependencies of your request handler as **input parameters** in its function signature. +Those inputs are going to be built and injected by the framework, according to the **constructors** you have registered. + +Check out the [dependency injection guide](../dependency_injection/index.md) for more details +on how the process works. +Check out the [request data guide](../request_data/index.md) for an overview of the data you can extract from the request +using Pavex's first-party extractors. + +## Request handlers can fail + +Your request handlers can be fallible, i.e. they can return a [`Result`][Result]. + +```rust +pub async fn greet(/* ... */) -> Result { + // ... +} +``` + +If they do, you must specify an **error handler** when registering the route: + +```rust hl_lines="7" +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/blueprint.rs" +``` + +The error handler is in charge of building a response from the error returned by the request handler. Just like +a request handler: + +- It can be synchronous or asynchronous. +- It can take advantage of dependency injection. +- It must return a type that implements [`IntoResponse`][IntoResponse]. + +In addition, it must take a reference to the error type as one of its input parameters: + +```rust +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/error_handler/src/error_handler.rs" +``` + +## Sync or async? + +Request handlers can either be synchronous or asynchronous: + +```rust +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/routes.rs" +``` + +There is no difference when registering the route with the [`Blueprint`][Blueprint]: + +```rust +--8<-- "doc_examples/code_samples/guide/routing/request_handlers/sync_or_async/src/blueprint.rs" +``` + +Be careful with synchronous handlers: they **block the thread** they're running on until they return. +That's not a concern if you are performing an operation that's **guaranteed** to be fast +(e.g. building a response from static data). +It becomes a problem if you're doing work that's **potentially** slow. +There are two types of work that can be slow: + +- I/O operations (e.g. reading from a file, querying a database, etc.) +- CPU-intensive operations (e.g. computing a password hash, parsing a large file, etc.) + +As a rule of thumb: + +| I/O | CPU-intensive | Handler type | Notes | +| --- | --------------|--------------|------------------------------------------------------------------------------------------------------------------------------------| +| Yes | No | Async | Use async libraries for the I/O portion. If the I/O interface is synchronous, use [`tokio::task::spawn_blocking`][spawn_blocking]. | +| No | Yes | Async | Use [`tokio::task::spawn_blocking`][spawn_blocking] for the CPU-intensive portion. | +| Yes | Yes | Async | See above. | +| No | No | Sync | You can also make it asynchronous, it doesn't matter. | + +If you want to learn more about what "blocking" means in async Rust, check out [Alice Rhyl's excellent article](https://ryhl.io/blog/async-what-is-blocking/). + +[Blueprint]: ../../api_reference/pavex/blueprint/struct.Blueprint.html +[Blueprint::route]: ../../api_reference/pavex/blueprint/struct.Blueprint.html#method.route +[IntoResponse]: ../../api_reference/pavex/response/trait.IntoResponse.html +[StatusCode]: ../../api_reference/pavex/http/struct.StatusCode.html +[Response]: ../../api_reference/pavex/response/struct.Response.html +[ResponseHead]: ../../api_reference/pavex/response/struct.ResponseHead.html +[spawn_blocking]: https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html +[f!]: ../../api_reference/pavex/macro.f.html +[Result]: https://doc.rust-lang.org/std/result/index.html diff --git a/docs/guides/index.md b/docs/guides/index.md deleted file mode 100644 index 11c5dd94c..000000000 --- a/docs/guides/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Guides - -Yo \ No newline at end of file diff --git a/docs/overview/index.md b/docs/overview/index.md index fcadca040..f62bf9d8e 100644 --- a/docs/overview/index.md +++ b/docs/overview/index.md @@ -1,7 +1,3 @@ ---- -title: Welcome ---- - # Welcome Hey there! @@ -10,35 +6,24 @@ This is the documentation for **Pavex**, a framework for building APIs in **Rust ## Getting Started If you're new to Pavex, head over to the ["Getting started"](../getting_started/index.md) section. -It'll walk you through the process of setting up a new Pavex project from scratch: -you'll learn how to install the required tools, how to create a new project, and how to run it. +It'll walk you through the process of setting up a new Pavex project from scratch. +You'll learn how to install the required tools, how to create a new project, and how to run it. If you're not sure whether Pavex is the right choice for your project, -check out the ["Why Pavex?"](why_pavex.md) section: it'll give you a high-level overview of the framework's +check out the ["Why Pavex?"](why_pavex.md) section for a high-level overview of the framework's design goals and features. -## Going Further - -### Guides +## Going Deeper Are you already hacking on a Pavex project? -The ["Guides"](../guides/index.md) section contains a collection of how-to guides to help you with common tasks -(e.g. integration testing, configuration, etc.). - -### Concepts - -Guides are task-oriented: their objective is to help you achieve a specific goal, not to teach you a concept. -They're a pragmatic way to get things done, but, over time, you might want to deepen your understanding of the -framework. -That's where the ["Concepts"](../concepts/index.md) section comes in: it contains a collection of articles that explain -the core concepts of the framework in detail (e.g. dependency injection, project structure, etc.). +Get familiar with the ["Guide"](../guide/index.md) and the ["API Reference"](../api_reference/pavex/index.html) sections! -### API Reference +The ["Guide"](../guide/index.md) is organised by topic. Each section focuses on one aspect of the framework +(e.g. routing, middlewares, etc.), with examples and code snippets. +It's the best place to build a mental model of the framework and learn how to wield it effectively. -Last but not least, the ["API Reference"](../api_reference/pavex/index.html) section contains the auto-generated -documentation -for all the first-party crates in the Pavex ecosystem. -It'll be your go-to place for all things API-related: you'll find detailed documentation for all the types and functions -exposed by the framework. -It's the same documentation shown in your IDE when you hover over a type or function. +The ["API Reference"](../api_reference/pavex/index.html), instead, contains the auto-generated +documentation for all the first-party crates in the Pavex ecosystem. +It's your go-to place for the nitty-gritty details of all the types and functions +exposed by the framework. It's the same documentation shown in your IDE when you hover over a type or function. diff --git a/libs/pavex/src/blueprint/reflection/callable/mod.rs b/libs/pavex/src/blueprint/reflection/callable/mod.rs index f88d179e3..baebb6d27 100644 --- a/libs/pavex/src/blueprint/reflection/callable/mod.rs +++ b/libs/pavex/src/blueprint/reflection/callable/mod.rs @@ -16,6 +16,7 @@ mod identifiers; // compile if the callable is generic, because the compiler would // demand to know the type of each generic parameter without a default. #[macro_export] +/// A macro to convert a fully-qualified path into a [`RawCallable`]. macro_rules! f { ($p:expr) => {{ #[cfg(pavex_ide_hint)] diff --git a/libs/pavex_cli/src/main.rs b/libs/pavex_cli/src/main.rs index 03f1734c6..226cc847e 100644 --- a/libs/pavex_cli/src/main.rs +++ b/libs/pavex_cli/src/main.rs @@ -7,15 +7,14 @@ use anyhow::Context; use cargo_generate::{GenerateArgs, TemplatePath}; use clap::{Parser, Subcommand}; use owo_colors::OwoColorize; +use pavex::blueprint::Blueprint; +use pavexc::App; use supports_color::Stream; use tracing_chrome::{ChromeLayerBuilder, FlushGuard}; +use tracing_subscriber::EnvFilter; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::EnvFilter; - -use pavex::blueprint::Blueprint; -use pavexc::App; #[derive(Parser)] #[clap(author, version = VERSION, about, long_about = None)] @@ -139,7 +138,7 @@ fn main() -> Result> { } Box::new(handler.build()) })) - .unwrap(); + .unwrap(); let _guard = if cli.debug { Some(init_telemetry()) } else { @@ -221,11 +220,11 @@ fn scaffold_project(path: PathBuf) -> Result