From f589e1583e21d62561d4b2f291410d12fd9b867e Mon Sep 17 00:00:00 2001 From: TheJanzap <16736682+TheJanzap@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:59:19 +0100 Subject: [PATCH 1/4] docs(config): Add error docs to config crate --- blueprint/config/src/lib.rs.liquid | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/blueprint/config/src/lib.rs.liquid b/blueprint/config/src/lib.rs.liquid index 1ef58751..017a48f4 100644 --- a/blueprint/config/src/lib.rs.liquid +++ b/blueprint/config/src/lib.rs.liquid @@ -113,10 +113,13 @@ pub struct DatabaseConfig { /// you can set that location using the `APP_DOTENV_CONFIG_DIR` environment variable. /// This is useful when they are mounted at separate locations in a Docker container, for example. /// -/// Configuration settings are loaded from these sources (in that order so that latter soruces override former): +/// Configuration settings are loaded from these sources (in that order so that latter sources override the former): /// * the `config/app.toml` file /// * the `config/environments/.toml` files depending on the environment /// * environment variables +/// +/// # Errors +/// Returns an error if any of the configuration sources could not be read. pub fn load_config<'a, T>(env: &Environment) -> Result where T: Deserialize<'a>, @@ -186,6 +189,9 @@ impl Display for Environment { /// Returns the currently active environment. /// /// If the `APP_ENVIRONMENT` env var is set, the application environment is parsed from that (which might fail if an invalid environment is set). If the env var is not set, [`Environment::Development`] is returned. +/// +/// # Errors +/// Returns an error if the `APP_ENVIRONMENT` env var is set to an invalid environment. pub fn get_env() -> Result { if let Ok(val) = env::var("APP_ENVIRONMENT") { info!(r#"Setting environment from APP_ENVIRONMENT: "{}""#, val); @@ -199,6 +205,9 @@ pub fn get_env() -> Result { /// Parses an [`Environment`] from a string. /// /// The environment can be passed in different forms, e.g. "dev", "development", "prod", etc. If an invalid environment is passed, an error is returned. +/// +/// # Errors +/// Returns an error if the environment is not one of `dev`, `development`, `prod`, `production` or `test`. pub fn parse_env(env: &str) -> Result { let env = &env.to_lowercase(); match env.as_str() { From e10797d624f06f583b71070f5615373386d6c44b Mon Sep 17 00:00:00 2001 From: TheJanzap <16736682+TheJanzap@users.noreply.github.com> Date: Sat, 21 Mar 2026 17:25:11 +0100 Subject: [PATCH 2/4] docs(db): Add error and panic docs to db crate --- blueprint/db/src/entities/tasks.rs | 20 +++++++++++++++----- blueprint/db/src/entities/users.rs | 5 ++++- blueprint/db/src/lib.rs | 6 ++++++ blueprint/db/src/test_helpers/mod.rs.liquid | 6 ++++++ blueprint/db/src/test_helpers/users.rs | 3 ++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/blueprint/db/src/entities/tasks.rs b/blueprint/db/src/entities/tasks.rs index 8d771883..4936aedc 100644 --- a/blueprint/db/src/entities/tasks.rs +++ b/blueprint/db/src/entities/tasks.rs @@ -17,7 +17,7 @@ pub struct Task { /// A changeset representing the data that is intended to be used to either create a new task or update an existing task. /// -/// Changesets are validatated in the [`create`] and [`update`] functions which return an [Result::Err] if validation fails. +/// Changesets are validated in the [`create`] and [`update`] functions which return an [`Result::Err`] if validation fails. /// /// Changesets can also be used to generate fake data for tests when the `test-helpers` feature is enabled: /// @@ -34,6 +34,8 @@ pub struct TaskChangeset { } /// Load all [`Task`]s from the database. +/// # Errors +/// - If there is a generic database error, a [`crate::Error::DbError`] will be returned. pub async fn load_all( executor: impl sqlx::Executor<'_, Database = Postgres>, ) -> Result, crate::Error> { @@ -45,7 +47,9 @@ pub async fn load_all( /// Load one [`Task`] from the database identified by its ID. /// -/// If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. +/// # Errors +/// - If there is a generic database error, a [`crate::Error::DbError`] will be returned. +/// - If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. pub async fn load( id: Uuid, executor: impl sqlx::Executor<'_, Database = Postgres>, @@ -59,7 +63,9 @@ pub async fn load( /// Delete a [`Task`] from the database identified by its ID. /// -/// If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. +/// # Errors +/// - If there is a generic database error, a [`crate::Error::DbError`] will be returned. +/// - If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. pub async fn delete( id: Uuid, executor: impl sqlx::Executor<'_, Database = Postgres>, @@ -75,7 +81,9 @@ pub async fn delete( /// Create a task in the database with the data in the passed [`TaskChangeset`]. /// -/// If the data in the changeset isn't valid, a [`crate::Error::ValidationError`] will be returned, otherwise the created task is returned. +/// # Errors +/// - If there is a generic database error, a [`crate::Error::DbError`] will be returned. +/// - If the data in the changeset isn't valid, a [`crate::Error::ValidationError`] will be returned, otherwise the created task is returned. pub async fn create( task: TaskChangeset, executor: impl sqlx::Executor<'_, Database = Postgres>, @@ -98,7 +106,9 @@ pub async fn create( /// Updates a task in the database with the data in the passed [`TaskChangeset`]. /// -/// If the data in the changeset isn't valid, a [`crate::Error::ValidationError`] will be returned, otherwise the updated [`Task`] is returned. If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. +/// # Errors +/// - If there is a generic database error, a [`crate::Error::DbError`] will be returned. +/// - If the data in the changeset isn't valid, a [`crate::Error::ValidationError`] will be returned, otherwise the updated [`Task`] is returned. If no record can be found for the ID, a [`crate::Error::NoRecordFound`] will be returned. pub async fn update( id: Uuid, task: TaskChangeset, diff --git a/blueprint/db/src/entities/users.rs b/blueprint/db/src/entities/users.rs index b32dd780..8afed767 100644 --- a/blueprint/db/src/entities/users.rs +++ b/blueprint/db/src/entities/users.rs @@ -14,10 +14,13 @@ pub struct User { /// Loads a user based on the passed token. /// /// If no user exists for the token, [`Option::None`] is returned, otherwise `Option::Some(User)` is returned. +/// +/// # Errors +/// Returns [`crate::Error::DbError`] if a general database error occurs. pub async fn load_with_token( token: &str, executor: impl sqlx::Executor<'_, Database = Postgres>, -) -> Result, anyhow::Error> { +) -> Result, crate::Error> { Ok( sqlx::query_as!(User, "SELECT id, name FROM users WHERE token = $1", token) .fetch_optional(executor) diff --git a/blueprint/db/src/lib.rs b/blueprint/db/src/lib.rs index e694afe7..30cc7119 100644 --- a/blueprint/db/src/lib.rs +++ b/blueprint/db/src/lib.rs @@ -25,6 +25,9 @@ pub mod entities; /// ``` /// /// Transactions are rolled back automatically when they are dropped without having been committed. +/// +/// # Errors +/// Returns a [`sqlx_core::error`] when the transaction is unsuccessful. pub async fn transaction( db_pool: &DbPool, ) -> Result, anyhow::Error> { @@ -54,6 +57,9 @@ pub enum Error { } /// Creates a connection pool to the database specified in the passed [`{{project-name}}-config::DatabaseConfig`] +/// +/// # Errors +/// Returns a [`sqlx_core::error`] when the transaction is unsuccessful. pub async fn connect_pool(config: DatabaseConfig) -> Result { let pool = PgPoolOptions::new() .connect(config.url.as_str()) diff --git a/blueprint/db/src/test_helpers/mod.rs.liquid b/blueprint/db/src/test_helpers/mod.rs.liquid index 6a599c36..fd1ab4f5 100644 --- a/blueprint/db/src/test_helpers/mod.rs.liquid +++ b/blueprint/db/src/test_helpers/mod.rs.liquid @@ -17,6 +17,9 @@ pub mod users; /// /// This sets up a dedicated database as a fork of the main test database as configured in `.env.test`. The database can be used in a test case to ensure the test case is isolated from other test cases. The function returns a connection pool connected to the created database. /// This function is automatically called by the [`{{project-name}}-macros::db_test`] macro. The return connection pool is passed to the test case via the [`{{project-name}}-macros::DbTestContext`]. +/// +/// # Panics +/// This function panics if it cannot connect to the database. #[allow(unused)] pub async fn setup_db(config: &DatabaseConfig) -> DbPool { let test_db_config = prepare_db(config).await; @@ -28,6 +31,9 @@ pub async fn setup_db(config: &DatabaseConfig) -> DbPool { /// Drops a dedicated database for a test case. /// /// This function is automatically called by the [`{{project-name}}-macros::db_test`] macro. It ensures test-specific database are cleaned up after each test run so we don't end up with large numbers of unused databases. +/// +/// # Panics +/// This function panics if connection to the database fails, the database name cannot be retrieved, or the database cannot be dropped. pub async fn teardown_db(db_pool: DbPool) { let mut connect_options = db_pool.connect_options(); let db_config = Arc::make_mut(&mut connect_options); diff --git a/blueprint/db/src/test_helpers/users.rs b/blueprint/db/src/test_helpers/users.rs index a2e8f901..b3fbd2aa 100644 --- a/blueprint/db/src/test_helpers/users.rs +++ b/blueprint/db/src/test_helpers/users.rs @@ -4,7 +4,7 @@ use sqlx::postgres::PgPool; /// A changeset representing the data that is intended to be used to either create a new user or update an existing user. /// -/// Changesets are validated in the [`create`] function which return an [Result::Err] if validation fails. +/// Changesets are validated in the [`create`] function that returns an [`Result::Err`] if validation fails. /// /// Changesets can also be used to generate fake data for tests when the `test-helpers` feature is enabled: /// @@ -23,6 +23,7 @@ pub struct UserChangeset { /// Creates a user in the database with the data in the passed [`UserChangeset`]. /// +/// # Errors /// If the data in the changeset isn't valid, a [`crate::Error::ValidationError`] will be returned, otherwise the created user is returned. pub async fn create(user: UserChangeset, db: &PgPool) -> Result { let record = sqlx::query!( From e65054c4450f5d1e665e16cf92e2a49a4af1629b Mon Sep 17 00:00:00 2001 From: TheJanzap <16736682+TheJanzap@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:15:00 +0100 Subject: [PATCH 3/4] docs(web): Add error and panic docs to web crate --- blueprint/web/src/controllers/tasks.rs | 31 ++++++++++++++++---- blueprint/web/src/lib.rs | 9 ++++++ blueprint/web/src/middlewares/auth.rs | 5 ++-- blueprint/web/src/state.rs.liquid | 2 ++ blueprint/web/src/test_helpers/mod.rs.liquid | 6 ++++ 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/blueprint/web/src/controllers/tasks.rs b/blueprint/web/src/controllers/tasks.rs index 196da09f..5749f61c 100644 --- a/blueprint/web/src/controllers/tasks.rs +++ b/blueprint/web/src/controllers/tasks.rs @@ -6,7 +6,10 @@ use uuid::Uuid; /// Creates a task in the database. /// -/// This function creates a task in the database (see [`{{crate_name}}_db::entities::tasks::create`]) based on a [`{{crate_name}}_db::entities::tasks::TaskChangeset`] (sent as JSON). If the task is created successfully, a 201 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]'s JSON representation in the response body. If the changeset is invalid, a 422 response is returned. +/// This function creates a task in the database (see [`{{crate_name}}_db::entities::tasks::create`]) based on a [`{{crate_name}}_db::entities::tasks::TaskChangeset`] (sent as JSON). If the task is created successfully, a 201 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]'s JSON representation in the response body. +/// +/// # Errors +/// - 422 Unprocessable Entity: Invalid changeset #[axum::debug_handler] pub async fn create( State(app_state): State, @@ -19,9 +22,12 @@ pub async fn create( /// Creates multiple tasks in the database. /// -/// This function creates multiple tasks in the database (see [`{{crate_name}}_db::entities::tasks::create`]) based on [`{{crate_name}}_db::entities::tasks::TaskChangeset`]s (sent as JSON). If all tasks are created successfully, a 201 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]s' JSON representation in the response body. If any of the passed changesets is invalid, a 422 response is returned. +/// This function creates multiple tasks in the database (see [`{{crate_name}}_db::entities::tasks::create`]) based on [`{{crate_name}}_db::entities::tasks::TaskChangeset`]s (sent as JSON). If all tasks are created successfully, a 201 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]s' JSON representation in the response body. /// /// This function creates all tasks in a transaction so that either all are created successfully or none is. +/// +/// # Errors +/// - 422 Unprocessable Entity: Any changeset is invalid #[axum::debug_handler] pub async fn create_batch( State(app_state): State, @@ -43,6 +49,9 @@ pub async fn create_batch( /// Reads and responds with all the tasks currently present in the database. /// /// This function reads all [`{{crate_name}}_db::entities::tasks::Task`]s from the database (see [`{{crate_name}}_db::entities::tasks::load_all`]) and responds with their JSON representations. +/// +/// # Errors +/// - 500 Internal Server Error: Database connection failed or other generic database error #[axum::debug_handler] pub async fn read_all(State(app_state): State) -> Result>, Error> { let tasks = tasks::load_all(&app_state.db_pool).await?; @@ -54,7 +63,11 @@ pub async fn read_all(State(app_state): State) -> Result, @@ -66,7 +79,11 @@ pub async fn read_one( /// Updates a task in the database. /// -/// This function updates a task identified by its ID in the database (see [`{{crate_name}}_db::entities::tasks::update`]) with the data from the passed [`{{crate_name}}_db::entities::tasks::TaskChangeset`] (sent as JSON). If the task is updated successfully, a 200 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]'s JSON representation in the response body. If the changeset is invalid, a 422 response is returned. +/// This function updates a task identified by its ID in the database (see [`{{crate_name}}_db::entities::tasks::update`]) with the data from the passed [`{{crate_name}}_db::entities::tasks::TaskChangeset`] (sent as JSON). If the task is updated successfully, a 200 response is returned with the created [`{{crate_name}}_db::entities::tasks::Task`]'s JSON representation in the response body. +/// +/// # Errors +/// - 422 Unprocessable Entity: Invalid changeset +/// - 500 Internal Server Error: Database connection failed or other generic database error #[axum::debug_handler] pub async fn update( State(app_state): State, @@ -79,7 +96,11 @@ pub async fn update( /// Deletes a task identified by its ID from the database. /// -/// This function deletes one [`{{crate_name}}_db::entities::tasks::Task`] identified by the entity's id from the database (see [`{{crate_name}}_db::entities::tasks::delete`]) and responds with a 204 status code and empty response body. If no task is found for the ID, a 404 response is returned. +/// This function deletes one [`{{crate_name}}_db::entities::tasks::Task`] identified by the entity's id from the database (see [`{{crate_name}}_db::entities::tasks::delete`]) and responds with a 204 status code and empty response body. +/// +/// # Errors +/// - 404 Not found: No task with the given ID was found. +/// - 500 Internal Server Error: Database connection failed or other generic database error #[axum::debug_handler] pub async fn delete( State(app_state): State, diff --git a/blueprint/web/src/lib.rs b/blueprint/web/src/lib.rs index 424dac99..40d50446 100644 --- a/blueprint/web/src/lib.rs +++ b/blueprint/web/src/lib.rs @@ -28,6 +28,12 @@ pub mod error; /// 3. Initialize the application state (see [`state::init_app_state`]) /// 4. Initialize the application's router (see [`routes::init_routes`]) /// 5. Boot the application and start listening for requests on the configured interface and port +/// +/// # Errors +/// This function returns an Error if +/// - The environment was not found +/// - The configuration could not be loaded +/// - The server could not be bound to the specified address pub async fn run() -> anyhow::Result<()> { let env = get_env().context("Cannot get environment!")?; let config: Config = load_config(&env).context("Cannot load config!")?; @@ -51,6 +57,9 @@ pub async fn run() -> anyhow::Result<()> { /// * registers a [`tracing_panic::panic_hook`] /// /// The function respects the `RUST_LOG` if set or defaults to filtering spans and events with level [`tracing_subscriber::filter::LevelFilter::INFO`] and higher. +/// +/// # Panics +/// This function panics if the filter could not be parsed from `RUST_LOG` and a filter could not be constructed from `info`. pub fn init_tracing() { let filter = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new("info")) diff --git a/blueprint/web/src/middlewares/auth.rs b/blueprint/web/src/middlewares/auth.rs index 88845095..b1028fc9 100644 --- a/blueprint/web/src/middlewares/auth.rs +++ b/blueprint/web/src/middlewares/auth.rs @@ -9,9 +9,10 @@ use axum::{ use {{crate_name}}_db::entities::users; use tracing::Span; -/// Authenticates an incoming request based on an auth token. +/// Authenticates an incoming request based on an auth token. This looks for a token in the `Authorization` header. /// -/// This looks for a token in the `Authorization` header. If no token is present or no user exists with that token (see [`{{crate_name}}_db::entities::users::load_with_token`]), a 401 response code is returned and the request is not processed further. +/// # Errors +/// If no token is present or no user exists with that token (see [`{{crate_name}}_db::entities::users::load_with_token`]), a 401 response code is returned and the request is not processed further. #[tracing::instrument(skip_all, fields(rejection_reason = tracing::field::Empty))] pub async fn auth( State(app_state): State, diff --git a/blueprint/web/src/state.rs.liquid b/blueprint/web/src/state.rs.liquid index e9c5f72c..df6827b3 100644 --- a/blueprint/web/src/state.rs.liquid +++ b/blueprint/web/src/state.rs.liquid @@ -20,6 +20,8 @@ pub type SharedAppState = Arc; /// Initializes the application state. /// /// This function creates an [`AppState`] based on the current [`{{crate_name}}_config::Config`]. +/// # Panics +/// This function panics if it cannot connect to the database. {%- if template_type != "minimal" %} pub async fn init_app_state(config: Config) -> AppState { let db_pool = connect_pool(config.database) diff --git a/blueprint/web/src/test_helpers/mod.rs.liquid b/blueprint/web/src/test_helpers/mod.rs.liquid index fde8ec6f..7da0c6df 100644 --- a/blueprint/web/src/test_helpers/mod.rs.liquid +++ b/blueprint/web/src/test_helpers/mod.rs.liquid @@ -75,6 +75,8 @@ impl TestRequest { /// .header(.header(http::header::CONTENT_TYPE, "application/json")) /// .await; /// ``` + /// # Panics + /// This function panics if the header value could not be parsed. #[allow(unused)] #[must_use] pub fn header(mut self, name: HeaderName, value: &str) -> Self { @@ -105,6 +107,8 @@ impl TestRequest { } /// Sends the request to the application under test. + /// # Panics + /// This function panics if the response body could not be read. #[allow(unused)] pub async fn send(self) -> Response { let mut request_builder = Request::builder().uri(&self.uri); @@ -224,6 +228,8 @@ pub struct DbTestContext { /// This function initializes a new instance of the application under test using the configuration for [`{{crate_name}}_config::Environment::Test`]. The application is configured to use the same database that is also made available to the test itself via the test context. That database is a clone of the main test database that is only used by the particular test case to ensure isolation between test cases. It is automatically torn down after the test case completes (see [`teardown`]). /// /// This function is not invoked directly but used inside of the [`{{crate_name}}_macros::db_test`] attribute macro. The test context is automatically passed to test cases marked with that macro as an argument. +/// # Panics +/// This function panics if the `Test` configuration could not be loaded. #[allow(unused)] pub async fn setup() -> DbTestContext { let init_config: OnceCell = OnceCell::new(); From dbbfb04d89c6eea278dbfe82302dbae98526bdc4 Mon Sep 17 00:00:00 2001 From: TheJanzap <16736682+TheJanzap@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:38:13 +0100 Subject: [PATCH 4/4] docs(web): Fix panic doc of `test_helper::send()` --- blueprint/web/src/test_helpers/mod.rs.liquid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprint/web/src/test_helpers/mod.rs.liquid b/blueprint/web/src/test_helpers/mod.rs.liquid index 7da0c6df..98602769 100644 --- a/blueprint/web/src/test_helpers/mod.rs.liquid +++ b/blueprint/web/src/test_helpers/mod.rs.liquid @@ -108,7 +108,7 @@ impl TestRequest { /// Sends the request to the application under test. /// # Panics - /// This function panics if the response body could not be read. + /// This function panics if the request could not be built. #[allow(unused)] pub async fn send(self) -> Response { let mut request_builder = Request::builder().uri(&self.uri);