custom extractors rejection
Some checks failed
Rust / Test (push) Has been cancelled
Rust / Rustfmt (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Code coverage (push) Has been cancelled

This commit is contained in:
Alphonse Paix
2025-09-27 02:28:04 +02:00
parent f9ae3f42a6
commit f43e143bf6
6 changed files with 68 additions and 13 deletions

View File

@@ -10,7 +10,8 @@ mod unsubscribe;
pub use admin::*; pub use admin::*;
use askama::Template; use askama::Template;
use axum::{ use axum::{
http::HeaderMap, extract::FromRequestParts,
http::{HeaderMap, request::Parts},
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };
pub use health_check::*; pub use health_check::*;
@@ -19,6 +20,7 @@ pub use login::*;
pub use posts::*; pub use posts::*;
use rand::{Rng, distr::Alphanumeric}; use rand::{Rng, distr::Alphanumeric};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::de::DeserializeOwned;
pub use subscriptions::*; pub use subscriptions::*;
pub use subscriptions_confirm::*; pub use subscriptions_confirm::*;
pub use unsubscribe::*; pub use unsubscribe::*;
@@ -61,6 +63,8 @@ pub enum AppError {
FormError(#[source] anyhow::Error), FormError(#[source] anyhow::Error),
#[error("Authentication is required.")] #[error("Authentication is required.")]
NotAuthenticated, NotAuthenticated,
#[error("Handler extractor failed.")]
Extractor(#[source] anyhow::Error),
} }
impl From<anyhow::Error> for AppError { impl From<anyhow::Error> for AppError {
@@ -124,6 +128,7 @@ impl IntoResponse for AppError {
headers.insert("HX-Redirect", "/login".parse().unwrap()); headers.insert("HX-Redirect", "/login".parse().unwrap());
(StatusCode::OK, headers).into_response() (StatusCode::OK, headers).into_response()
} }
AppError::Extractor(_) => not_found_html(),
} }
} }
} }
@@ -155,6 +160,50 @@ impl From<AuthError> for AppError {
} }
pub async fn not_found() -> Response { pub async fn not_found() -> Response {
let template = HtmlTemplate(NotFoundTemplate); (StatusCode::NOT_FOUND, not_found_html()).into_response()
(StatusCode::NOT_FOUND, template).into_response() }
pub fn not_found_html() -> Response {
let template = HtmlTemplate(NotFoundTemplate);
template.into_response()
}
pub struct Path<T>(T);
impl<T, S> FromRequestParts<S> for Path<T>
where
T: DeserializeOwned + Send,
S: Send + Sync,
{
type Rejection = AppError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
match axum::extract::Path::<T>::from_request_parts(parts, state).await {
Ok(value) => Ok(Self(value.0)),
Err(rejection) => Err(AppError::Extractor(anyhow::anyhow!(
"Path rejection: {:?}",
rejection
))),
}
}
}
pub struct Query<T>(pub T);
impl<T, S> FromRequestParts<S> for Query<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = AppError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
match axum::extract::Query::<T>::from_request_parts(parts, state).await {
Ok(value) => Ok(Self(value.0)),
Err(rejection) => Err(AppError::Extractor(anyhow::anyhow!(
"Query rejection: {:?}",
rejection
))),
}
}
} }

View File

@@ -1,7 +1,9 @@
use crate::{ use crate::{
authentication::AuthenticatedUser, authentication::AuthenticatedUser,
idempotency::{IdempotencyKey, save_response, try_processing}, idempotency::{IdempotencyKey, save_response, try_processing},
routes::{AdminError, AppError, EmailType, enqueue_delivery_tasks, insert_newsletter_issue}, routes::{
AdminError, AppError, EmailType, Path, enqueue_delivery_tasks, insert_newsletter_issue,
},
startup::AppState, startup::AppState,
templates::{MessageTemplate, NewPostEmailTemplate}, templates::{MessageTemplate, NewPostEmailTemplate},
}; };
@@ -9,7 +11,7 @@ use anyhow::Context;
use askama::Template; use askama::Template;
use axum::{ use axum::{
Extension, Form, Extension, Form,
extract::{Path, State}, extract::State,
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };
use chrono::Utc; use chrono::Utc;

View File

@@ -1,13 +1,13 @@
use crate::{ use crate::{
domain::SubscriberEntry, domain::SubscriberEntry,
routes::AppError, routes::{AppError, Path, Query},
startup::AppState, startup::AppState,
templates::{MessageTemplate, SubListTemplate}, templates::{MessageTemplate, SubListTemplate},
}; };
use anyhow::Context; use anyhow::Context;
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::{Path, Query, State}, extract::State,
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };
use sqlx::PgPool; use sqlx::PgPool;

View File

@@ -1,13 +1,13 @@
use crate::{ use crate::{
domain::PostEntry, domain::PostEntry,
routes::{AppError, not_found}, routes::{AppError, Path, Query, not_found_html},
startup::AppState, startup::AppState,
templates::{HtmlTemplate, PostListTemplate, PostTemplate, PostsTemplate}, templates::{HtmlTemplate, PostListTemplate, PostTemplate, PostsTemplate},
}; };
use anyhow::Context; use anyhow::Context;
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::{Path, Query, State}, extract::State,
response::{Html, IntoResponse, Redirect, Response}, response::{Html, IntoResponse, Redirect, Response},
}; };
use sqlx::PgPool; use sqlx::PgPool;
@@ -90,7 +90,7 @@ pub async fn see_post(
let template = HtmlTemplate(PostTemplate { post }); let template = HtmlTemplate(PostTemplate { post });
Ok(template.into_response()) Ok(template.into_response())
} else { } else {
Ok(not_found().await) Ok(not_found_html())
} }
} }

View File

@@ -1,7 +1,11 @@
use crate::{routes::generate_token, startup::AppState, templates::ConfirmTemplate}; use crate::{
routes::{Query, generate_token},
startup::AppState,
templates::ConfirmTemplate,
};
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::{Query, State}, extract::State,
http::StatusCode, http::StatusCode,
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };

View File

@@ -39,7 +39,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<div class="text-sm text-amber-800"> <div class="text-sm text-amber-800">
<p>You will receive an email with an confirmation link.</p> <p>You will receive an email with a confirmation link.</p>
</div> </div>
</div> </div>
</div> </div>