use axum::{
extract::{FromRequest, Json},
response::{IntoResponse, Response},
};
use alun_core::api::ApiError;
use serde::de::DeserializeOwned;
use validator::ValidationError;
#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedJson<T>(pub T);
impl<T, S> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = ValidatedJsonRejection;
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
match Json::<T>::from_request(req, state).await {
Ok(Json(value)) => Ok(ValidatedJson(value)),
Err(rejection) => {
let msg = format!("请求体格式错误: {}", rejection.body_text());
Err(ValidatedJsonRejection(ApiError::bad_request(msg)))
}
}
}
}
impl<T: validator::Validate> ValidatedJson<T> {
pub fn validate(self) -> Result<Self, ApiError> {
self.0.validate().map_err(|e| {
ApiError::unprocessable_entity(
alun_utils::valid::Valid::format_validation_errors(&e)
)
})?;
Ok(self)
}
}
impl<T> std::ops::Deref for ValidatedJson<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> std::ops::DerefMut for ValidatedJson<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug)]
pub struct ValidatedJsonRejection(pub ApiError);
impl IntoResponse for ValidatedJsonRejection {
fn into_response(self) -> Response {
self.0.into_response()
}
}
impl From<ValidatedJsonRejection> for ApiError {
fn from(rejection: ValidatedJsonRejection) -> Self {
rejection.0
}
}
pub fn validate_uuid(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_uuid(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_uuid")
.with_message("必须是有效的 UUID 格式".into()))
}
}
pub fn validate_mobile(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_mobile(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_mobile")
.with_message("必须是有效的手机号".into()))
}
}
pub fn validate_password_strength(value: &str) -> Result<(), ValidationError> {
if alun_utils::valid::Valid::is_strong_password(value) {
Ok(())
} else {
Err(ValidationError::new("weak_password")
.with_message("密码必须至少 8 位,包含大小写字母、数字和特殊字符".into()))
}
}
pub fn validate_id_card(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_id_card(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_id_card")
.with_message("必须是有效的身份证号".into()))
}
}
pub fn validate_date(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_date(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_date")
.with_message("日期格式必须为 YYYY-MM-DD".into()))
}
}
pub fn validate_email(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_email(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_email")
.with_message("必须是有效的邮箱格式".into()))
}
}
pub fn validate_url(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_url(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_url")
.with_message("必须是有效的 URL 格式".into()))
}
}
pub fn validate_datetime(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_datetime(value) {
Ok(())
} else {
Err(ValidationError::new("invalid_datetime")
.with_message("日期时间格式必须为 ISO 8601/RFC 3339".into()))
}
}
pub fn validate_date_or_datetime(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Ok(());
}
if alun_utils::valid::Valid::is_datetime(value)
|| alun_utils::valid::Valid::is_date(value)
{
Ok(())
} else {
Err(ValidationError::new("invalid_date_or_datetime")
.with_message("日期格式必须为 YYYY-MM-DD 或 ISO 8601/RFC 3339".into()))
}
}
pub trait ValidateExt {
fn validate_or_reject(&self) -> Result<(), ApiError>;
}
impl<T: validator::Validate> ValidateExt for T {
fn validate_or_reject(&self) -> Result<(), ApiError> {
self.validate().map_err(|e| {
ApiError::unprocessable_entity(
alun_utils::valid::Valid::format_validation_errors(&e)
)
})
}
}