mirror of
https://github.com/perstarkse/minne.git
synced 2026-07-05 20:41:41 +02:00
fix: htmx aware error handling
This commit is contained in:
@@ -4,6 +4,7 @@ use axum::{
|
|||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
|
use axum_htmx::{HxRequest, HX_TRIGGER};
|
||||||
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
|
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
|
||||||
use minijinja::{context, Value};
|
use minijinja::{context, Value};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -97,7 +98,11 @@ impl IntoResponse for TemplateResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn with_template_response<S>(State(state): State<S>, response: Response) -> Response
|
pub async fn with_template_response<S>(
|
||||||
|
State(state): State<S>,
|
||||||
|
HxRequest(is_htmx): HxRequest,
|
||||||
|
response: Response,
|
||||||
|
) -> Response
|
||||||
where
|
where
|
||||||
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
|
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -123,44 +128,37 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplateKind::Error(_status) => {
|
TemplateKind::Error(status) => {
|
||||||
// Extract title and description from context
|
if is_htmx {
|
||||||
let title = template_response
|
// HTMX request: Send 204 + HX-Trigger for toast
|
||||||
.context
|
let title = template_response
|
||||||
.get_attr("title")
|
.context
|
||||||
.ok()
|
.get_attr("title")
|
||||||
.and_then(|v| v.as_str().map(|s| s.to_string()))
|
.ok()
|
||||||
.unwrap_or_else(|| "Error".to_string()); // Fallback title
|
.and_then(|v| v.as_str().map(String::from))
|
||||||
let description = template_response
|
.unwrap_or_else(|| "Error".to_string());
|
||||||
.context
|
let description = template_response
|
||||||
.get_attr("description")
|
.context
|
||||||
.ok()
|
.get_attr("description")
|
||||||
.and_then(|v| v.as_str().map(|s| s.to_string()))
|
.ok()
|
||||||
.unwrap_or_else(|| "An error occurred.".to_string()); // Fallback desc
|
.and_then(|v| v.as_str().map(String::from))
|
||||||
|
.unwrap_or_else(|| "An error occurred.".to_string());
|
||||||
|
|
||||||
let trigger_payload = json!({
|
let trigger_payload = json!({"toast": {"title": title, "description": description, "type": "error"}});
|
||||||
"toast": {
|
let trigger_value = serde_json::to_string(&trigger_payload).unwrap_or_else(|e| {error!("Failed to serialize HX-Trigger payload: {}", e);
|
||||||
"title": title,
|
r#"{"toast":{"title":"Error","description":"An unexpected error occurred.", "type":"error"}}"#.to_string()});
|
||||||
"description": description,
|
(StatusCode::NO_CONTENT, [(HX_TRIGGER, trigger_value)], "").into_response()
|
||||||
"type": "error"
|
} else {
|
||||||
|
// Non-HTMX request: Render the full errors/error.html page
|
||||||
|
match template_engine.render("errors/error.html", &template_response.context) {
|
||||||
|
Ok(html) => (*status, Html(html)).into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Critical: Failed to render 'errors/error.html': {:?}", e);
|
||||||
|
// Fallback HTML, but use the intended status code
|
||||||
|
(*status, Html(fallback_error())).into_response()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Convert payload to string
|
|
||||||
let trigger_value = serde_json::to_string(&trigger_payload)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
error!("Failed to serialize HX-Trigger payload: {}", e);
|
|
||||||
// Fallback trigger if serialization fails
|
|
||||||
r#"{"toast":{"title":"Error","description":"An unexpected error occurred.", "type":"error"}}"#.to_string()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return 204 No Content with HX-Trigger header
|
|
||||||
(
|
|
||||||
StatusCode::NO_CONTENT,
|
|
||||||
[(axum_htmx::HX_TRIGGER, trigger_value)],
|
|
||||||
"", // Empty body for 204
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
}
|
||||||
TemplateKind::Redirect(path) => {
|
TemplateKind::Redirect(path) => {
|
||||||
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
|
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ pub async fn show_active_jobs(
|
|||||||
State(state): State<HtmlState>,
|
State(state): State<HtmlState>,
|
||||||
RequireUser(user): RequireUser,
|
RequireUser(user): RequireUser,
|
||||||
) -> Result<impl IntoResponse, HtmlError> {
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
return Ok(TemplateResponse::server_error());
|
||||||
let active_jobs = User::get_unfinished_ingestion_tasks(&user.id, &state.db).await?;
|
let active_jobs = User::get_unfinished_ingestion_tasks(&user.id, &state.db).await?;
|
||||||
|
|
||||||
Ok(TemplateResponse::new_partial(
|
Ok(TemplateResponse::new_partial(
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends 'body_base.html' %}
|
{% extends 'body_base.html' %}
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<main class="container justify-center flex-grow flex mx-auto mt-4">
|
<main class="container justify-center flex-grow flex mx-auto mt-4">
|
||||||
<div class="flex flex-col space-y-4 text-center">
|
<div class="flex flex-col space-y-4 text-center justify-center">
|
||||||
<h1 class="text-2xl font-bold text-error">
|
<h1 class="text-2xl font-bold text-error">
|
||||||
{{ status_code }}
|
{{ status_code }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-2xl my-4">{{ error }}</p>
|
<p class="text-2xl my-4">{{ title }}</p>
|
||||||
<p class="text-base-content/60">{{ description }}</p>
|
<p class="text-base-content/60">{{ description }}</p>
|
||||||
<a href="/" class="btn btn-primary mt-8">Go Home</a>
|
<a href="/" class="btn btn-primary mt-8">Go Home</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user