1use axum::{
10 extract::{FromRequest, Json},
11 response::{IntoResponse, Response},
12};
13use alun_core::api::ApiError;
14use serde::de::DeserializeOwned;
15use validator::ValidationError;
16
17#[derive(Debug, Clone, Copy, Default)]
37pub struct ValidatedJson<T>(pub T);
38
39impl<T, S> FromRequest<S> for ValidatedJson<T>
43where
44 T: DeserializeOwned,
45 S: Send + Sync,
46{
47 type Rejection = ValidatedJsonRejection;
48
49 async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
50 match Json::<T>::from_request(req, state).await {
51 Ok(Json(value)) => Ok(ValidatedJson(value)),
52 Err(rejection) => {
53 let msg = format!("请求体格式错误: {}", rejection.body_text());
54 Err(ValidatedJsonRejection(ApiError::bad_request(msg)))
55 }
56 }
57 }
58}
59
60impl<T: validator::Validate> ValidatedJson<T> {
62 pub fn validate(self) -> Result<Self, ApiError> {
66 self.0.validate().map_err(|e| {
67 ApiError::unprocessable_entity(
68 alun_utils::valid::Valid::format_validation_errors(&e)
69 )
70 })?;
71 Ok(self)
72 }
73}
74
75impl<T> std::ops::Deref for ValidatedJson<T> {
76 type Target = T;
77
78 fn deref(&self) -> &Self::Target {
79 &self.0
80 }
81}
82
83impl<T> std::ops::DerefMut for ValidatedJson<T> {
84 fn deref_mut(&mut self) -> &mut Self::Target {
85 &mut self.0
86 }
87}
88
89#[derive(Debug)]
91pub struct ValidatedJsonRejection(pub ApiError);
92
93impl IntoResponse for ValidatedJsonRejection {
94 fn into_response(self) -> Response {
95 self.0.into_response()
96 }
97}
98
99impl From<ValidatedJsonRejection> for ApiError {
100 fn from(rejection: ValidatedJsonRejection) -> Self {
101 rejection.0
102 }
103}
104
105pub fn validate_uuid(value: &str) -> Result<(), ValidationError> {
124 if value.is_empty() {
125 return Ok(());
126 }
127 if alun_utils::valid::Valid::is_uuid(value) {
128 Ok(())
129 } else {
130 Err(ValidationError::new("invalid_uuid")
131 .with_message("必须是有效的 UUID 格式".into()))
132 }
133}
134
135pub fn validate_mobile(value: &str) -> Result<(), ValidationError> {
144 if value.is_empty() {
145 return Ok(());
146 }
147 if alun_utils::valid::Valid::is_mobile(value) {
148 Ok(())
149 } else {
150 Err(ValidationError::new("invalid_mobile")
151 .with_message("必须是有效的手机号".into()))
152 }
153}
154
155pub fn validate_password_strength(value: &str) -> Result<(), ValidationError> {
164 if alun_utils::valid::Valid::is_strong_password(value) {
165 Ok(())
166 } else {
167 Err(ValidationError::new("weak_password")
168 .with_message("密码必须至少 8 位,包含大小写字母、数字和特殊字符".into()))
169 }
170}
171
172pub fn validate_id_card(value: &str) -> Result<(), ValidationError> {
181 if value.is_empty() {
182 return Ok(());
183 }
184 if alun_utils::valid::Valid::is_id_card(value) {
185 Ok(())
186 } else {
187 Err(ValidationError::new("invalid_id_card")
188 .with_message("必须是有效的身份证号".into()))
189 }
190}
191
192pub fn validate_date(value: &str) -> Result<(), ValidationError> {
201 if value.is_empty() {
202 return Ok(());
203 }
204 if alun_utils::valid::Valid::is_date(value) {
205 Ok(())
206 } else {
207 Err(ValidationError::new("invalid_date")
208 .with_message("日期格式必须为 YYYY-MM-DD".into()))
209 }
210}
211
212pub fn validate_email(value: &str) -> Result<(), ValidationError> {
221 if value.is_empty() {
222 return Ok(());
223 }
224 if alun_utils::valid::Valid::is_email(value) {
225 Ok(())
226 } else {
227 Err(ValidationError::new("invalid_email")
228 .with_message("必须是有效的邮箱格式".into()))
229 }
230}
231
232pub fn validate_url(value: &str) -> Result<(), ValidationError> {
241 if value.is_empty() {
242 return Ok(());
243 }
244 if alun_utils::valid::Valid::is_url(value) {
245 Ok(())
246 } else {
247 Err(ValidationError::new("invalid_url")
248 .with_message("必须是有效的 URL 格式".into()))
249 }
250}
251
252pub fn validate_datetime(value: &str) -> Result<(), ValidationError> {
261 if value.is_empty() {
262 return Ok(());
263 }
264 if alun_utils::valid::Valid::is_datetime(value) {
265 Ok(())
266 } else {
267 Err(ValidationError::new("invalid_datetime")
268 .with_message("日期时间格式必须为 ISO 8601/RFC 3339".into()))
269 }
270}
271
272pub fn validate_date_or_datetime(value: &str) -> Result<(), ValidationError> {
282 if value.is_empty() {
283 return Ok(());
284 }
285 if alun_utils::valid::Valid::is_datetime(value)
286 || alun_utils::valid::Valid::is_date(value)
287 {
288 Ok(())
289 } else {
290 Err(ValidationError::new("invalid_date_or_datetime")
291 .with_message("日期格式必须为 YYYY-MM-DD 或 ISO 8601/RFC 3339".into()))
292 }
293}
294
295pub trait ValidateExt {
299 fn validate_or_reject(&self) -> Result<(), ApiError>;
300}
301
302impl<T: validator::Validate> ValidateExt for T {
303 fn validate_or_reject(&self) -> Result<(), ApiError> {
304 self.validate().map_err(|e| {
305 ApiError::unprocessable_entity(
306 alun_utils::valid::Valid::format_validation_errors(&e)
307 )
308 })
309 }
310}