axum_valid/
extra.rs

1//! # Support for extractors from `axum-extra`
2//!
3//! ## Feature
4//!
5//! Enable the `extra` feature to use `Valid<Cached<T>>`, `Valid<WithRejection<T, R>>` and `WithRejection<Valid<T>, R>`.
6//!
7//! ## Modules
8//!
9//! * [`self`] : `Cache<T>`
10//! * [`self`] : `WithRejection<T, R>`
11//! * [`form`] : `Form<T>`
12//! * [`protobuf`] : `Protobuf<T>`
13//! * [`query`] : `Query<T>`
14//! * [`typed_path`] : `T: TypedPath`
15//!
16//! ## `Cached<T>` and `WithRejection<T, R>`
17//!
18//! ### `Valid<Cached<T>>`
19//!
20//! #### Usage
21//!
22//! 0. Implement your own extractor `T`.
23//! 1. Implement `Clone` and `Validate` for your extractor type `T`.
24//! 2. In your handler function, use `Valid<Cached<T>>` as some parameter's type.
25//!
26//! #### Example
27//!
28//! ```no_run
29//! #[cfg(feature = "validator")]
30//! mod validator_example {
31//!     use axum::extract::FromRequestParts;
32//!     use axum::http::request::Parts;
33//!     use axum::response::{IntoResponse, Response};
34//!     use axum::routing::post;
35//!     use axum::Router;
36//!     use axum_extra::extract::Cached;
37//!     use axum_valid::Valid;
38//!     use validator::Validate;
39//!
40//!     pub fn router() -> Router {
41//!         Router::new().route("/cached", post(handler))
42//!     }
43//!
44//!     async fn handler(Valid(Cached(parameter)): Valid<Cached<Parameter>>) {
45//!         assert!(parameter.validate().is_ok());
46//!     }
47//!
48//!     #[derive(Validate, Clone)]
49//!     pub struct Parameter {
50//!         #[validate(range(min = 5, max = 10))]
51//!         pub v0: i32,
52//!         #[validate(length(min = 1, max = 10))]
53//!         pub v1: String,
54//!     }
55//!
56//!     pub struct ParameterRejection;
57//!
58//!     impl IntoResponse for ParameterRejection {
59//!         fn into_response(self) -> Response {
60//!             todo!()
61//!         }
62//!     }
63//!
64//!     impl<S> FromRequestParts<S> for Parameter
65//!     where
66//!         S: Send + Sync,
67//!     {
68//!         type Rejection = ParameterRejection;
69//!
70//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
71//!             todo!()
72//!         }
73//!     }
74//! }
75//! #[cfg(feature = "garde")]
76//! mod garde_example {
77//!     use axum::extract::FromRequestParts;
78//!     use axum::http::request::Parts;
79//!     use axum::response::{IntoResponse, Response};
80//!     use axum::routing::post;
81//!     use axum::Router;
82//!     use axum_extra::extract::Cached;
83//!     use axum_valid::Garde;
84//!     use garde::Validate;
85//!
86//!     pub fn router() -> Router {
87//!         Router::new().route("/cached", post(handler))
88//!     }
89//!
90//!     async fn handler(Garde(Cached(parameter)): Garde<Cached<Parameter>>) {
91//!         assert!(parameter.validate_with(&()).is_ok());
92//!     }
93//!
94//!     #[derive(Validate, Clone)]
95//!     pub struct Parameter {
96//!         #[garde(range(min = 5, max = 10))]
97//!         pub v0: i32,
98//!         #[garde(length(min = 1, max = 10))]
99//!         pub v1: String,
100//!     }
101//!
102//!     pub struct ParameterRejection;
103//!
104//!     impl IntoResponse for ParameterRejection {
105//!         fn into_response(self) -> Response {
106//!             todo!()
107//!         }
108//!     }
109//!
110//!
111//!     impl<S> FromRequestParts<S> for Parameter
112//!     where
113//!         S: Send + Sync,
114//!     {
115//!         type Rejection = ParameterRejection;
116//!
117//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
118//!             todo!()
119//!         }
120//!     }
121//! }
122//!
123//! # #[tokio::main]
124//! # async fn main() -> anyhow::Result<()> {
125//! #     use std::net::SocketAddr;
126//! #     use axum::Router;
127//! #     use tokio::net::TcpListener;
128//! #     let router = Router::new();
129//! #     #[cfg(feature = "validator")]
130//! #     let router = router.nest("/validator", validator_example::router());
131//! #     #[cfg(feature = "garde")]
132//! #     let router = router.nest("/garde", garde_example::router());
133//! #     let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
134//! #     axum::serve(listener, router.into_make_service())
135//! #         .await?;
136//! #     Ok(())
137//! # }
138//! ```
139//!
140//! ### `Valid<WithRejection<T, R>>`
141//!
142//! #### Usage
143//!
144//! 0. Implement your own extractor `T` and rejection type `R`.
145//! 1. Implement `Validate` for your extractor type `T`.
146//! 2. In your handler function, use `Valid<WithRejection<T, R>>` as some parameter's type.
147//!
148//! #### Example
149//!
150//! ```no_run
151//! #[cfg(feature = "validator")]
152//! mod validator_example {
153//!     use axum::extract::FromRequestParts;
154//!     use axum::http::request::Parts;
155//!     use axum::http::StatusCode;
156//!     use axum::response::{IntoResponse, Response};
157//!     use axum::routing::post;
158//!     use axum::Router;
159//!     use axum_extra::extract::WithRejection;
160//!     use axum_valid::Valid;
161//!     use validator::Validate;
162//!
163//!     pub fn router() -> Router {
164//!         Router::new().route("/valid_with_rejection", post(handler))
165//!     }
166//!
167//!     async fn handler(
168//!         Valid(WithRejection(parameter, _)): Valid<
169//!             WithRejection<Parameter, ValidWithRejectionRejection>,
170//!         >,
171//!     ) {
172//!         assert!(parameter.validate().is_ok());
173//!     }
174//!
175//!     #[derive(Validate)]
176//!     pub struct Parameter {
177//!         #[validate(range(min = 5, max = 10))]
178//!         pub v0: i32,
179//!         #[validate(length(min = 1, max = 10))]
180//!         pub v1: String,
181//!     }
182//!
183//!     pub struct ValidWithRejectionRejection;
184//!
185//!     impl IntoResponse for ValidWithRejectionRejection {
186//!         fn into_response(self) -> Response {
187//!             StatusCode::BAD_REQUEST.into_response()
188//!         }
189//!     }
190//!
191//!
192//!     impl<S> FromRequestParts<S> for Parameter
193//!     where
194//!         S: Send + Sync,
195//!     {
196//!         type Rejection = ValidWithRejectionRejection;
197//!
198//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
199//!             todo!()
200//!         }
201//!     }
202//! }
203//!
204//! #[cfg(feature = "garde")]
205//! mod garde_example {
206//!     use axum::extract::FromRequestParts;
207//!     use axum::http::request::Parts;
208//!     use axum::http::StatusCode;
209//!     use axum::response::{IntoResponse, Response};
210//!     use axum::routing::post;
211//!     use axum::Router;
212//!     use axum_extra::extract::WithRejection;
213//!     use axum_valid::Garde;
214//!     use garde::Validate;
215//!
216//!     pub fn router() -> Router {
217//!         Router::new().route("/valid_with_rejection", post(handler))
218//!     }
219//!
220//!     async fn handler(
221//!         Garde(WithRejection(parameter, _)): Garde<
222//!             WithRejection<Parameter, ValidWithRejectionRejection>,
223//!         >,
224//!     ) {
225//!         assert!(parameter.validate_with(&()).is_ok());
226//!     }
227//!
228//!     #[derive(Validate)]
229//!     pub struct Parameter {
230//!         #[garde(range(min = 5, max = 10))]
231//!         pub v0: i32,
232//!         #[garde(length(min = 1, max = 10))]
233//!         pub v1: String,
234//!     }
235//!
236//!     pub struct ValidWithRejectionRejection;
237//!
238//!     impl IntoResponse for ValidWithRejectionRejection {
239//!         fn into_response(self) -> Response {
240//!             StatusCode::BAD_REQUEST.into_response()
241//!         }
242//!     }
243//!
244//!
245//!     impl<S> FromRequestParts<S> for Parameter
246//!     where
247//!         S: Send + Sync,
248//!     {
249//!         type Rejection = ValidWithRejectionRejection;
250//!
251//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
252//!             todo!()
253//!         }
254//!     }
255//! }
256//!
257//! # #[tokio::main]
258//! # async fn main() -> anyhow::Result<()> {
259//! #     use std::net::SocketAddr;
260//! #     use axum::Router;
261//! #     use tokio::net::TcpListener;
262//! #     let router = Router::new();
263//! #     #[cfg(feature = "validator")]
264//! #     let router = router.nest("/validator", validator_example::router());
265//! #     #[cfg(feature = "garde")]
266//! #     let router = router.nest("/garde", garde_example::router());
267//! #     let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
268//! #     axum::serve(listener, router.into_make_service())
269//! #         .await?;
270//! #     Ok(())
271//! # }
272//! ```
273//!
274//! ### `WithRejection<Valid<T>, R>`
275//!
276//! #### Usage
277//!
278//! 0. Implement your own extractor `T` and rejection type `R`.
279//! 1. Implement `Validate` and `HasValidate` for your extractor type `T`.
280//! 2. Implement `From<ValidRejection<T::Rejection>>` for `R`.
281//! 3. In your handler function, use `WithRejection<Valid<T>, R>` as some parameter's type.
282//!
283//! #### Example
284//!
285//! ```no_run
286//! #[cfg(feature = "validator")]
287//! mod validator_example {
288//!     use axum::extract::FromRequestParts;
289//!     use axum::http::request::Parts;
290//!     use axum::response::{IntoResponse, Response};
291//!     use axum::routing::post;
292//!     use axum::Router;
293//!     use axum_extra::extract::WithRejection;
294//!     use axum_valid::{HasValidate, Valid, ValidRejection};
295//!     use validator::Validate;
296//!
297//!     pub fn router() -> Router {
298//!         Router::new().route("/with_rejection_valid", post(handler))
299//!     }
300//!
301//!     async fn handler(
302//!         WithRejection(Valid(parameter), _): WithRejection<
303//!             Valid<Parameter>,
304//!             WithRejectionValidRejection,
305//!         >,
306//!     ) {
307//!         assert!(parameter.validate().is_ok());
308//!     }
309//!
310//!     #[derive(Validate)]
311//!     pub struct Parameter {
312//!         #[validate(range(min = 5, max = 10))]
313//!         pub v0: i32,
314//!         #[validate(length(min = 1, max = 10))]
315//!         pub v1: String,
316//!     }
317//!
318//!     impl HasValidate for Parameter {
319//!         type Validate = Self;
320//!
321//!         fn get_validate(&self) -> &Self::Validate {
322//!             self
323//!         }
324//!     }
325//!
326//!     pub struct ParameterRejection;
327//!
328//!     impl IntoResponse for ParameterRejection {
329//!         fn into_response(self) -> Response {
330//!             todo!()
331//!         }
332//!     }
333//!
334//!
335//!     impl<S> FromRequestParts<S> for Parameter
336//!     where
337//!         S: Send + Sync,
338//!     {
339//!         type Rejection = ParameterRejection;
340//!
341//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
342//!             todo!()
343//!         }
344//!     }
345//!
346//!     pub struct WithRejectionValidRejection;
347//!
348//!     impl From<ValidRejection<ParameterRejection>> for WithRejectionValidRejection {
349//!         fn from(_inner: ValidRejection<ParameterRejection>) -> Self {
350//!             todo!()
351//!         }
352//!     }
353//!
354//!     impl IntoResponse for WithRejectionValidRejection {
355//!         fn into_response(self) -> Response {
356//!             todo!()
357//!         }
358//!     }
359//! }
360//!
361//! #[cfg(feature = "garde")]
362//! mod garde_example {
363//!     use axum::extract::FromRequestParts;
364//!     use axum::http::request::Parts;
365//!     use axum::response::{IntoResponse, Response};
366//!     use axum::routing::post;
367//!     use axum::Router;
368//!     use axum_extra::extract::WithRejection;
369//!     use axum_valid::{HasValidate, Garde, GardeRejection};
370//!     use garde::Validate;
371//!
372//!     pub fn router() -> Router {
373//!         Router::new().route("/with_rejection_valid", post(handler))
374//!     }
375//!
376//!     async fn handler(
377//!         WithRejection(Garde(parameter), _): WithRejection<
378//!             Garde<Parameter>,
379//!             WithRejectionGardeRejection,
380//!         >,
381//!     ) {
382//!         assert!(parameter.validate_with(&()).is_ok());
383//!     }
384//!
385//!     #[derive(Validate)]
386//!     pub struct Parameter {
387//!         #[garde(range(min = 5, max = 10))]
388//!         pub v0: i32,
389//!         #[garde(length(min = 1, max = 10))]
390//!         pub v1: String,
391//!     }
392//!
393//!     impl HasValidate for Parameter {
394//!         type Validate = Self;
395//!
396//!         fn get_validate(&self) -> &Self::Validate {
397//!             self
398//!         }
399//!     }
400//!
401//!     pub struct ParameterRejection;
402//!
403//!     impl IntoResponse for ParameterRejection {
404//!         fn into_response(self) -> Response {
405//!             todo!()
406//!         }
407//!     }
408//!
409//!
410//!     impl<S> FromRequestParts<S> for Parameter
411//!     where
412//!         S: Send + Sync,
413//!     {
414//!         type Rejection = ParameterRejection;
415//!
416//!         async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
417//!             todo!()
418//!         }
419//!     }
420//!
421//!     pub struct WithRejectionGardeRejection;
422//!
423//!     impl From<GardeRejection<ParameterRejection>> for WithRejectionGardeRejection {
424//!         fn from(_inner: GardeRejection<ParameterRejection>) -> Self {
425//!             todo!()
426//!         }
427//!     }
428//!
429//!     impl IntoResponse for WithRejectionGardeRejection {
430//!         fn into_response(self) -> Response {
431//!             todo!()
432//!         }
433//!     }
434//! }
435//! ```
436
437#[cfg(feature = "extra_form")]
438pub mod form;
439#[cfg(feature = "extra_protobuf")]
440pub mod protobuf;
441#[cfg(feature = "extra_query")]
442pub mod query;
443#[cfg(feature = "extra_typed_path")]
444pub mod typed_path;
445
446use crate::HasValidate;
447#[cfg(feature = "validator")]
448use crate::HasValidateArgs;
449use axum_extra::extract::{Cached, WithRejection};
450#[cfg(feature = "validator")]
451use validator::ValidateArgs;
452
453impl<T> HasValidate for Cached<T> {
454    type Validate = T;
455
456    fn get_validate(&self) -> &Self::Validate {
457        &self.0
458    }
459}
460
461#[cfg(feature = "validator")]
462impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Cached<T> {
463    type ValidateArgs = T;
464    fn get_validate_args(&self) -> &Self::ValidateArgs {
465        &self.0
466    }
467}
468
469#[cfg(feature = "validify")]
470impl<T: validify::Modify> crate::HasModify for Cached<T> {
471    type Modify = T;
472
473    fn get_modify(&mut self) -> &mut Self::Modify {
474        &mut self.0
475    }
476}
477
478impl<T, R> HasValidate for WithRejection<T, R> {
479    type Validate = T;
480    fn get_validate(&self) -> &T {
481        &self.0
482    }
483}
484
485#[cfg(feature = "validator")]
486impl<'v, T: ValidateArgs<'v>, R> HasValidateArgs<'v> for WithRejection<T, R> {
487    type ValidateArgs = T;
488    fn get_validate_args(&self) -> &Self::ValidateArgs {
489        &self.0
490    }
491}
492
493#[cfg(feature = "validify")]
494impl<T: validify::Modify, R> crate::HasModify for WithRejection<T, R> {
495    type Modify = T;
496
497    fn get_modify(&mut self) -> &mut Self::Modify {
498        &mut self.0
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use crate::tests::{Rejection, ValidTest};
505    #[cfg(feature = "garde")]
506    use crate::Garde;
507    #[cfg(feature = "validator")]
508    use crate::Valid;
509    #[cfg(feature = "validify")]
510    use crate::{Modified, Validated, ValidifiedByRef};
511    use axum::http::StatusCode;
512    use axum_extra::extract::{Cached, WithRejection};
513    use reqwest::RequestBuilder;
514
515    impl<T: ValidTest> ValidTest for Cached<T> {
516        const ERROR_STATUS_CODE: StatusCode = T::ERROR_STATUS_CODE;
517
518        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
519            T::set_valid_request(builder)
520        }
521
522        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
523            // cached never fails
524            T::set_error_request(builder)
525        }
526
527        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
528            T::set_invalid_request(builder)
529        }
530    }
531
532    impl<T: ValidTest, R: Rejection> ValidTest for WithRejection<T, R> {
533        const ERROR_STATUS_CODE: StatusCode = R::STATUS_CODE;
534
535        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
536            T::set_valid_request(builder)
537        }
538
539        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
540            // cached never fails
541            T::set_error_request(builder)
542        }
543
544        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
545            T::set_invalid_request(builder)
546        }
547    }
548
549    #[cfg(feature = "validator")]
550    impl<T: ValidTest, R> ValidTest for WithRejection<Valid<T>, R> {
551        // just use `418 I'm a teapot` to test
552        const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
553        // If `WithRejection` is the outermost extractor,
554        // the error code returned will always be the one provided by WithRejection.
555        const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
556        // If `WithRejection` is the outermost extractor,
557        // the returned body may not be in JSON format.
558        const JSON_SERIALIZABLE: bool = false;
559
560        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
561            T::set_valid_request(builder)
562        }
563
564        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
565            // invalid requests will cause the Valid extractor to fail.
566            T::set_invalid_request(builder)
567        }
568
569        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
570            T::set_invalid_request(builder)
571        }
572    }
573
574    #[cfg(feature = "garde")]
575    impl<T: ValidTest, R> ValidTest for WithRejection<Garde<T>, R> {
576        // just use `418 I'm a teapot` to test
577        const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
578        // If `WithRejection` is the outermost extractor,
579        // the error code returned will always be the one provided by WithRejection.
580        const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
581        // If `WithRejection` is the outermost extractor,
582        // the returned body may not be in JSON format.
583        const JSON_SERIALIZABLE: bool = false;
584
585        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
586            T::set_valid_request(builder)
587        }
588
589        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
590            // invalid requests will cause the Valid extractor to fail.
591            T::set_invalid_request(builder)
592        }
593
594        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
595            T::set_invalid_request(builder)
596        }
597    }
598
599    #[cfg(feature = "validify")]
600    impl<T: ValidTest, R> ValidTest for WithRejection<Validated<T>, R> {
601        // just use `418 I'm a teapot` to test
602        const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
603        // If `WithRejection` is the outermost extractor,
604        // the error code returned will always be the one provided by WithRejection.
605        const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
606        // If `WithRejection` is the outermost extractor,
607        // the returned body may not be in JSON format.
608        const JSON_SERIALIZABLE: bool = false;
609
610        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
611            T::set_valid_request(builder)
612        }
613
614        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
615            // invalid requests will cause the Valid extractor to fail.
616            T::set_invalid_request(builder)
617        }
618
619        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
620            T::set_invalid_request(builder)
621        }
622    }
623
624    #[cfg(feature = "validify")]
625    impl<T: ValidTest, R> ValidTest for WithRejection<Modified<T>, R> {
626        // just use `418 I'm a teapot` to test
627        const ERROR_STATUS_CODE: StatusCode = StatusCode::OK;
628        // If `WithRejection` is the outermost extractor,
629        // the error code returned will always be the one provided by WithRejection.
630        const INVALID_STATUS_CODE: StatusCode = StatusCode::OK;
631        // If `WithRejection` is the outermost extractor,
632        // the returned body may not be in JSON format.
633        const JSON_SERIALIZABLE: bool = false;
634
635        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
636            T::set_valid_request(builder)
637        }
638
639        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
640            // invalid requests will cause the Valid extractor to fail.
641            T::set_invalid_request(builder)
642        }
643
644        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
645            T::set_invalid_request(builder)
646        }
647    }
648
649    #[cfg(feature = "validify")]
650    impl<T: ValidTest, R> ValidTest for WithRejection<ValidifiedByRef<T>, R> {
651        // just use `418 I'm a teapot` to test
652        const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
653        // If `WithRejection` is the outermost extractor,
654        // the error code returned will always be the one provided by WithRejection.
655        const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
656        // If `WithRejection` is the outermost extractor,
657        // the returned body may not be in JSON format.
658        const JSON_SERIALIZABLE: bool = false;
659
660        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
661            T::set_valid_request(builder)
662        }
663
664        fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
665            // invalid requests will cause the Valid extractor to fail.
666            T::set_invalid_request(builder)
667        }
668
669        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
670            T::set_invalid_request(builder)
671        }
672    }
673}