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}