axum_valid/
validify.rs

1//! # Validify support
2//!
3//! ## Feature
4//!
5//! Enable the `validify` feature to use `Validated<E>`, `Modified<E>`, `Validified<E>` and `ValidifiedByRef<E>`.
6//!
7
8#[cfg(test)]
9pub mod test;
10
11use crate::{HasValidate, ValidationRejection};
12use axum::extract::{FromRequest, FromRequestParts, Request};
13use axum::http::request::Parts;
14use axum::response::{IntoResponse, Response};
15use std::fmt::{Display, Formatter};
16use std::ops::{Deref, DerefMut};
17use validify::{Modify, Validate, ValidationErrors, ValidifyPayload};
18
19/// # `Validated` data extractor
20///
21/// `Validated` provides simple data validation based on `validify`.
22///
23/// It only does validation, usage is similar to `Valid`.
24///
25#[derive(Debug, Clone, Copy, Default)]
26pub struct Validated<E>(pub E);
27
28impl<E> Deref for Validated<E> {
29    type Target = E;
30
31    fn deref(&self) -> &Self::Target {
32        &self.0
33    }
34}
35
36impl<E> DerefMut for Validated<E> {
37    fn deref_mut(&mut self) -> &mut Self::Target {
38        &mut self.0
39    }
40}
41
42impl<T: Display> Display for Validated<T> {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        self.0.fmt(f)
45    }
46}
47
48impl<E> Validated<E> {
49    /// Consumes the `Validated` and returns the validated data within.
50    ///
51    /// This returns the `E` type which represents the data that has been
52    /// successfully validated.
53    pub fn into_inner(self) -> E {
54        self.0
55    }
56}
57
58#[cfg(feature = "aide")]
59impl<T> aide::OperationInput for Validated<T>
60where
61    T: aide::OperationInput,
62{
63    fn operation_input(
64        ctx: &mut aide::generate::GenContext,
65        operation: &mut aide::openapi::Operation,
66    ) {
67        T::operation_input(ctx, operation);
68    }
69}
70
71/// # `Modified` data extractor / response
72///
73/// ## Extractor
74///
75/// `Modified` uses `validify`'s modification capabilities to alter data, without validation.
76///
77/// Operations like trimming and case modification can be done based on `modify` attributes.
78///
79/// ## Response
80///
81/// `Modified` also implements the `IntoResponse` trait. When its inner `IntoResponse` type also implements the `HasModify` trait:
82///
83/// `Modified` will call `validify`'s modify method to alter the inner data.
84/// Then call the inner type's own `into_response` method to convert it into a HTTP response.
85///
86/// This allows applying modifications during response conversion by leveraging validify.
87#[derive(Debug, Clone, Copy, Default)]
88pub struct Modified<E>(pub E);
89
90impl<E> Deref for Modified<E> {
91    type Target = E;
92
93    fn deref(&self) -> &Self::Target {
94        &self.0
95    }
96}
97
98impl<E> DerefMut for Modified<E> {
99    fn deref_mut(&mut self) -> &mut Self::Target {
100        &mut self.0
101    }
102}
103
104impl<T: Display> Display for Modified<T> {
105    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106        self.0.fmt(f)
107    }
108}
109
110impl<E> Modified<E> {
111    /// Consumes the `Modified` and returns the modified data within.
112    ///
113    /// This returns the `E` type which represents the data that has been
114    /// modified.
115    pub fn into_inner(self) -> E {
116        self.0
117    }
118}
119
120impl<E: IntoResponse + HasModify> IntoResponse for Modified<E> {
121    fn into_response(mut self) -> Response {
122        self.get_modify().modify();
123        self.0.into_response()
124    }
125}
126
127#[cfg(feature = "aide")]
128impl<T> aide::OperationInput for Modified<T>
129where
130    T: aide::OperationInput,
131{
132    fn operation_input(
133        ctx: &mut aide::generate::GenContext,
134        operation: &mut aide::openapi::Operation,
135    ) {
136        T::operation_input(ctx, operation);
137    }
138}
139
140#[cfg(feature = "aide")]
141impl<T> aide::OperationOutput for Modified<T>
142where
143    T: aide::OperationOutput,
144{
145    type Inner = T::Inner;
146
147    fn operation_response(
148        ctx: &mut aide::generate::GenContext,
149        operation: &mut aide::openapi::Operation,
150    ) -> Option<aide::openapi::Response> {
151        T::operation_response(ctx, operation)
152    }
153
154    fn inferred_responses(
155        ctx: &mut aide::generate::GenContext,
156        operation: &mut aide::openapi::Operation,
157    ) -> Vec<(Option<u16>, aide::openapi::Response)> {
158        T::inferred_responses(ctx, operation)
159    }
160}
161
162/// # `Validified` data extractor
163///
164/// `Validified` provides construction, modification and validation abilities based on `validify`.
165///
166/// It requires a serde-based inner extractor.
167///
168/// And can treat missing fields as validation errors.
169///
170#[derive(Debug, Clone, Copy, Default)]
171pub struct Validified<E>(pub E);
172
173impl<E> Deref for Validified<E> {
174    type Target = E;
175
176    fn deref(&self) -> &Self::Target {
177        &self.0
178    }
179}
180
181impl<E> DerefMut for Validified<E> {
182    fn deref_mut(&mut self) -> &mut Self::Target {
183        &mut self.0
184    }
185}
186
187impl<T: Display> Display for Validified<T> {
188    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189        self.0.fmt(f)
190    }
191}
192
193impl<E> Validified<E> {
194    /// Consumes the `Validified` and returns the modified and validated data within.
195    ///
196    /// This returns the `E` type which represents the data that has been
197    /// successfully validated.
198    pub fn into_inner(self) -> E {
199        self.0
200    }
201}
202
203#[cfg(feature = "aide")]
204impl<T> aide::OperationInput for Validified<T>
205where
206    T: aide::OperationInput,
207{
208    fn operation_input(
209        ctx: &mut aide::generate::GenContext,
210        operation: &mut aide::openapi::Operation,
211    ) {
212        T::operation_input(ctx, operation);
213    }
214}
215
216/// # `ValidifiedByRef` data extractor
217///
218/// `ValidifiedByRef` is similar to `Validified`, but operates via reference.
219///
220/// Suitable for inner extractors not based on `serde`.
221///
222#[derive(Debug, Clone, Copy, Default)]
223pub struct ValidifiedByRef<E>(pub E);
224
225impl<E> Deref for ValidifiedByRef<E> {
226    type Target = E;
227
228    fn deref(&self) -> &Self::Target {
229        &self.0
230    }
231}
232
233impl<E> DerefMut for ValidifiedByRef<E> {
234    fn deref_mut(&mut self) -> &mut Self::Target {
235        &mut self.0
236    }
237}
238
239impl<T: Display> Display for ValidifiedByRef<T> {
240    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241        self.0.fmt(f)
242    }
243}
244
245impl<E> ValidifiedByRef<E> {
246    /// Consumes the `ValidifiedByRef` and returns the modified and validated data within.
247    ///
248    /// This returns the `E` type which represents the data that has been
249    /// successfully validated.
250    pub fn into_inner(self) -> E {
251        self.0
252    }
253}
254
255#[cfg(feature = "aide")]
256impl<T> aide::OperationInput for ValidifiedByRef<T>
257where
258    T: aide::OperationInput,
259{
260    fn operation_input(
261        ctx: &mut aide::generate::GenContext,
262        operation: &mut aide::openapi::Operation,
263    ) {
264        T::operation_input(ctx, operation);
265    }
266}
267
268/// `ValidifyRejection` is returned when the `Validated` / `Modified` / `Validified` / `ValidifiedByRef` extractor fails.
269///
270pub type ValidifyRejection<E> = ValidationRejection<ValidationErrors, E>;
271
272impl<E> From<ValidationErrors> for ValidifyRejection<E> {
273    fn from(value: ValidationErrors) -> Self {
274        Self::Valid(value)
275    }
276}
277
278/// Trait for types that can supply a reference that can be modified.
279///
280/// Extractor types `T` that implement this trait can be used with `Modified`.
281///
282pub trait HasModify {
283    /// Inner type that can be modified
284    type Modify: Modify;
285    /// Get the inner value
286    fn get_modify(&mut self) -> &mut Self::Modify;
287}
288
289/// Extractor to extract payload for constructing data
290pub trait PayloadExtractor {
291    /// Type of payload for constructing data
292    type Payload;
293    /// Get payload from the extractor
294    fn get_payload(self) -> Self::Payload;
295}
296
297/// Trait for extractors whose inner data type that can be constructed using some payload,  
298/// then modified and validated using `validify`.
299///
300/// Extractor types `T` that implement this trait can be used with `Validified`.
301///
302pub trait HasValidify: Sized {
303    /// Inner type that can be modified and validated using `validify`.
304    type Validify: ValidifyPayload;
305
306    /// Extracts payload from the request,
307    /// which will be used to construct the `Self::Validify` type  
308    /// and perform modification and validation on it.
309    type PayloadExtractor: PayloadExtractor<Payload = <Self::Validify as ValidifyPayload>::Payload>;
310
311    /// Re-packages the validified data back into the inner Extractor type.  
312    fn from_validify(v: Self::Validify) -> Self;
313}
314
315impl<State, Extractor> FromRequest<State> for Validated<Extractor>
316where
317    State: Send + Sync,
318    Extractor: HasValidate + FromRequest<State>,
319    Extractor::Validate: validify::Validate,
320{
321    type Rejection = ValidifyRejection<<Extractor as FromRequest<State>>::Rejection>;
322
323    async fn from_request(req: Request, state: &State) -> Result<Self, Self::Rejection> {
324        let inner = Extractor::from_request(req, state)
325            .await
326            .map_err(ValidifyRejection::Inner)?;
327        inner.get_validate().validate()?;
328        Ok(Validated(inner))
329    }
330}
331
332impl<State, Extractor> FromRequestParts<State> for Validated<Extractor>
333where
334    State: Send + Sync,
335    Extractor: HasValidate + FromRequestParts<State>,
336    Extractor::Validate: Validate,
337{
338    type Rejection = ValidifyRejection<<Extractor as FromRequestParts<State>>::Rejection>;
339
340    async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
341        let inner = Extractor::from_request_parts(parts, state)
342            .await
343            .map_err(ValidifyRejection::Inner)?;
344        inner.get_validate().validate()?;
345        Ok(Validated(inner))
346    }
347}
348
349impl<State, Extractor> FromRequest<State> for Modified<Extractor>
350where
351    State: Send + Sync,
352    Extractor: HasModify + FromRequest<State>,
353{
354    type Rejection = <Extractor as FromRequest<State>>::Rejection;
355
356    async fn from_request(req: Request, state: &State) -> Result<Self, Self::Rejection> {
357        let mut inner = Extractor::from_request(req, state).await?;
358        inner.get_modify().modify();
359        Ok(Modified(inner))
360    }
361}
362
363impl<State, Extractor> FromRequestParts<State> for Modified<Extractor>
364where
365    State: Send + Sync,
366    Extractor: HasModify + FromRequestParts<State>,
367{
368    type Rejection = <Extractor as FromRequestParts<State>>::Rejection;
369
370    async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
371        let mut inner = Extractor::from_request_parts(parts, state).await?;
372        inner.get_modify().modify();
373        Ok(Modified(inner))
374    }
375}
376
377impl<State, Extractor> FromRequest<State> for Validified<Extractor>
378where
379    State: Send + Sync,
380    Extractor: HasValidify,
381    Extractor::PayloadExtractor: FromRequest<State>,
382{
383    type Rejection =
384        ValidifyRejection<<Extractor::PayloadExtractor as FromRequest<State>>::Rejection>;
385
386    async fn from_request(req: Request, state: &State) -> Result<Self, Self::Rejection> {
387        let payload = Extractor::PayloadExtractor::from_request(req, state)
388            .await
389            .map_err(ValidifyRejection::Inner)?
390            .get_payload();
391        let validify = Extractor::Validify::validify_from(payload)?;
392        Ok(Validified(Extractor::from_validify(validify)))
393    }
394}
395
396impl<State, Extractor> FromRequestParts<State> for Validified<Extractor>
397where
398    State: Send + Sync,
399    Extractor: HasValidify,
400    Extractor::PayloadExtractor: FromRequestParts<State>,
401{
402    type Rejection =
403        ValidifyRejection<<Extractor::PayloadExtractor as FromRequestParts<State>>::Rejection>;
404
405    async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
406        let payload = Extractor::PayloadExtractor::from_request_parts(parts, state)
407            .await
408            .map_err(ValidifyRejection::Inner)?
409            .get_payload();
410        let validify = Extractor::Validify::validify_from(payload)?;
411        Ok(Validified(Extractor::from_validify(validify)))
412    }
413}
414
415impl<State, Extractor> FromRequest<State> for ValidifiedByRef<Extractor>
416where
417    State: Send + Sync,
418    Extractor: HasValidate + HasModify + FromRequest<State>,
419    Extractor::Validate: Validate,
420{
421    type Rejection = ValidifyRejection<<Extractor as FromRequest<State>>::Rejection>;
422
423    async fn from_request(req: Request, state: &State) -> Result<Self, Self::Rejection> {
424        let mut inner = Extractor::from_request(req, state)
425            .await
426            .map_err(ValidifyRejection::Inner)?;
427        inner.get_modify().modify();
428        inner.get_validate().validate()?;
429        Ok(ValidifiedByRef(inner))
430    }
431}
432
433impl<State, Extractor> FromRequestParts<State> for ValidifiedByRef<Extractor>
434where
435    State: Send + Sync,
436    Extractor: HasValidate + HasModify + FromRequestParts<State>,
437    Extractor::Validate: Validate,
438{
439    type Rejection = ValidifyRejection<<Extractor as FromRequestParts<State>>::Rejection>;
440
441    async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
442        let mut inner = Extractor::from_request_parts(parts, state)
443            .await
444            .map_err(ValidifyRejection::Inner)?;
445        inner.get_modify().modify();
446        inner.get_validate().validate()?;
447        Ok(ValidifiedByRef(inner))
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use axum::Json;
455    use serde::Serialize;
456    use std::error::Error;
457    use std::io;
458
459    const VALIDIFY: &str = "validify";
460
461    #[test]
462    fn validify_deref_deref_mut_into_inner() {
463        let mut inner = String::from(VALIDIFY);
464        let mut v = Validated(inner.clone());
465        assert_eq!(&inner, v.deref());
466        inner.push_str(VALIDIFY);
467        v.deref_mut().push_str(VALIDIFY);
468        assert_eq!(&inner, v.deref());
469        println!("{}", v);
470        assert_eq!(inner, v.into_inner());
471
472        let mut inner = String::from(VALIDIFY);
473        let mut v = Modified(inner.clone());
474        assert_eq!(&inner, v.deref());
475        inner.push_str(VALIDIFY);
476        v.deref_mut().push_str(VALIDIFY);
477        assert_eq!(&inner, v.deref());
478        println!("{}", v);
479        assert_eq!(inner, v.into_inner());
480
481        let mut inner = String::from(VALIDIFY);
482        let mut v = Validified(inner.clone());
483        assert_eq!(&inner, v.deref());
484        inner.push_str(VALIDIFY);
485        v.deref_mut().push_str(VALIDIFY);
486        assert_eq!(&inner, v.deref());
487        println!("{}", v);
488        assert_eq!(inner, v.into_inner());
489
490        let mut inner = String::from(VALIDIFY);
491        let mut v = ValidifiedByRef(inner.clone());
492        assert_eq!(&inner, v.deref());
493        inner.push_str(VALIDIFY);
494        v.deref_mut().push_str(VALIDIFY);
495        assert_eq!(&inner, v.deref());
496        println!("{}", v);
497        assert_eq!(inner, v.into_inner());
498    }
499
500    #[test]
501    fn display_error() {
502        // ValidifyRejection::Valid Display
503        let mut report = ValidationErrors::new();
504        report.add(validify::ValidationError::new_schema(VALIDIFY));
505        let s = report.to_string();
506        let vr = ValidifyRejection::<String>::Valid(report);
507        assert_eq!(vr.to_string(), s);
508
509        // ValidifyRejection::Inner Display
510        let inner = String::from(VALIDIFY);
511        let vr = ValidifyRejection::<String>::Inner(inner.clone());
512        assert_eq!(inner.to_string(), vr.to_string());
513
514        // ValidifyRejection::Valid Error
515        let mut report = ValidationErrors::new();
516        report.add(validify::ValidationError::new_schema(VALIDIFY));
517        let vr = ValidifyRejection::<io::Error>::Valid(report);
518        assert!(
519            matches!(vr.source(), Some(source) if source.downcast_ref::<ValidationErrors>().is_some())
520        );
521
522        // ValidifyRejection::Valid Error
523        let vr = ValidifyRejection::<io::Error>::Inner(io::Error::other(VALIDIFY));
524        assert!(
525            matches!(vr.source(), Some(source) if source.downcast_ref::<io::Error>().is_some())
526        );
527    }
528
529    #[test]
530    fn modified_into_response() {
531        use validify::Validify;
532        #[derive(Validify, Serialize)]
533        struct Data {
534            #[modify(trim)]
535            v: String,
536        }
537        println!(
538            "{:?}",
539            Modified(Json(Data { v: "a  ".into() })).into_response()
540        );
541    }
542}