From 84131e0bc939e489f670611cf6c55a761a5c77da Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:19:59 +0000 Subject: [PATCH] Warn if a constructor is unused (#187) --- examples/realworld/Cargo.lock | 36 +- examples/realworld/api_server_sdk/Cargo.toml | 4 +- .../realworld/api_server_sdk/blueprint.ron | 109 ++- examples/realworld/api_server_sdk/src/lib.rs | 114 +-- .../realworld/conduit_core/src/blueprint.rs | 44 +- examples/skeleton/Cargo.lock | 907 ------------------ examples/skeleton/Cargo.toml | 3 - examples/skeleton/README.md | 26 - examples/skeleton/app_blueprint/Cargo.toml | 14 - examples/skeleton/app_blueprint/src/bin.rs | 14 - examples/skeleton/app_blueprint/src/lib.rs | 45 - examples/skeleton/app_server_sdk/Cargo.toml | 16 - .../skeleton/app_server_sdk/blueprint.ron | 75 -- examples/skeleton/blueprint.ron | 80 -- libs/pavex/src/blueprint/blueprint.rs | 2 + .../src/blueprint/constructor/registered.rs | 23 +- .../src/blueprint/constructor/unregistered.rs | 22 +- libs/pavex/src/blueprint/conversions.rs | 7 + libs/pavex/src/blueprint/linter.rs | 14 + libs/pavex/src/blueprint/mod.rs | 1 + libs/pavex/src/kit/api.rs | 11 +- libs/pavex_bp_schema/src/lib.rs | 24 +- .../expectations/app.rs | 54 +- .../expectations/diagnostics.dot | 3 + .../expectations/stderr.txt | 14 + .../lib.rs | 16 + .../test_config.toml | 7 + .../expectations/app.rs | 62 ++ .../expectations/diagnostics.dot | 3 + .../lib.rs | 17 + .../test_config.toml | 7 + libs/pavex_test_runner/src/lib.rs | 17 + .../analyses/call_graph/application_state.rs | 1 + .../src/compiler/analyses/components.rs | 97 +- libs/pavexc/src/compiler/analyses/mod.rs | 1 + .../analyses/processing_pipeline/pipeline.rs | 1 + libs/pavexc/src/compiler/analyses/unused.rs | 131 +++ .../analyses/user_components/processed_db.rs | 15 +- .../analyses/user_components/raw_db.rs | 12 +- libs/pavexc/src/compiler/app.rs | 46 +- .../src/diagnostic/compiler_diagnostic.rs | 5 + libs/pavexc_cli/src/main.rs | 13 +- 42 files changed, 686 insertions(+), 1427 deletions(-) delete mode 100644 examples/skeleton/Cargo.lock delete mode 100644 examples/skeleton/Cargo.toml delete mode 100644 examples/skeleton/README.md delete mode 100644 examples/skeleton/app_blueprint/Cargo.toml delete mode 100644 examples/skeleton/app_blueprint/src/bin.rs delete mode 100644 examples/skeleton/app_blueprint/src/lib.rs delete mode 100644 examples/skeleton/app_server_sdk/Cargo.toml delete mode 100644 examples/skeleton/app_server_sdk/blueprint.ron delete mode 100644 examples/skeleton/blueprint.ron create mode 100644 libs/pavex/src/blueprint/linter.rs rename examples/skeleton/app_server_sdk/src/lib.rs => libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/app.rs (52%) create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/diagnostics.dot create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/stderr.txt create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/lib.rs create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/test_config.toml create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/app.rs create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/diagnostics.dot create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/lib.rs create mode 100644 libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/test_config.toml create mode 100644 libs/pavexc/src/compiler/analyses/unused.rs diff --git a/examples/realworld/Cargo.lock b/examples/realworld/Cargo.lock index c3f9afb42..fab6eb116 100644 --- a/examples/realworld/Cargo.lock +++ b/examples/realworld/Cargo.lock @@ -94,8 +94,8 @@ dependencies = [ "http 1.0.0", "hyper 1.1.0", "jsonwebtoken", - "matchit", "pavex", + "pavex_matchit", "sqlx-core", "sqlx-postgres", "thiserror", @@ -1247,11 +1247,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "git+https://github.com/ibraheemdev/matchit?branch=master#7766d457ee20826497b5061581fa6cef682d05b7" - [[package]] name = "md-5" version = "0.10.6" @@ -1533,7 +1528,7 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pavex" -version = "0.1.0" +version = "0.1.16" dependencies = [ "anyhow", "bytes", @@ -1545,13 +1540,14 @@ dependencies = [ "hyper 1.1.0", "hyper-util", "indexmap", - "matchit", "mime", "paste", "pavex_bp_schema", "pavex_macros", + "pavex_matchit", "pavex_reflection", "percent-encoding", + "persist_if_changed", "pin-project-lite", "ron", "serde", @@ -1568,7 +1564,7 @@ dependencies = [ [[package]] name = "pavex_bp_schema" -version = "0.1.0" +version = "0.1.16" dependencies = [ "pavex_reflection", "serde", @@ -1576,7 +1572,7 @@ dependencies = [ [[package]] name = "pavex_cli_client" -version = "0.1.0" +version = "0.1.16" dependencies = [ "anyhow", "pavex", @@ -1585,16 +1581,22 @@ dependencies = [ [[package]] name = "pavex_macros" -version = "0.1.0" +version = "0.1.16" dependencies = [ "proc-macro2", "quote", "syn 2.0.46", ] +[[package]] +name = "pavex_matchit" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b9753b03381fd8856966146c4d626d9a8507901c1bcdfbc41df03deea551fc" + [[package]] name = "pavex_reflection" -version = "0.1.0" +version = "0.1.16" dependencies = [ "serde", ] @@ -1655,6 +1657,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "persist_if_changed" +version = "0.1.16" +dependencies = [ + "anyhow", + "fs-err", + "sha2", + "tracing", +] + [[package]] name = "pin-project-lite" version = "0.2.13" diff --git a/examples/realworld/api_server_sdk/Cargo.toml b/examples/realworld/api_server_sdk/Cargo.toml index 6161c5b9b..e5450f2f4 100644 --- a/examples/realworld/api_server_sdk/Cargo.toml +++ b/examples/realworld/api_server_sdk/Cargo.toml @@ -12,8 +12,8 @@ conduit_core = { version = "0.1.0", path = "../conduit_core", package = "conduit http = { version = "1.0.0", package = "http" } hyper = { version = "1.1.0", package = "hyper" } jsonwebtoken = { version = "8.3.0", package = "jsonwebtoken" } -matchit = { version = "0.7.3", git = "https://github.com/ibraheemdev/matchit", branch = "master", package = "matchit" } -pavex = { version = "0.1.0", path = "../../../libs/pavex", package = "pavex" } +pavex = { version = "0.1.16", path = "../../../libs/pavex", package = "pavex" } +pavex_matchit = { version = "0.7.4", package = "pavex_matchit" } sqlx_core = { version = "0.7.3", package = "sqlx-core" } sqlx_postgres = { version = "0.7.3", package = "sqlx-postgres" } thiserror = { version = "1.0.56", package = "thiserror" } diff --git a/examples/realworld/api_server_sdk/blueprint.ron b/examples/realworld/api_server_sdk/blueprint.ron index 3dfd7f19b..08f15e7a2 100644 --- a/examples/realworld/api_server_sdk/blueprint.ron +++ b/examples/realworld/api_server_sdk/blueprint.ron @@ -1,6 +1,6 @@ ( creation_location: ( - line: 9, + line: 10, column: 18, file: "conduit_core/src/blueprint.rs", ), @@ -8,122 +8,127 @@ Constructor(( constructor: ( callable: ( - registered_at: "conduit_core", - import_path: "pavex::request::query::QueryParams::extract", + registered_at: "pavex", + import_path: "pavex::request::path::PathParams::extract", ), location: ( - line: 33, - column: 8, - file: "conduit_core/src/blueprint.rs", + line: 105, + column: 9, + file: "/Users/luca/code/pavex/libs/pavex/src/request/path/path_params.rs", ), ), lifecycle: RequestScoped, cloning_strategy: None, error_handler: Some(( callable: ( - registered_at: "conduit_core", - import_path: "pavex::request::query::errors::ExtractQueryParamsError::into_response", + registered_at: "pavex", + import_path: "pavex::request::path::errors::ExtractPathParamsError::into_response", ), location: ( - line: 37, - column: 6, - file: "conduit_core/src/blueprint.rs", + line: 109, + column: 10, + file: "/Users/luca/code/pavex/libs/pavex/src/request/path/path_params.rs", ), )), + lints: {}, )), Constructor(( constructor: ( callable: ( - registered_at: "conduit_core", - import_path: "pavex::request::path::PathParams::extract", + registered_at: "pavex", + import_path: "pavex::request::query::QueryParams::extract", ), location: ( - line: 42, - column: 8, - file: "conduit_core/src/blueprint.rs", + line: 70, + column: 9, + file: "/Users/luca/code/pavex/libs/pavex/src/request/query/query_params.rs", ), ), lifecycle: RequestScoped, cloning_strategy: None, error_handler: Some(( callable: ( - registered_at: "conduit_core", - import_path: "pavex::request::path::errors::ExtractPathParamsError::into_response", + registered_at: "pavex", + import_path: "pavex::request::query::errors::ExtractQueryParamsError::into_response", ), location: ( - line: 46, - column: 6, - file: "conduit_core/src/blueprint.rs", + line: 74, + column: 10, + file: "/Users/luca/code/pavex/libs/pavex/src/request/query/query_params.rs", ), )), + lints: {}, )), Constructor(( constructor: ( callable: ( - registered_at: "conduit_core", + registered_at: "pavex", import_path: "pavex::request::body::JsonBody::extract", ), location: ( - line: 51, - column: 8, - file: "conduit_core/src/blueprint.rs", + line: 90, + column: 9, + file: "/Users/luca/code/pavex/libs/pavex/src/request/body/json.rs", ), ), lifecycle: RequestScoped, cloning_strategy: None, error_handler: Some(( callable: ( - registered_at: "conduit_core", + registered_at: "pavex", import_path: "pavex::request::body::errors::ExtractJsonBodyError::into_response", ), location: ( - line: 55, - column: 6, - file: "conduit_core/src/blueprint.rs", + line: 94, + column: 10, + file: "/Users/luca/code/pavex/libs/pavex/src/request/body/json.rs", ), )), + lints: {}, )), Constructor(( constructor: ( callable: ( - registered_at: "conduit_core", + registered_at: "pavex", import_path: "pavex::request::body::BufferedBody::extract", ), location: ( - line: 58, - column: 8, - file: "conduit_core/src/blueprint.rs", + line: 89, + column: 9, + file: "/Users/luca/code/pavex/libs/pavex/src/request/body/buffered_body.rs", ), ), lifecycle: RequestScoped, cloning_strategy: None, error_handler: Some(( callable: ( - registered_at: "conduit_core", + registered_at: "pavex", import_path: "pavex::request::body::errors::ExtractBufferedBodyError::into_response", ), location: ( - line: 62, - column: 6, - file: "conduit_core/src/blueprint.rs", + line: 93, + column: 10, + file: "/Users/luca/code/pavex/libs/pavex/src/request/body/buffered_body.rs", ), )), + lints: {}, )), Constructor(( constructor: ( callable: ( - registered_at: "conduit_core", + registered_at: "pavex", import_path: "::default", ), location: ( - line: 65, - column: 8, - file: "conduit_core/src/blueprint.rs", + line: 30, + column: 9, + file: "/Users/luca/code/pavex/libs/pavex/src/request/body/limit.rs", ), ), lifecycle: RequestScoped, cloning_strategy: None, error_handler: None, + lints: {}, )), Constructor(( constructor: ( @@ -132,7 +137,7 @@ import_path: "crate::configuration::DatabaseConfig::get_pool", ), location: ( - line: 11, + line: 12, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -140,6 +145,7 @@ lifecycle: Singleton, cloning_strategy: None, error_handler: None, + lints: {}, )), Constructor(( constructor: ( @@ -148,7 +154,7 @@ import_path: "crate::configuration::AuthConfig::decoding_key", ), location: ( - line: 15, + line: 16, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -156,6 +162,7 @@ lifecycle: Singleton, cloning_strategy: None, error_handler: None, + lints: {}, )), Constructor(( constructor: ( @@ -164,7 +171,7 @@ import_path: "crate::telemetry::RootSpan::new", ), location: ( - line: 73, + line: 33, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -172,6 +179,7 @@ lifecycle: RequestScoped, cloning_strategy: Some(CloneIfNecessary), error_handler: None, + lints: {}, )), WrappingMiddleware(( middleware: ( @@ -180,7 +188,7 @@ import_path: "crate::telemetry::logger", ), location: ( - line: 79, + line: 39, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -397,7 +405,7 @@ ), path_prefix: Some("/articles"), nesting_location: ( - line: 22, + line: 23, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -468,7 +476,7 @@ ), path_prefix: Some("/profiles"), nesting_location: ( - line: 23, + line: 24, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -496,6 +504,7 @@ lifecycle: Singleton, cloning_strategy: None, error_handler: None, + lints: {}, )), Route(( path: "/users", @@ -593,7 +602,7 @@ ), path_prefix: None, nesting_location: ( - line: 24, + line: 25, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -609,7 +618,7 @@ import_path: "crate::routes::status::ping", ), location: ( - line: 25, + line: 26, column: 8, file: "conduit_core/src/blueprint.rs", ), @@ -627,7 +636,7 @@ import_path: "crate::routes::tags::get_tags", ), location: ( - line: 26, + line: 27, column: 8, file: "conduit_core/src/blueprint.rs", ), diff --git a/examples/realworld/api_server_sdk/src/lib.rs b/examples/realworld/api_server_sdk/src/lib.rs index 7d17c9dc0..70925db49 100644 --- a/examples/realworld/api_server_sdk/src/lib.rs +++ b/examples/realworld/api_server_sdk/src/lib.rs @@ -3,7 +3,7 @@ //! All manual edits will be lost next time the code is generated. extern crate alloc; struct ServerState { - router: matchit::Router, + router: pavex_matchit::Router, application_state: ApplicationState, } pub struct ApplicationState { @@ -57,8 +57,8 @@ pub fn run( }); server_builder.serve(route_request, server_state) } -fn build_router() -> matchit::Router { - let mut router = matchit::Router::new(); +fn build_router() -> pavex_matchit::Router { + let mut router = pavex_matchit::Router::new(); router.insert("/api/ping", 0u32).unwrap(); router.insert("/articles", 1u32).unwrap(); router.insert("/articles/:slug", 2u32).unwrap(); @@ -93,8 +93,8 @@ async fn route_request( "*", ); return route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await; @@ -120,8 +120,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -151,8 +151,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -183,8 +183,8 @@ async fn route_request( &pavex::http::Method::PUT => { route_15::middleware_0( matched_route_template, - url_params, request_body, + url_params, &request_head, ) .await @@ -197,8 +197,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -221,8 +221,8 @@ async fn route_request( &pavex::http::Method::POST => { route_19::middleware_0( matched_route_template, - url_params, request_body, + url_params, &request_head, ) .await @@ -234,8 +234,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -261,8 +261,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -297,8 +297,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -319,8 +319,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -346,8 +346,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -382,8 +382,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -404,8 +404,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -435,8 +435,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -450,8 +450,8 @@ async fn route_request( match &request_head.method { &pavex::http::Method::POST => { route_3::middleware_0( - matched_route_template, &server_state.application_state.s0, + matched_route_template, &server_state.application_state.s1, request_body, &request_head, @@ -464,8 +464,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -479,8 +479,8 @@ async fn route_request( match &request_head.method { &pavex::http::Method::POST => { route_4::middleware_0( - matched_route_template, &server_state.application_state.s0, + matched_route_template, &server_state.application_state.s1, request_body, &request_head, @@ -493,8 +493,8 @@ async fn route_request( ]) .into(); route_2::middleware_0( - matched_route_template, &allowed_methods, + matched_route_template, &request_head, ) .await @@ -572,13 +572,13 @@ pub mod route_1 { } pub mod route_2 { pub async fn middleware_0( - v0: pavex::request::path::MatchedPathPattern, - v1: &pavex::router::AllowedMethods, + v0: &pavex::router::AllowedMethods, + v1: pavex::request::path::MatchedPathPattern, v2: &pavex::request::RequestHead, ) -> pavex::response::Response { - let v3 = conduit_core::telemetry::RootSpan::new(v2, v0); + let v3 = conduit_core::telemetry::RootSpan::new(v2, v1); let v4 = crate::route_2::Next0 { - s_0: v1, + s_0: v0, next: handler, }; let v5 = pavex::middleware::Next::new(v4); @@ -610,28 +610,28 @@ pub mod route_2 { } pub mod route_3 { pub async fn middleware_0( - v0: pavex::request::path::MatchedPathPattern, - v1: &sqlx_core::driver_prelude::pool::Pool, + v0: &sqlx_core::driver_prelude::pool::Pool, + v1: pavex::request::path::MatchedPathPattern, v2: &jsonwebtoken::EncodingKey, v3: pavex::request::body::RawIncomingBody, v4: &pavex::request::RequestHead, ) -> pavex::response::Response { - let v5 = conduit_core::telemetry::RootSpan::new(v4, v0); + let v5 = conduit_core::telemetry::RootSpan::new(v4, v1); let v6 = crate::route_3::Next0 { - s_0: v1, + s_0: v2, s_1: v4, s_2: v3, - s_3: v2, + s_3: v0, next: handler, }; let v7 = pavex::middleware::Next::new(v6); conduit_core::telemetry::logger(v7, v5).await } pub async fn handler( - v0: &sqlx_core::driver_prelude::pool::Pool, + v0: &jsonwebtoken::EncodingKey, v1: &pavex::request::RequestHead, v2: pavex::request::body::RawIncomingBody, - v3: &jsonwebtoken::EncodingKey, + v3: &sqlx_core::driver_prelude::pool::Pool, ) -> pavex::response::Response { let v4 = ::default(); let v5 = pavex::request::body::BufferedBody::extract(v1, v2, v4).await; @@ -662,7 +662,7 @@ pub mod route_3 { }; } }; - let v9 = conduit_core::routes::users::signup(v8, v0, v3).await; + let v9 = conduit_core::routes::users::signup(v8, v3, v0).await; let v10 = match v9 { Ok(ok) => ok, Err(v10) => { @@ -682,15 +682,15 @@ pub mod route_3 { where T: std::future::Future, { - s_0: &'a sqlx_core::driver_prelude::pool::Pool, + s_0: &'a jsonwebtoken::EncodingKey, s_1: &'b pavex::request::RequestHead, s_2: pavex::request::body::RawIncomingBody, - s_3: &'c jsonwebtoken::EncodingKey, + s_3: &'c sqlx_core::driver_prelude::pool::Pool, next: fn( - &'a sqlx_core::driver_prelude::pool::Pool, + &'a jsonwebtoken::EncodingKey, &'b pavex::request::RequestHead, pavex::request::body::RawIncomingBody, - &'c jsonwebtoken::EncodingKey, + &'c sqlx_core::driver_prelude::pool::Pool, ) -> T, } impl<'a, 'b, 'c, T> std::future::IntoFuture for Next0<'a, 'b, 'c, T> @@ -706,28 +706,28 @@ pub mod route_3 { } pub mod route_4 { pub async fn middleware_0( - v0: pavex::request::path::MatchedPathPattern, - v1: &sqlx_core::driver_prelude::pool::Pool, + v0: &sqlx_core::driver_prelude::pool::Pool, + v1: pavex::request::path::MatchedPathPattern, v2: &jsonwebtoken::EncodingKey, v3: pavex::request::body::RawIncomingBody, v4: &pavex::request::RequestHead, ) -> pavex::response::Response { - let v5 = conduit_core::telemetry::RootSpan::new(v4, v0); + let v5 = conduit_core::telemetry::RootSpan::new(v4, v1); let v6 = crate::route_4::Next0 { - s_0: v1, + s_0: v2, s_1: v4, s_2: v3, - s_3: v2, + s_3: v0, next: handler, }; let v7 = pavex::middleware::Next::new(v6); conduit_core::telemetry::logger(v7, v5).await } pub async fn handler( - v0: &sqlx_core::driver_prelude::pool::Pool, + v0: &jsonwebtoken::EncodingKey, v1: &pavex::request::RequestHead, v2: pavex::request::body::RawIncomingBody, - v3: &jsonwebtoken::EncodingKey, + v3: &sqlx_core::driver_prelude::pool::Pool, ) -> pavex::response::Response { let v4 = ::default(); let v5 = pavex::request::body::BufferedBody::extract(v1, v2, v4).await; @@ -758,7 +758,7 @@ pub mod route_4 { }; } }; - let v9 = conduit_core::routes::users::login(v8, v0, v3).await; + let v9 = conduit_core::routes::users::login(v8, v3, v0).await; let v10 = match v9 { Ok(ok) => ok, Err(v10) => { @@ -778,15 +778,15 @@ pub mod route_4 { where T: std::future::Future, { - s_0: &'a sqlx_core::driver_prelude::pool::Pool, + s_0: &'a jsonwebtoken::EncodingKey, s_1: &'b pavex::request::RequestHead, s_2: pavex::request::body::RawIncomingBody, - s_3: &'c jsonwebtoken::EncodingKey, + s_3: &'c sqlx_core::driver_prelude::pool::Pool, next: fn( - &'a sqlx_core::driver_prelude::pool::Pool, + &'a jsonwebtoken::EncodingKey, &'b pavex::request::RequestHead, pavex::request::body::RawIncomingBody, - &'c jsonwebtoken::EncodingKey, + &'c sqlx_core::driver_prelude::pool::Pool, ) -> T, } impl<'a, 'b, 'c, T> std::future::IntoFuture for Next0<'a, 'b, 'c, T> @@ -1340,14 +1340,14 @@ pub mod route_14 { pub mod route_15 { pub async fn middleware_0( v0: pavex::request::path::MatchedPathPattern, - v1: pavex::request::path::RawPathParams<'_, '_>, - v2: pavex::request::body::RawIncomingBody, + v1: pavex::request::body::RawIncomingBody, + v2: pavex::request::path::RawPathParams<'_, '_>, v3: &pavex::request::RequestHead, ) -> pavex::response::Response { let v4 = conduit_core::telemetry::RootSpan::new(v3, v0); let v5 = crate::route_15::Next0 { - s_0: v2, - s_1: v1, + s_0: v1, + s_1: v2, s_2: v3, next: handler, }; @@ -1588,14 +1588,14 @@ pub mod route_18 { pub mod route_19 { pub async fn middleware_0( v0: pavex::request::path::MatchedPathPattern, - v1: pavex::request::path::RawPathParams<'_, '_>, - v2: pavex::request::body::RawIncomingBody, + v1: pavex::request::body::RawIncomingBody, + v2: pavex::request::path::RawPathParams<'_, '_>, v3: &pavex::request::RequestHead, ) -> pavex::response::Response { let v4 = conduit_core::telemetry::RootSpan::new(v3, v0); let v5 = crate::route_19::Next0 { - s_0: v2, - s_1: v1, + s_0: v1, + s_1: v2, s_2: v3, next: handler, }; diff --git a/examples/realworld/conduit_core/src/blueprint.rs b/examples/realworld/conduit_core/src/blueprint.rs index 84b6a2735..4972eab24 100644 --- a/examples/realworld/conduit_core/src/blueprint.rs +++ b/examples/realworld/conduit_core/src/blueprint.rs @@ -2,12 +2,13 @@ use crate::routes; use pavex::blueprint::constructor::CloningStrategy; use pavex::blueprint::{constructor::Lifecycle, router::GET, Blueprint}; use pavex::f; +use pavex::kit::ApiKit; /// The main API blueprint, containing all the routes, constructors and error handlers /// required to implement the Realworld API specification. pub fn blueprint() -> Blueprint { let mut bp = Blueprint::new(); - register_common_constructors(&mut bp); + ApiKit::new().register(&mut bp); bp.constructor( f!(crate::configuration::DatabaseConfig::get_pool), Lifecycle::Singleton, @@ -27,47 +28,6 @@ pub fn blueprint() -> Blueprint { bp } -/// Common constructors used by all routes. -fn register_common_constructors(bp: &mut Blueprint) { - // Query parameters - bp.constructor( - f!(pavex::request::query::QueryParams::extract), - Lifecycle::RequestScoped, - ) - .error_handler(f!( - pavex::request::query::errors::ExtractQueryParamsError::into_response - )); - - // Route parameters - bp.constructor( - f!(pavex::request::path::PathParams::extract), - Lifecycle::RequestScoped, - ) - .error_handler(f!( - pavex::request::path::errors::ExtractPathParamsError::into_response - )); - - // Json body - bp.constructor( - f!(pavex::request::body::JsonBody::extract), - Lifecycle::RequestScoped, - ) - .error_handler(f!( - pavex::request::body::errors::ExtractJsonBodyError::into_response - )); - bp.constructor( - f!(pavex::request::body::BufferedBody::extract), - Lifecycle::RequestScoped, - ) - .error_handler(f!( - pavex::request::body::errors::ExtractBufferedBodyError::into_response - )); - bp.constructor( - f!(::default), - Lifecycle::RequestScoped, - ); -} - /// Add the telemetry middleware, as well as the constructors of its dependencies. fn add_telemetry_middleware(bp: &mut Blueprint) { bp.constructor( diff --git a/examples/skeleton/Cargo.lock b/examples/skeleton/Cargo.lock deleted file mode 100644 index e2de4ef7d..000000000 --- a/examples/skeleton/Cargo.lock +++ /dev/null @@ -1,907 +0,0 @@ -# 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 = "anyhow" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" - -[[package]] -name = "app_blueprint" -version = "0.1.0" -dependencies = [ - "anyhow", - "cargo_px_env", - "pavex", - "pavex_cli_client", -] - -[[package]] -name = "app_sdk" -version = "0.1.0" -dependencies = [ - "app_blueprint", - "http", - "hyper", - "matchit", - "pavex", - "thiserror", -] - -[[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.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "cargo_px_env" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b459425e7bfec0783df9538ecc749961f076cc793a09e91f1d082ee0012c925" - -[[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 = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[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.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs-err" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gimli" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24590385be94998c5def4cf53d34edc5381144c805126f00efb954d986f9a7b2" -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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cec7646c446f27ed929e7bf1feace8c7d5e248109ba7fc716d846b27081fac" -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 = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "libc" -version = "0.2.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.45.0", -] - -[[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.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[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_bp_schema", - "pavex_macros", - "pavex_reflection", - "percent-encoding", - "pin-project-lite", - "ron", - "serde", - "serde_html_form", - "serde_json", - "serde_path_to_error", - "smallvec", - "socket2", - "thiserror", - "tokio", - "tracing", - "ubyte", -] - -[[package]] -name = "pavex_bp_schema" -version = "0.1.0" -dependencies = [ - "pavex_reflection", - "serde", -] - -[[package]] -name = "pavex_cli_client" -version = "0.1.0" -dependencies = [ - "anyhow", - "pavex", - "thiserror", -] - -[[package]] -name = "pavex_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pavex_reflection" -version = "0.1.0" -dependencies = [ - "serde", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[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.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ron" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" -dependencies = [ - "base64", - "bitflags", - "serde", -] - -[[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.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "serde" -version = "1.0.160" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.160" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_html_form" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde65b75f2603066b78d6fa239b2c07b43e06ead09435f60554d3912962b4a3c" -dependencies = [ - "form_urlencoded", - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" -dependencies = [ - "serde", -] - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "1.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-util" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" -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.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" -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.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "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.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/examples/skeleton/Cargo.toml b/examples/skeleton/Cargo.toml deleted file mode 100644 index 9d822b447..000000000 --- a/examples/skeleton/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[workspace] -members = ["app_blueprint", "app_server_sdk"] -resolver = "2" diff --git a/examples/skeleton/README.md b/examples/skeleton/README.md deleted file mode 100644 index 5ddc28d17..000000000 --- a/examples/skeleton/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Skeleton example - -A barebone example that showcases the _mechanics_ of Pavex. - -`app_blueprint` provides two entrypoints: - -- The library crate, where the `Blueprint` for the application is built; -- A binary, `bp`, which can be used to generate the server SDK crate. - -`app_server_sdk` is code-generated starting from the `Blueprint`, using `cargo-px`. - -# Pre-requisites - -- `cargo-px` (`cargo install --locked cargo-px`) - -# How to build - -All commands must be proxied through `cargo-px` in order to re-generate `app_server_sdk` when necessary. - -```bash -# Build the project -cargo px build -# Run tests -cargo px test -# Etc. -``` \ No newline at end of file diff --git a/examples/skeleton/app_blueprint/Cargo.toml b/examples/skeleton/app_blueprint/Cargo.toml deleted file mode 100644 index e144b6bc8..000000000 --- a/examples/skeleton/app_blueprint/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "app_blueprint" -version = "0.1.0" -edition = "2021" - -[[bin]] -path = "src/bin.rs" -name = "bp" - -[dependencies] -anyhow = "1" -pavex_cli_client = { path = "../../../libs/pavex_cli_client" } -cargo_px_env = "0.1" -pavex = { path = "../../../libs/pavex" } \ No newline at end of file diff --git a/examples/skeleton/app_blueprint/src/bin.rs b/examples/skeleton/app_blueprint/src/bin.rs deleted file mode 100644 index f6f7097ae..000000000 --- a/examples/skeleton/app_blueprint/src/bin.rs +++ /dev/null @@ -1,14 +0,0 @@ -use app_blueprint::app_blueprint; -use cargo_px_env::generated_pkg_manifest_path; -use pavex_cli_client::Client; -use std::error::Error; - -fn main() -> Result<(), Box> { - let generated_dir = generated_pkg_manifest_path()?.parent().unwrap().into(); - let blueprint = app_blueprint(); - Client::new() - .pavex_cli_path("../../libs/target/release/pavex".into()) - .generate(blueprint, generated_dir) - .execute()?; - Ok(()) -} diff --git a/examples/skeleton/app_blueprint/src/lib.rs b/examples/skeleton/app_blueprint/src/lib.rs deleted file mode 100644 index 342edc133..000000000 --- a/examples/skeleton/app_blueprint/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; - -use pavex::f; -use pavex::blueprint::router::GET; -use pavex::blueprint::{constructor::Lifecycle, Blueprint}; - -pub struct Logger; - -pub fn extract_path(_inner: &pavex::request::RequestHead) -> PathBuf { - todo!() -} - -pub fn logger() -> Logger { - todo!() -} - -pub fn stream_file( - _inner: PathBuf, - _logger: Logger, - _http_client: HttpClient, -) -> pavex::response::Response { - todo!() -} - -pub struct Config; - -pub fn config() -> Config { - todo!() -} - -#[derive(Clone)] -pub struct HttpClient; - -pub fn http_client(_config: Config) -> HttpClient { - todo!() -} - -pub fn app_blueprint() -> Blueprint { - let mut bp = Blueprint::new(); - bp.constructor(f!(crate::http_client), Lifecycle::Singleton); - bp.constructor(f!(crate::extract_path), Lifecycle::RequestScoped); - bp.constructor(f!(crate::logger), Lifecycle::Transient); - bp.route(GET, "/home", f!(crate::stream_file)); - bp -} diff --git a/examples/skeleton/app_server_sdk/Cargo.toml b/examples/skeleton/app_server_sdk/Cargo.toml deleted file mode 100644 index 9cc90f58e..000000000 --- a/examples/skeleton/app_server_sdk/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "app_sdk" -edition = "2021" -version = "0.1.0" - -[package.metadata.px.generate] -generator_type = "cargo_workspace_binary" -generator_name = "bp" - -[dependencies] -app_blueprint = { version = "0.1.0", path = "../app_blueprint", package = "app_blueprint" } -http = { version = "1.0.0", package = "http" } -hyper = { version = "1.0.0", package = "hyper" } -matchit = { version = "0.7.3", git = "https://github.com/ibraheemdev/matchit", branch = "master", package = "matchit" } -pavex = { version = "0.1.0", path = "../../../libs/pavex", package = "pavex" } -thiserror = { version = "1.0.56", package = "thiserror" } diff --git a/examples/skeleton/app_server_sdk/blueprint.ron b/examples/skeleton/app_server_sdk/blueprint.ron deleted file mode 100644 index a179009d1..000000000 --- a/examples/skeleton/app_server_sdk/blueprint.ron +++ /dev/null @@ -1,75 +0,0 @@ -( - creation_location: ( - line: 39, - column: 18, - file: "app_blueprint/src/lib.rs", - ), - components: [ - Constructor(( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate::http_client", - ), - location: ( - line: 40, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: Singleton, - cloning_strategy: None, - error_handler: None, - )), - Constructor(( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate::extract_path", - ), - location: ( - line: 41, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: RequestScoped, - cloning_strategy: None, - error_handler: None, - )), - Constructor(( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate::logger", - ), - location: ( - line: 42, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: Transient, - cloning_strategy: None, - error_handler: None, - )), - Route(( - path: "/home", - method_guard: Some([ - "GET", - ]), - request_handler: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate::stream_file", - ), - location: ( - line: 43, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - error_handler: None, - )), - ], -) \ No newline at end of file diff --git a/examples/skeleton/blueprint.ron b/examples/skeleton/blueprint.ron deleted file mode 100644 index 121200fb4..000000000 --- a/examples/skeleton/blueprint.ron +++ /dev/null @@ -1,80 +0,0 @@ -( - creation_location: ( - line: 38, - column: 18, - file: "app_blueprint/src/lib.rs", - ), - constructors: [ - ( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate :: http_client", - ), - location: ( - line: 39, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: Singleton, - cloning_strategy: None, - error_handler: None, - ), - ( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate :: extract_path", - ), - location: ( - line: 40, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: RequestScoped, - cloning_strategy: None, - error_handler: None, - ), - ( - constructor: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate :: logger", - ), - location: ( - line: 41, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - lifecycle: Transient, - cloning_strategy: None, - error_handler: None, - ), - ], - routes: [ - ( - path: "/home", - method_guard: ( - allowed_methods: [ - "GET", - ], - ), - request_handler: ( - callable: ( - registered_at: "app_blueprint", - import_path: "crate :: stream_file", - ), - location: ( - line: 42, - column: 8, - file: "app_blueprint/src/lib.rs", - ), - ), - error_handler: None, - ), - ], - nested_blueprints: [], -) \ No newline at end of file diff --git a/libs/pavex/src/blueprint/blueprint.rs b/libs/pavex/src/blueprint/blueprint.rs index 20403e05f..0aa1d4619 100644 --- a/libs/pavex/src/blueprint/blueprint.rs +++ b/libs/pavex/src/blueprint/blueprint.rs @@ -160,6 +160,7 @@ impl Blueprint { lifecycle: lifecycle2lifecycle(lifecycle), cloning_strategy: None, error_handler: None, + lints: Default::default(), }; let component_id = self.push_component(registered_constructor); RegisteredConstructor { @@ -177,6 +178,7 @@ impl Blueprint { lifecycle: lifecycle2lifecycle(constructor.lifecycle), cloning_strategy: constructor.cloning_strategy.map(cloning2cloning), error_handler: constructor.error_handler, + lints: constructor.lints, }; let component_id = self.push_component(constructor); RegisteredConstructor { diff --git a/libs/pavex/src/blueprint/constructor/registered.rs b/libs/pavex/src/blueprint/constructor/registered.rs index 7386ad482..2afe71c5c 100644 --- a/libs/pavex/src/blueprint/constructor/registered.rs +++ b/libs/pavex/src/blueprint/constructor/registered.rs @@ -1,7 +1,8 @@ use crate::blueprint::constructor::CloningStrategy; -use crate::blueprint::conversions::{cloning2cloning, raw_callable2registered_callable}; +use crate::blueprint::conversions::{cloning2cloning, lint2lint, raw_callable2registered_callable}; +use crate::blueprint::linter::Lint; use crate::blueprint::reflection::RawCallable; -use pavex_bp_schema::Blueprint as BlueprintSchema; +use pavex_bp_schema::{Blueprint as BlueprintSchema, LintSetting}; use pavex_bp_schema::{Component, Constructor}; /// The type returned by [`Blueprint::constructor`]. @@ -73,6 +74,24 @@ impl<'a> RegisteredConstructor<'a> { self } + /// Tell Pavex to ignore a specific [`Lint`] when analysing + /// this constructor and the way it's used. + pub fn ignore(mut self, lint: Lint) -> Self { + self.constructor() + .lints + .insert(lint2lint(lint), LintSetting::Ignore); + self + } + + /// Tell Pavex to enforce a specific [`Lint`] when analysing + /// this constructor and the way it's used. + pub fn enforce(mut self, lint: Lint) -> Self { + self.constructor() + .lints + .insert(lint2lint(lint), LintSetting::Enforce); + self + } + fn constructor(&mut self) -> &mut Constructor { let component = &mut self.blueprint.components[self.component_id]; let Component::Constructor(c) = component else { diff --git a/libs/pavex/src/blueprint/constructor/unregistered.rs b/libs/pavex/src/blueprint/constructor/unregistered.rs index 353d20e1e..691dd025c 100644 --- a/libs/pavex/src/blueprint/constructor/unregistered.rs +++ b/libs/pavex/src/blueprint/constructor/unregistered.rs @@ -1,8 +1,10 @@ use crate::blueprint::constructor::{CloningStrategy, Lifecycle, RegisteredConstructor}; -use crate::blueprint::conversions::raw_callable2registered_callable; +use crate::blueprint::conversions::{lint2lint, raw_callable2registered_callable}; +use crate::blueprint::linter::Lint; use crate::blueprint::reflection::RawCallable; use crate::blueprint::Blueprint; -use pavex_bp_schema::Callable; +use pavex_bp_schema::{Callable, LintSetting}; +use std::collections::BTreeMap; /// A constructor that has been configured but has not yet been registered with a [`Blueprint`]. /// @@ -23,6 +25,7 @@ pub struct Constructor { pub(in crate::blueprint) lifecycle: Lifecycle, pub(in crate::blueprint) cloning_strategy: Option, pub(in crate::blueprint) error_handler: Option, + pub(in crate::blueprint) lints: BTreeMap, } impl Constructor { @@ -37,6 +40,7 @@ impl Constructor { lifecycle, cloning_strategy: None, error_handler: None, + lints: Default::default(), } } @@ -57,6 +61,20 @@ impl Constructor { self } + /// Tell Pavex to ignore a specific [`Lint`] when analysing + /// this constructor and the way it's used. + pub fn ignore(mut self, lint: Lint) -> Self { + self.lints.insert(lint2lint(lint), LintSetting::Ignore); + self + } + + /// Tell Pavex to enforce a specific [`Lint`] when analysing + /// this constructor and the way it's used. + pub fn enforce(mut self, lint: Lint) -> Self { + self.lints.insert(lint2lint(lint), LintSetting::Enforce); + self + } + /// Register this constructor with a [`Blueprint`]. /// /// Check out the documentation of [`Blueprint::constructor`] for more details. diff --git a/libs/pavex/src/blueprint/conversions.rs b/libs/pavex/src/blueprint/conversions.rs index 214fc6bb8..5196254f9 100644 --- a/libs/pavex/src/blueprint/conversions.rs +++ b/libs/pavex/src/blueprint/conversions.rs @@ -1,5 +1,6 @@ //! Conversions between `pavex_bp_schema` and `pavex_bp` types. use crate::blueprint::constructor::{CloningStrategy, Lifecycle}; +use crate::blueprint::linter::Lint; use crate::blueprint::reflection::RawCallable; use crate::router::AllowedMethods; use pavex_bp_schema::{Callable, Location}; @@ -40,3 +41,9 @@ pub(super) fn method_guard2method_guard( AllowedMethods::All => pavex_bp_schema::MethodGuard::Any, } } + +pub(super) fn lint2lint(lint: Lint) -> pavex_bp_schema::Lint { + match lint { + Lint::Unused => pavex_bp_schema::Lint::Unused, + } +} diff --git a/libs/pavex/src/blueprint/linter.rs b/libs/pavex/src/blueprint/linter.rs new file mode 100644 index 000000000..721c9a6d5 --- /dev/null +++ b/libs/pavex/src/blueprint/linter.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash)] +#[non_exhaustive] +/// Common mistakes and antipatterns that Pavex +/// tries to catch when analysing your [`Blueprint`]. +/// +/// These issues aren't considered fatal: Pavex will still +/// generate the server SDK code. +/// +/// [`Blueprint`]: crate::blueprint::Blueprint +pub enum Lint { + /// You registered a component that's never used in the generated + /// server SDK code. + Unused, +} diff --git a/libs/pavex/src/blueprint/mod.rs b/libs/pavex/src/blueprint/mod.rs index 3e284b856..adeae7d62 100644 --- a/libs/pavex/src/blueprint/mod.rs +++ b/libs/pavex/src/blueprint/mod.rs @@ -9,6 +9,7 @@ mod blueprint; pub mod constructor; mod conversions; pub mod error_observer; +pub mod linter; pub mod middleware; pub mod reflection; pub mod router; diff --git a/libs/pavex/src/kit/api.rs b/libs/pavex/src/kit/api.rs index 6ff006d2a..8fb1e68c0 100644 --- a/libs/pavex/src/kit/api.rs +++ b/libs/pavex/src/kit/api.rs @@ -1,4 +1,5 @@ use crate::blueprint::constructor::Constructor; +use crate::blueprint::linter::Lint; use crate::blueprint::Blueprint; use crate::request::body::{BodySizeLimit, BufferedBody, JsonBody}; use crate::request::path::PathParams; @@ -42,11 +43,11 @@ impl ApiKit { /// Create a new [`ApiKit`] with all the bundled constructors. pub fn new() -> Self { Self { - path_params: Some(PathParams::default_constructor()), - query_params: Some(QueryParams::default_constructor()), - json_body: Some(JsonBody::default_constructor()), - buffered_body: Some(BufferedBody::default_constructor()), - body_size_limit: Some(BodySizeLimit::default_constructor()), + path_params: Some(PathParams::default_constructor().ignore(Lint::Unused)), + query_params: Some(QueryParams::default_constructor().ignore(Lint::Unused)), + json_body: Some(JsonBody::default_constructor().ignore(Lint::Unused)), + buffered_body: Some(BufferedBody::default_constructor().ignore(Lint::Unused)), + body_size_limit: Some(BodySizeLimit::default_constructor().ignore(Lint::Unused)), } } diff --git a/libs/pavex_bp_schema/src/lib.rs b/libs/pavex_bp_schema/src/lib.rs index 73c9ca764..aa72820c9 100644 --- a/libs/pavex_bp_schema/src/lib.rs +++ b/libs/pavex_bp_schema/src/lib.rs @@ -3,7 +3,7 @@ //! There are no guarantees that this schema will remain stable across Pavex versions: //! it is considered (for the time being) an internal implementation detail of Pavex's reflection system. pub use pavex_reflection::{Location, RawCallableIdentifiers}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use std::fmt::Formatter; @@ -103,6 +103,8 @@ pub struct Constructor { pub cloning_strategy: Option, /// The callable in charge of processing errors returned by this constructor, if any. pub error_handler: Option, + /// Lint settings for this constructor. + pub lints: BTreeMap, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] @@ -172,3 +174,23 @@ pub enum MethodGuard { Any, Some(BTreeSet), } + +#[derive( + Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Serialize, serde::Deserialize, +)] +#[non_exhaustive] +/// Common mistakes and antipatterns that Pavex +/// tries to catch when analysing your [`Blueprint`]. +pub enum Lint { + /// You registered a component that's never used in the generated + /// server SDK code. + Unused, +} + +#[derive( + Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, +)] +pub enum LintSetting { + Ignore, + Enforce, +} diff --git a/examples/skeleton/app_server_sdk/src/lib.rs b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/app.rs similarity index 52% rename from examples/skeleton/app_server_sdk/src/lib.rs rename to libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/app.rs index c0aa764dd..662873c32 100644 --- a/examples/skeleton/app_server_sdk/src/lib.rs +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/app.rs @@ -3,17 +3,13 @@ //! All manual edits will be lost next time the code is generated. extern crate alloc; struct ServerState { - router: matchit::Router, + router: pavex_matchit::Router, + #[allow(dead_code)] application_state: ApplicationState, } -pub struct ApplicationState { - s0: app_blueprint::HttpClient, -} -pub async fn build_application_state( - v0: app_blueprint::Config, -) -> crate::ApplicationState { - let v1 = app_blueprint::http_client(v0); - crate::ApplicationState { s0: v1 } +pub struct ApplicationState {} +pub async fn build_application_state() -> crate::ApplicationState { + crate::ApplicationState {} } pub fn run( server_builder: pavex::server::Server, @@ -25,9 +21,8 @@ pub fn run( }); server_builder.serve(route_request, server_state) } -fn build_router() -> matchit::Router { - let mut router = matchit::Router::new(); - router.insert("/home", 0u32).unwrap(); +fn build_router() -> pavex_matchit::Router { + let mut router = pavex_matchit::Router::new(); router } async fn route_request( @@ -45,7 +40,7 @@ async fn route_request( vec![], ) .into(); - return route_1::handler(&allowed_methods).await; + return route_0::handler(&allowed_methods).await; } }; let route_id = matched_route.value; @@ -54,45 +49,14 @@ async fn route_request( .params .into(); match route_id { - 0u32 => { - match &request_head.method { - &pavex::http::Method::GET => { - route_0::handler( - server_state.application_state.s0.clone(), - &request_head, - ) - .await - } - _ => { - let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter([ - pavex::http::Method::GET, - ]) - .into(); - route_1::handler(&allowed_methods).await - } - } - } i => unreachable!("Unknown route id: {}", i), } } pub mod route_0 { - pub async fn handler( - v0: app_blueprint::HttpClient, - v1: &pavex::request::RequestHead, - ) -> pavex::response::Response { - let v2 = app_blueprint::extract_path(v1); - let v4 = { - let v3 = app_blueprint::logger(); - app_blueprint::stream_file(v2, v3, v0) - }; - ::into_response(v4) - } -} -pub mod route_1 { pub async fn handler( v0: &pavex::router::AllowedMethods, ) -> pavex::response::Response { let v1 = pavex::router::default_fallback(v0).await; ::into_response(v1) } -} +} \ No newline at end of file diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/diagnostics.dot b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/diagnostics.dot new file mode 100644 index 000000000..d42a8f858 --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/diagnostics.dot @@ -0,0 +1,3 @@ +digraph app_state { + 0 [ label = "crate::ApplicationState() -> crate::ApplicationState"] +} \ No newline at end of file diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/stderr.txt b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/stderr.txt new file mode 100644 index 000000000..d3d68877a --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/expectations/stderr.txt @@ -0,0 +1,14 @@ +WARNING: + ⚠ You registered a constructor for `app::Unused`, but it's never used. + β”‚ `app::Unused::new` is never invoked since no component is asking for + β”‚ `app::Unused` to be injected as one of its inputs. + β”‚ + β”‚ ╭─[src/lib.rs:13:1] + β”‚ 13 β”‚ let mut bp = Blueprint::new(); + β”‚ 14 β”‚ bp.constructor(f!(crate::Unused::new), Lifecycle::RequestScoped); + β”‚ Β·  ───────────┬────────── + β”‚ Β· The unused constructor was registered here + β”‚ 15 β”‚ bp + β”‚ ╰──── + β”‚  help: If you want to ignore this warning, call `.ignore(Lint::Unused)` on + β”‚ the registered constructor. \ No newline at end of file diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/lib.rs b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/lib.rs new file mode 100644 index 000000000..f4c797eb9 --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/lib.rs @@ -0,0 +1,16 @@ +use pavex::blueprint::{constructor::Lifecycle, Blueprint}; +use pavex::f; + +pub struct Unused; + +impl Unused { + pub fn new() -> Self { + Self + } +} + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.constructor(f!(crate::Unused::new), Lifecycle::RequestScoped); + bp +} diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/test_config.toml b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/test_config.toml new file mode 100644 index 000000000..5bacf0be4 --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/a_warning_is_emitted_for_unused_constructors/test_config.toml @@ -0,0 +1,7 @@ +description = """When you have an unused constructor, Pavex emits a warning.""" + +[expectations] +codegen = "pass" +lints = "fail" + + diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/app.rs b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/app.rs new file mode 100644 index 000000000..662873c32 --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/app.rs @@ -0,0 +1,62 @@ +//! Do NOT edit this code. +//! It was automatically generated by Pavex. +//! All manual edits will be lost next time the code is generated. +extern crate alloc; +struct ServerState { + router: pavex_matchit::Router, + #[allow(dead_code)] + application_state: ApplicationState, +} +pub struct ApplicationState {} +pub async fn build_application_state() -> crate::ApplicationState { + crate::ApplicationState {} +} +pub fn run( + server_builder: pavex::server::Server, + application_state: ApplicationState, +) -> pavex::server::ServerHandle { + let server_state = std::sync::Arc::new(ServerState { + router: build_router(), + application_state, + }); + server_builder.serve(route_request, server_state) +} +fn build_router() -> pavex_matchit::Router { + let mut router = pavex_matchit::Router::new(); + router +} +async fn route_request( + request: http::Request, + server_state: std::sync::Arc, +) -> pavex::response::Response { + let (request_head, request_body) = request.into_parts(); + #[allow(unused)] + let request_body = pavex::request::body::RawIncomingBody::from(request_body); + let request_head: pavex::request::RequestHead = request_head.into(); + let matched_route = match server_state.router.at(&request_head.target.path()) { + Ok(m) => m, + Err(_) => { + let allowed_methods: pavex::router::AllowedMethods = pavex::router::MethodAllowList::from_iter( + vec![], + ) + .into(); + return route_0::handler(&allowed_methods).await; + } + }; + let route_id = matched_route.value; + #[allow(unused)] + let url_params: pavex::request::path::RawPathParams<'_, '_> = matched_route + .params + .into(); + match route_id { + i => unreachable!("Unknown route id: {}", i), + } +} +pub mod route_0 { + pub async fn handler( + v0: &pavex::router::AllowedMethods, + ) -> pavex::response::Response { + let v1 = pavex::router::default_fallback(v0).await; + ::into_response(v1) + } +} \ No newline at end of file diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/diagnostics.dot b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/diagnostics.dot new file mode 100644 index 000000000..d42a8f858 --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/expectations/diagnostics.dot @@ -0,0 +1,3 @@ +digraph app_state { + 0 [ label = "crate::ApplicationState() -> crate::ApplicationState"] +} \ No newline at end of file diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/lib.rs b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/lib.rs new file mode 100644 index 000000000..5f370f69d --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/lib.rs @@ -0,0 +1,17 @@ +use pavex::blueprint::{constructor::Lifecycle, linter::Lint, Blueprint}; +use pavex::f; + +pub struct Unused; + +impl Unused { + pub fn new() -> Self { + Self + } +} + +pub fn blueprint() -> Blueprint { + let mut bp = Blueprint::new(); + bp.constructor(f!(crate::Unused::new), Lifecycle::RequestScoped) + .ignore(Lint::Unused); + bp +} diff --git a/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/test_config.toml b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/test_config.toml new file mode 100644 index 000000000..5e47382ca --- /dev/null +++ b/libs/pavex_cli/tests/ui_tests/blueprint/constructors/unused_constructor_warning_can_be_ignored/test_config.toml @@ -0,0 +1,7 @@ +description = """You can suppress the unused warning constructor""" + +[expectations] +codegen = "pass" +lints = "pass" + + diff --git a/libs/pavex_test_runner/src/lib.rs b/libs/pavex_test_runner/src/lib.rs index fd9613bca..cf5f2c329 100644 --- a/libs/pavex_test_runner/src/lib.rs +++ b/libs/pavex_test_runner/src/lib.rs @@ -147,12 +147,15 @@ struct TestExpectations { /// returned by Pavex to the user. #[serde(default = "ExpectedOutcome::pass")] codegen: ExpectedOutcome, + #[serde(default = "ExpectedOutcome::pass")] + lints: ExpectedOutcome, } impl Default for TestExpectations { fn default() -> Self { Self { codegen: ExpectedOutcome::Pass, + lints: ExpectedOutcome::Pass, } } } @@ -582,6 +585,20 @@ fn _run_test( }); }; + if let ExpectedOutcome::Fail = test_config.expectations.lints { + let stderr_snapshot = SnapshotTest::new(expectations_directory.join("stderr.txt")); + if stderr_snapshot.verify(&codegen_output.stderr).is_err() { + return Ok(TestOutcome { + outcome: Err( + "The warnings returned by code generation don't match what we expected".into(), + ), + codegen_output, + compilation_output: None, + test_output: None, + }); + } + } + let diagnostics_snapshot = SnapshotTest::new(expectations_directory.join("diagnostics.dot")); let actual_diagnostics = fs_err::read_to_string(test.test_runtime_directory().join("diagnostics.dot"))?; diff --git a/libs/pavexc/src/compiler/analyses/call_graph/application_state.rs b/libs/pavexc/src/compiler/analyses/call_graph/application_state.rs index d36d8c2d3..0856b9750 100644 --- a/libs/pavexc/src/compiler/analyses/call_graph/application_state.rs +++ b/libs/pavexc/src/compiler/analyses/call_graph/application_state.rs @@ -90,6 +90,7 @@ pub(crate) fn application_state_call_graph( application_state_scope_id, CloningStrategy::NeverClone, computation_db, + None, ) .unwrap(); let Ok(CallGraph { diff --git a/libs/pavexc/src/compiler/analyses/components.rs b/libs/pavexc/src/compiler/analyses/components.rs index fe3f13399..26a3846ba 100644 --- a/libs/pavexc/src/compiler/analyses/components.rs +++ b/libs/pavexc/src/compiler/analyses/components.rs @@ -1,14 +1,14 @@ use std::borrow::Cow; +use std::collections::BTreeMap; -use ahash::{HashMap, HashMapExt}; -use bimap::BiHashMap; +use ahash::{HashMap, HashMapExt, HashSet}; use guppy::graph::PackageGraph; use indexmap::IndexSet; use miette::NamedSource; use rustdoc_types::ItemEnum; use syn::spanned::Spanned; -use pavex_bp_schema::{CloningStrategy, Lifecycle}; +use pavex_bp_schema::{CloningStrategy, Lifecycle, Lint, LintSetting}; use crate::compiler::analyses::computations::{ComputationDb, ComputationId}; use crate::compiler::analyses::into_error::register_error_new_transformer; @@ -121,6 +121,13 @@ pub(crate) enum UnregisteredComponent { computation_id: ComputationId, scope_id: ScopeId, cloning_strategy: CloningStrategy, + /// Synthetic constructors can be built by "deriving" user-registered constructors. + /// For example, by binding unassigned generic parameters or by extracting the `Ok` variant + /// from the output of fallible constructors. + /// + /// If that's the case, + /// this field should be populated with the id of the "source" constructor. + derived_from: Option, }, Transformer { computation_id: ComputationId, @@ -310,7 +317,6 @@ pub(crate) struct ComponentDb { match_err_id2error_handler_id: HashMap, fallible_id2match_ids: HashMap, match_id2fallible_id: HashMap, - borrow_id2owned_id: BiHashMap, id2transformer_ids: HashMap>, id2lifecycle: HashMap, /// For each constructor component, determine if it can be cloned or not. @@ -346,9 +352,34 @@ pub(crate) struct ComponentDb { /// - match request handlers with the sequence of middlewares that wrap around them. /// - convert the ids in the router. user_component_id2component_id: HashMap, + /// For each scope, it stores the ordered list of error observers that should be + /// invoked if a component fails in that scope. scope_ids_with_observers: Vec, + /// A switch to control if, when a fallible component is registered, [`ComponentDb`] + /// should automatically register matcher components for its output type. autoregister_matchers: bool, + /// The resolved path to `pavex::Error`. + /// It's memoised here to avoid re-resolving it multiple times while analysing a single + /// blueprint. pavex_error: PathType, + /// Users register constructors directly with a blueprint. + /// From these user-provided constructors, we build **derived** constructors: + /// - if a constructor is fallible, + /// we create a synthetic constructor to retrieve the Ok variant of its output type + /// - if a constructor is generic, we create new synthetic constructors by binding its unassigned generic parameters + /// to concrete types + /// + /// This map holds an entry for each derived constructor. + /// The value points to the original user-registered constructor it was derived from. + /// + /// This dependency relationship can be **indirect**β€”e.g. an Ok-matcher is derived from a fallible constructor + /// which was in turn derived + /// by binding the generic parameters of a user-registered constructor. + /// The key for the Ok-matcher would point to the user-registered constructor in this scenario, + /// not to the intermediate derived constructor. + derived2user_registered: HashMap, + /// The id for all framework primitivesβ€”i.e. components coming from [`FrameworkItemDb`]. + framework_primitive_ids: HashSet, } /// The `build` method and its auxiliary routines. @@ -384,7 +415,6 @@ impl ComponentDb { match_err_id2error_handler_id: Default::default(), fallible_id2match_ids: Default::default(), match_id2fallible_id: Default::default(), - borrow_id2owned_id: Default::default(), id2transformer_ids: Default::default(), id2lifecycle: Default::default(), constructor_id2cloning_strategy: Default::default(), @@ -397,6 +427,8 @@ impl ComponentDb { scope_ids_with_observers: vec![], autoregister_matchers: false, pavex_error, + derived2user_registered: Default::default(), + framework_primitive_ids: Default::default(), }; { @@ -473,7 +505,7 @@ impl ComponentDb { ); for (id, type_) in framework_item_db.iter() { - self_.get_or_intern( + let component_id = self_.get_or_intern( UnregisteredComponent::SyntheticConstructor { computation_id: computation_db.get_or_intern(Constructor( Computation::FrameworkItem(Cow::Owned(type_.clone())), @@ -481,9 +513,11 @@ impl ComponentDb { scope_id: self_.scope_graph().root_scope_id(), lifecycle: framework_item_db.lifecycle(id), cloning_strategy: framework_item_db.cloning_strategy(id), + derived_from: None, }, computation_db, ); + self_.framework_primitive_ids.insert(component_id); } // Add a synthetic constructor for the `pavex::middleware::Next` type. @@ -500,6 +534,7 @@ impl ComponentDb { scope_id: self_.scope_graph().root_scope_id(), lifecycle: Lifecycle::RequestScoped, cloning_strategy: CloningStrategy::NeverClone, + derived_from: None, }, computation_db, ); @@ -556,10 +591,21 @@ impl ComponentDb { .insert(id, cloning_strategy.to_owned()); } SyntheticConstructor { - cloning_strategy, .. + cloning_strategy, + derived_from, + .. } => { self.constructor_id2cloning_strategy .insert(id, cloning_strategy); + if let Some(derived_from) = derived_from { + self.derived2user_registered.insert( + id, + self.derived2user_registered + .get(&derived_from) + .cloned() + .unwrap_or(derived_from), + ); + } } Transformer { when_to_insert, @@ -608,15 +654,17 @@ impl ComponentDb { let ok_id = match self.hydrated_component(id, computation_db) { HydratedComponent::Constructor(_) => { let ok_computation_id = computation_db.get_or_intern(ok); - self.get_or_intern( + let ok_id = self.get_or_intern( UnregisteredComponent::SyntheticConstructor { computation_id: ok_computation_id, scope_id: self.scope_id(id), lifecycle: self.lifecycle(id), cloning_strategy: self.constructor_id2cloning_strategy[&id], + derived_from: Some(id), }, computation_db, - ) + ); + ok_id } _ => self.add_synthetic_transformer( ok.into(), @@ -1123,6 +1171,7 @@ impl ComponentDb { scope_id: ScopeId, cloning_strategy: CloningStrategy, computation_db: &mut ComputationDb, + derived_from: Option, ) -> Result { let callable = computation_db[callable_id].to_owned(); TryInto::::try_into(callable)?; @@ -1131,6 +1180,7 @@ impl ComponentDb { computation_id: callable_id, scope_id, cloning_strategy, + derived_from, }; Ok(self.get_or_intern(constructor_component, computation_db)) } @@ -1175,6 +1225,14 @@ impl ComponentDb { self.id2lifecycle[&id] } + /// Retrieve the lint overrides for a component. + pub fn lints(&self, id: ComponentId) -> Option<&BTreeMap> { + let Some(user_component_id) = self.user_component_id(id) else { + return None; + }; + self.user_component_db.get_lints(user_component_id) + } + /// The mapping from a low-level [`UserComponentId`] to its corresponding [`ComponentId`]. pub fn user_component_id2component_id(&self) -> &HashMap { &self.user_component_id2component_id @@ -1249,13 +1307,25 @@ impl ComponentDb { derived_ids.extend(self.derived_component_ids(match_ids.0)); derived_ids.extend(self.derived_component_ids(match_ids.1)); } - if let Some(borrow_id) = self.borrow_id2owned_id.get_by_right(&component_id) { - derived_ids.push(*borrow_id); - derived_ids.extend(self.derived_component_ids(*borrow_id)); - } derived_ids } + /// Return the id of user-registered component that `component_id` was derived from + /// (e.g. an Ok-matcher is derived from a fallible constructor or + /// a bound constructor from a generic user-registered one). + /// + /// **It only works for constructors**. + pub fn derived_from(&self, component_id: &ComponentId) -> Option { + self.derived2user_registered.get(component_id).cloned() + } + + /// Returns `true` if the component is a framework primitive (i.e. it comes from + /// [`FrameworkItemDb`]. + /// `false` otherwise. + pub fn is_framework_primitive(&self, component_id: &ComponentId) -> bool { + self.framework_primitive_ids.contains(component_id) + } + /// Given the id of a [`MatchResult`] component, return the id of the corresponding fallible /// component. #[track_caller] @@ -1505,6 +1575,7 @@ impl ComponentDb { scope_id, cloning_strategy, computation_db, + Some(id), ) .unwrap() } diff --git a/libs/pavexc/src/compiler/analyses/mod.rs b/libs/pavexc/src/compiler/analyses/mod.rs index cc1e6583c..db418eabd 100644 --- a/libs/pavexc/src/compiler/analyses/mod.rs +++ b/libs/pavexc/src/compiler/analyses/mod.rs @@ -6,4 +6,5 @@ pub(crate) mod framework_items; pub(crate) mod into_error; pub(crate) mod processing_pipeline; pub(crate) mod router; +pub(crate) mod unused; pub(crate) mod user_components; diff --git a/libs/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs b/libs/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs index 500e45288..0c5ad6728 100644 --- a/libs/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs +++ b/libs/pavexc/src/compiler/analyses/processing_pipeline/pipeline.rs @@ -213,6 +213,7 @@ impl RequestHandlerPipeline { next_state_scope_id, CloningStrategy::NeverClone, computation_db, + None, ) .unwrap(); constructible_db.insert(next_state_constructor_id, component_db, computation_db); diff --git a/libs/pavexc/src/compiler/analyses/unused.rs b/libs/pavexc/src/compiler/analyses/unused.rs new file mode 100644 index 000000000..9244fc4d0 --- /dev/null +++ b/libs/pavexc/src/compiler/analyses/unused.rs @@ -0,0 +1,131 @@ +use crate::compiler::analyses::call_graph::ApplicationStateCallGraph; +use crate::compiler::analyses::components::{ComponentDb, ComponentId, HydratedComponent}; +use crate::compiler::analyses::computations::ComputationDb; +use crate::compiler::analyses::processing_pipeline::RequestHandlerPipeline; +use crate::compiler::computation::Computation; +use crate::compiler::utils::get_ok_variant; +use crate::diagnostic; +use crate::diagnostic::{CompilerDiagnostic, LocationExt, SourceSpanExt}; +use guppy::graph::PackageGraph; +use indexmap::IndexSet; +use miette::Severity; +use pavex_bp_schema::{Lint, LintSetting}; + +/// Emit a warning for each user-registered constructor that hasn't +/// been used in the code-generated pipelines. +pub(crate) fn detect_unused<'a, I>( + pipelines: I, + application_state_call_graph: &ApplicationStateCallGraph, + component_db: &ComponentDb, + computation_db: &ComputationDb, + package_graph: &PackageGraph, + diagnostics: &mut Vec, +) where + I: Iterator, +{ + // Some user-registered constructors are never used directlyβ€”e.g. a constructor with + // unassigned generic parameters. + // We consider the original user-registered constructor as "used" if one of its derivations + // is used. + let mut used_user_constructor_ids = IndexSet::new(); + + let graphs = pipelines + .flat_map(|p| p.graph_iter()) + .chain(std::iter::once(&application_state_call_graph.call_graph)); + + for graph in graphs { + for node in graph.call_graph.node_weights() { + let Some(component_id) = node.component_id() else { + continue; + }; + let HydratedComponent::Constructor(_) = + component_db.hydrated_component(component_id, computation_db) + else { + continue; + }; + used_user_constructor_ids.insert( + component_db + .derived_from(&component_id) + .unwrap_or(component_id), + ); + } + } + + for (id, _) in component_db.constructors(computation_db) { + if component_db.derived_from(&id).is_some() || component_db.is_framework_primitive(&id) { + // It's not a user-registered constructor. + continue; + } + if used_user_constructor_ids.contains(&id) { + continue; + } + + if let Some(overrides) = component_db.lints(id) { + if overrides.get(&Lint::Unused) == Some(&LintSetting::Ignore) { + // No warning! + continue; + } + } + + emit_unused_warning(id, component_db, computation_db, diagnostics, package_graph); + } +} + +fn emit_unused_warning( + constructor_id: ComponentId, + component_db: &ComponentDb, + computation_db: &ComputationDb, + diagnostics: &mut Vec, + package_graph: &PackageGraph, +) { + let Some(user_component_id) = component_db.user_component_id(constructor_id) else { + return; + }; + let location = component_db + .user_component_db() + .get_location(user_component_id); + let source = match location.source_file(package_graph) { + Ok(s) => { + let span = diagnostic::get_f_macro_invocation_span(&s, location) + .map(|s| s.labeled("The unused constructor was registered here".into())); + Some((s, span)) + } + Err(e) => { + diagnostics.push(e.into()); + None + } + }; + let HydratedComponent::Constructor(constructor) = + component_db.hydrated_component(constructor_id, computation_db) + else { + unreachable!() + }; + let output_type = constructor.output_type(); + let output_type = if output_type.is_result() { + get_ok_variant(output_type) + } else { + output_type + }; + let Computation::Callable(callable) = &constructor.0 else { + unreachable!() + }; + let error = anyhow::anyhow!( + "You registered a constructor for `{output_type:?}`, \ + but it's never used.\n\ + `{}` is never invoked since no component is asking for `{output_type:?}` to be injected as one of its inputs.", + &callable.path + ); + let builder = match source { + None => CompilerDiagnostic::builder_without_source(error), + Some((source, labeled_span)) => { + CompilerDiagnostic::builder(source, error).optional_label(labeled_span) + } + } + .severity(Severity::Warning) + .help( + "If you want to ignore this warning, call `.ignore(Lint::Unused)` \ + on the registered constructor." + .to_string(), + ); + diagnostics.push(builder.build().into()) +} diff --git a/libs/pavexc/src/compiler/analyses/user_components/processed_db.rs b/libs/pavexc/src/compiler/analyses/user_components/processed_db.rs index 668b1b6d4..5c3c43fb6 100644 --- a/libs/pavexc/src/compiler/analyses/user_components/processed_db.rs +++ b/libs/pavexc/src/compiler/analyses/user_components/processed_db.rs @@ -2,9 +2,12 @@ use ahash::HashMap; use guppy::graph::PackageGraph; use indexmap::IndexSet; use miette::{miette, NamedSource}; +use std::collections::BTreeMap; use syn::spanned::Spanned; -use pavex_bp_schema::{Blueprint, CloningStrategy, Lifecycle, Location, RawCallableIdentifiers}; +use pavex_bp_schema::{ + Blueprint, CloningStrategy, Lifecycle, Lint, LintSetting, Location, RawCallableIdentifiers, +}; use crate::compiler::analyses::computations::ComputationDb; use crate::compiler::analyses::user_components::raw_db::RawUserComponentDb; @@ -45,6 +48,9 @@ pub struct UserComponentDb { /// /// Invariants: there is an entry for every single user component. id2lifecycle: HashMap, + /// Associate each user-registered component with its lint overrides, if any. + /// If there is no entry for a component, there are no overrides. + id2lints: HashMap>, /// For each constructor component, determine if it can be cloned or not. /// /// Invariants: there is an entry for every constructor. @@ -105,6 +111,7 @@ impl UserComponentDb { let RawUserComponentDb { component_interner, id2locations, + id2lints, constructor_id2cloning_strategy, id2lifecycle, identifiers_interner, @@ -124,6 +131,7 @@ impl UserComponentDb { handler_id2middleware_ids, handler_id2error_observer_ids, scope_graph, + id2lints, }, )) } @@ -221,6 +229,11 @@ impl UserComponentDb { &self.handler_id2middleware_ids[&id] } + /// Return the lint overrides for this component, if any. + pub fn get_lints(&self, id: UserComponentId) -> Option<&BTreeMap> { + self.id2lints.get(&id) + } + /// Return the ids of the error observers that must be invoked when something goes wrong /// in the request processing pipeline for this handler. /// diff --git a/libs/pavexc/src/compiler/analyses/user_components/raw_db.rs b/libs/pavexc/src/compiler/analyses/user_components/raw_db.rs index da95c72d7..5c1d8e0d4 100644 --- a/libs/pavexc/src/compiler/analyses/user_components/raw_db.rs +++ b/libs/pavexc/src/compiler/analyses/user_components/raw_db.rs @@ -1,10 +1,12 @@ use ahash::{HashMap, HashMapExt}; use anyhow::anyhow; use guppy::graph::PackageGraph; +use std::collections::BTreeMap; use pavex_bp_schema::{ Blueprint, Callable, CloningStrategy, Component, Constructor, ErrorObserver, Fallback, - Lifecycle, Location, NestedBlueprint, RawCallableIdentifiers, Route, WrappingMiddleware, + Lifecycle, Lint, LintSetting, Location, NestedBlueprint, RawCallableIdentifiers, Route, + WrappingMiddleware, }; use crate::compiler::analyses::user_components::router_key::RouterKey; @@ -149,6 +151,9 @@ pub(super) struct RawUserComponentDb { /// /// Invariants: there is an entry for every single user component. pub(super) id2lifecycle: HashMap, + /// Associate each user-registered component with its lint overrides, if any. + /// If there is no entry for a component, there are no overrides. + pub(super) id2lints: HashMap>, /// For each constructor component, determine if it can be cloned or not. /// /// Invariants: there is an entry for every constructor. @@ -195,6 +200,7 @@ impl RawUserComponentDb { identifiers_interner: Interner::new(), id2locations: HashMap::new(), id2lifecycle: HashMap::new(), + id2lints: HashMap::new(), constructor_id2cloning_strategy: HashMap::new(), handler_id2middleware_ids: HashMap::new(), handler_id2error_observer_ids: HashMap::new(), @@ -527,6 +533,10 @@ impl RawUserComponentDb { .cloning_strategy .unwrap_or(CloningStrategy::NeverClone), ); + if !constructor.lints.is_empty() { + self.id2lints + .insert(constructor_id, constructor.lints.clone()); + } self.process_error_handler( &constructor.error_handler, diff --git a/libs/pavexc/src/compiler/app.rs b/libs/pavexc/src/compiler/app.rs index e17a3883a..6943229ca 100644 --- a/libs/pavexc/src/compiler/app.rs +++ b/libs/pavexc/src/compiler/app.rs @@ -24,6 +24,7 @@ use crate::compiler::analyses::constructibles::ConstructibleDb; use crate::compiler::analyses::framework_items::FrameworkItemDb; use crate::compiler::analyses::processing_pipeline::RequestHandlerPipeline; use crate::compiler::analyses::router::Router; +use crate::compiler::analyses::unused::detect_unused; use crate::compiler::analyses::user_components::UserComponentDb; use crate::compiler::computation::Computation; use crate::compiler::generated_app::GeneratedApp; @@ -60,11 +61,19 @@ impl App { /// /// Many different things can go wrong during this process: this method tries its best to /// report all errors to the user, but it may not be able to do so in all cases. - pub fn build(bp: Blueprint, project_fingerprint: String) -> Result> { + pub fn build( + bp: Blueprint, + project_fingerprint: String, + ) -> Result<(Self, Vec), Vec> { /// Exit early if there is at least one error. macro_rules! exit_on_errors { ($var:ident) => { - if !$var.is_empty() { + if !$var.is_empty() + && $var.iter().any(|e| { + let severity = e.severity(); + severity == Some(miette::Severity::Error) || severity.is_none() + }) + { return Err($var); } }; @@ -174,18 +183,29 @@ impl App { ) else { return Err(diagnostics); }; + detect_unused( + handler_id2pipeline.values(), + &application_state_call_graph, + &component_db, + &computation_db, + &package_graph, + &mut diagnostics, + ); exit_on_errors!(diagnostics); - Ok(Self { - package_graph, - router, - handler_id2pipeline, - component_db, - computation_db, - application_state_call_graph, - framework_item_db, - runtime_singleton_bindings, - codegen_deps, - }) + Ok(( + Self { + package_graph, + router, + handler_id2pipeline, + component_db, + computation_db, + application_state_call_graph, + framework_item_db, + runtime_singleton_bindings, + codegen_deps, + }, + diagnostics, + )) } /// Generate the manifest and the Rust code for the analysed application. diff --git a/libs/pavexc/src/diagnostic/compiler_diagnostic.rs b/libs/pavexc/src/diagnostic/compiler_diagnostic.rs index adc10a897..f4dbf2eb2 100644 --- a/libs/pavexc/src/diagnostic/compiler_diagnostic.rs +++ b/libs/pavexc/src/diagnostic/compiler_diagnostic.rs @@ -26,6 +26,11 @@ impl CompilerDiagnosticBuilder { } } + pub fn severity(mut self, severity: Severity) -> Self { + self.severity = severity; + self + } + pub fn label(self, label: LabeledSpan) -> Self { self.labels(std::iter::once(label)) } diff --git a/libs/pavexc_cli/src/main.rs b/libs/pavexc_cli/src/main.rs index db384f8bb..450dab96b 100644 --- a/libs/pavexc_cli/src/main.rs +++ b/libs/pavexc_cli/src/main.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use anyhow::Context; use clap::{Parser, Subcommand}; use generate_from_path::GenerateArgs; +use miette::Severity; use owo_colors::OwoColorize; use pavexc::App; use supports_color::Stream; @@ -170,7 +171,17 @@ fn generate( // We use the path to the generated application crate as a fingerprint for the project. let project_fingerprint = output.to_string_lossy().into_owned(); let app = match App::build(blueprint, project_fingerprint) { - Ok(a) => a, + Ok((a, warnings)) => { + for e in warnings { + assert_eq!(e.severity(), Some(Severity::Warning)); + if color_on_stderr { + eprintln!("{}: {e:?}", "WARNING".bold().yellow()); + } else { + eprintln!("WARNING: {e:?}"); + }; + } + a + } Err(errors) => { for e in errors { if color_on_stderr {