- Add load_roaster_options, load_roast_options, load_cafe_options helpers to support.rs, replacing duplicated inline loading in roasts.rs, bags.rs, and cups.rs - Add define_list_fragment_renderer! macro for the 5 structurally identical render_*_list_fragment functions - Fix gear.rs to return navigator from load_gear_page like all other loaders instead of discarding and reconstructing it
153 lines
6.2 KiB
Rust
153 lines
6.2 KiB
Rust
/// Generates a GET-by-ID handler that retrieves an entity from a repository.
|
|
///
|
|
/// # Arguments
|
|
/// * `$fn_name` - Name of the generated handler function
|
|
/// * `$id_type` - Type of the ID path parameter (e.g., `RoasterId`)
|
|
/// * `$entity_type` - Type of the entity returned as JSON (e.g., `Roaster`)
|
|
/// * `$repo_field` - Name of the repository field on `AppState` (e.g., `roaster_repo`)
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// define_get_handler!(get_roaster, RoasterId, Roaster, roaster_repo);
|
|
/// ```
|
|
macro_rules! define_get_handler {
|
|
($fn_name:ident, $id_type:ty, $entity_type:ty, $repo_field:ident) => {
|
|
#[tracing::instrument(skip(state))]
|
|
pub(crate) async fn $fn_name(
|
|
axum::extract::State(state): axum::extract::State<crate::application::server::AppState>,
|
|
axum::extract::Path(id): axum::extract::Path<$id_type>,
|
|
) -> Result<axum::Json<$entity_type>, crate::application::errors::ApiError> {
|
|
let entity = state
|
|
.$repo_field
|
|
.get(id)
|
|
.await
|
|
.map_err(crate::application::errors::AppError::from)?;
|
|
Ok(axum::Json(entity))
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generates a GET-by-ID handler that retrieves an enriched entity using a custom method.
|
|
///
|
|
/// # Arguments
|
|
/// * `$fn_name` - Name of the generated handler function
|
|
/// * `$id_type` - Type of the ID path parameter (e.g., `RoastId`)
|
|
/// * `$entity_type` - Type of the enriched entity returned as JSON (e.g., `RoastWithRoaster`)
|
|
/// * `$repo_field` - Name of the repository field on `AppState` (e.g., `roast_repo`)
|
|
/// * `$method` - Name of the repository method to call (e.g., `get_with_roaster`)
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// define_enriched_get_handler!(get_roast, RoastId, RoastWithRoaster, roast_repo, get_with_roaster);
|
|
/// ```
|
|
macro_rules! define_enriched_get_handler {
|
|
($fn_name:ident, $id_type:ty, $entity_type:ty, $repo_field:ident, $method:ident) => {
|
|
#[tracing::instrument(skip(state))]
|
|
pub(crate) async fn $fn_name(
|
|
axum::extract::State(state): axum::extract::State<crate::application::server::AppState>,
|
|
axum::extract::Path(id): axum::extract::Path<$id_type>,
|
|
) -> Result<axum::Json<$entity_type>, crate::application::errors::ApiError> {
|
|
let entity = state
|
|
.$repo_field
|
|
.$method(id)
|
|
.await
|
|
.map_err(crate::application::errors::AppError::from)?;
|
|
Ok(axum::Json(entity))
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generates a DELETE handler with Datastar fragment re-rendering support.
|
|
///
|
|
/// # Arguments
|
|
/// * `$fn_name` - Name of the generated handler function
|
|
/// * `$id_type` - Type of the ID path parameter (e.g., `RoasterId`)
|
|
/// * `$sort_key` - Sort key type for list requests (e.g., `RoasterSortKey`)
|
|
/// * `$repo_field` - Name of the repository field on `AppState` (e.g., `roaster_repo`)
|
|
/// * `$render_fragment` - Path to the fragment render function
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// define_delete_handler!(
|
|
/// delete_roaster,
|
|
/// RoasterId,
|
|
/// RoasterSortKey,
|
|
/// roaster_repo,
|
|
/// render_roaster_list_fragment
|
|
/// );
|
|
/// ```
|
|
macro_rules! define_delete_handler {
|
|
($fn_name:ident, $id_type:ty, $sort_key:ty, $repo_field:ident, $render_fragment:path) => {
|
|
#[tracing::instrument(skip(state, _auth_user, headers, query))]
|
|
pub(crate) async fn $fn_name(
|
|
axum::extract::State(state): axum::extract::State<crate::application::server::AppState>,
|
|
_auth_user: crate::application::auth::AuthenticatedUser,
|
|
headers: axum::http::HeaderMap,
|
|
axum::extract::Path(id): axum::extract::Path<$id_type>,
|
|
axum::extract::Query(query): axum::extract::Query<
|
|
crate::application::routes::support::ListQuery,
|
|
>,
|
|
) -> Result<axum::response::Response, crate::application::errors::ApiError> {
|
|
let (request, search) = query.into_request_and_search::<$sort_key>();
|
|
state
|
|
.$repo_field
|
|
.delete(id)
|
|
.await
|
|
.map_err(crate::application::errors::AppError::from)?;
|
|
|
|
if crate::application::routes::support::is_datastar_request(&headers) {
|
|
$render_fragment(state, request, search, true)
|
|
.await
|
|
.map_err(crate::application::errors::ApiError::from)
|
|
} else {
|
|
Ok(axum::http::StatusCode::NO_CONTENT.into_response())
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generates a list-fragment renderer for Datastar partial updates.
|
|
///
|
|
/// Produces a function that loads a page via `$loader`, builds the given list
|
|
/// template, and returns it as a Datastar fragment targeting `$selector`.
|
|
///
|
|
/// # Arguments
|
|
/// * `$fn_name` - Name of the generated function
|
|
/// * `$sort_key` - Sort key type (e.g., `RoasterSortKey`)
|
|
/// * `$loader` - Page-loader function returning `(Paginated<V>, ListNavigator<K>)`
|
|
/// * `$template { $field }` - List template type and its items field name
|
|
/// * `$selector` - CSS selector for Datastar patching (e.g., `"#roaster-list"`)
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// define_list_fragment_renderer!(
|
|
/// render_roaster_list_fragment,
|
|
/// RoasterSortKey,
|
|
/// load_roaster_page,
|
|
/// RoasterListTemplate { roasters },
|
|
/// "#roaster-list"
|
|
/// );
|
|
/// ```
|
|
macro_rules! define_list_fragment_renderer {
|
|
($fn_name:ident, $sort_key:ty, $loader:ident, $template:ident { $field:ident }, $selector:literal) => {
|
|
async fn $fn_name(
|
|
state: crate::application::server::AppState,
|
|
request: crate::domain::listing::ListRequest<$sort_key>,
|
|
search: Option<String>,
|
|
is_authenticated: bool,
|
|
) -> Result<axum::response::Response, crate::application::errors::AppError> {
|
|
let (items, navigator) = $loader(&state, request, search.as_deref()).await?;
|
|
let template = $template {
|
|
is_authenticated,
|
|
$field: items,
|
|
navigator,
|
|
};
|
|
crate::application::routes::support::render_fragment(template, $selector)
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(super) use define_delete_handler;
|
|
pub(super) use define_enriched_get_handler;
|
|
pub(super) use define_get_handler;
|
|
pub(super) use define_list_fragment_renderer;
|