parent
93abc216e1
commit
c4b1d95cf7
@ -0,0 +1,3 @@
|
||||
pub fn form_checkbox_is_checked(val: &Option<String>) -> bool {
|
||||
val.as_ref().map(|val| val == "on").unwrap_or(false)
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
use axum::async_trait;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use serde::de::DeserializeOwned;
|
||||
use crate::app::routes::AppState;
|
||||
use crate::error::AppError;
|
||||
|
||||
#[async_trait]
|
||||
pub trait HtmxFormData : DeserializeOwned + Send {
|
||||
type FormTemplate: IntoResponse;
|
||||
|
||||
async fn validate(self, state: &AppState) -> Result<Self, HtmxFormDataError<Self::FormTemplate>>;
|
||||
|
||||
async fn base_template(state: &AppState) -> anyhow::Result<Self::FormTemplate>;
|
||||
}
|
||||
|
||||
pub enum HtmxFormDataError<T> {
|
||||
ValidationError(T),
|
||||
InternalServerError(anyhow::Error),
|
||||
}
|
||||
|
||||
impl<T> From<anyhow::Error> for HtmxFormDataError<T> {
|
||||
fn from(err: anyhow::Error) -> Self {
|
||||
HtmxFormDataError::InternalServerError(err)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
pub mod validated_form;
|
||||
pub mod htmx_form_data;
|
||||
pub mod form_helpers;
|
||||
@ -0,0 +1,82 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use axum::extract::{FromRequest, FromRequestParts, Request};
|
||||
use axum::http::StatusCode;
|
||||
use axum::{async_trait, RequestPartsExt};
|
||||
use axum::extract::rejection::FormRejection;
|
||||
use axum::response::{IntoResponse, IntoResponseParts};
|
||||
use crate::app::routes::AppState;
|
||||
use crate::error::AppError;
|
||||
use super::htmx_form_data::{HtmxFormData, HtmxFormDataError};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ValidatedForm<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T> FromRequest<AppState> for ValidatedForm<T>
|
||||
where
|
||||
T: HtmxFormData
|
||||
{
|
||||
type Rejection = ValidatedFormError<T::FormTemplate>;
|
||||
|
||||
async fn from_request(req: Request, state: &AppState) -> Result<Self, Self::Rejection> {
|
||||
|
||||
let raw_form_data = axum::Form::<T>::from_request(req, state)
|
||||
.await
|
||||
.map_err(|e| ValidatedFormError::FormError(e))?;
|
||||
|
||||
let value = raw_form_data.0
|
||||
.validate(&state)
|
||||
.await
|
||||
.map_err(|e|
|
||||
match e {
|
||||
HtmxFormDataError::ValidationError(e) => ValidatedFormError::ValidationError(e),
|
||||
HtmxFormDataError::InternalServerError(e) => ValidatedFormError::InternalServerError(e),
|
||||
}
|
||||
)?;
|
||||
|
||||
Ok(ValidatedForm(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for ValidatedForm<T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ValidatedForm<T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ValidatedFormError<T> {
|
||||
ValidationError(T),
|
||||
FormError(FormRejection),
|
||||
InternalServerError(anyhow::Error)
|
||||
}
|
||||
|
||||
|
||||
impl<T> IntoResponse for ValidatedFormError<T>
|
||||
where T: IntoResponse
|
||||
{
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
ValidatedFormError::FormError(err) => {
|
||||
err.into_response()
|
||||
},
|
||||
ValidatedFormError::ValidationError(err) => {
|
||||
let (mut parts, body) = err.into_response().into_parts();
|
||||
parts.status = StatusCode::OK;
|
||||
(parts, body).into_response()
|
||||
}
|
||||
ValidatedFormError::InternalServerError(err) => {
|
||||
AppError::from(err).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue