From cc4cd9f70c0fae60329ee17c35d6bf2aa0f00b22 Mon Sep 17 00:00:00 2001 From: gengteng Date: Mon, 9 Oct 2023 14:57:58 +0800 Subject: [PATCH] update README.md and doc tests --- CHANGELOG.md | 12 + Cargo.toml | 4 +- README.md | 97 ++++++-- src/extra.rs | 490 ++++++++++++++++++++++++++++------------ src/extra/protobuf.rs | 4 +- src/extra/typed_path.rs | 102 ++++++--- src/garde.rs | 45 +++- src/typed_header.rs | 140 ++++++++---- src/validator.rs | 16 +- 9 files changed, 662 insertions(+), 248 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69edf5d..8b68913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ ### Fixed +## axum-valid 0.10.0 (2023-10-09) + +### Added + +* Added support for `garde`. + +### Changed + +* Refactored the module structure. + +### Fixed + ## axum-valid 0.9.0 (2023-09-29) ### Added diff --git a/Cargo.toml b/Cargo.toml index ef16b2b..7ee9138 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axum-valid" -version = "0.9.0" +version = "0.10.0" description = "Provides validation extractors for your Axum application to validate data using validator, garde, or both." authors = ["GengTeng "] license = "MIT" @@ -21,7 +21,7 @@ categories = [ edition = "2021" [package.metadata.docs.rs] -features = ["all_types"] +features = ["all_types", "garde"] [dependencies] axum = { version = "0.6.20", default-features = false } diff --git a/README.md b/README.md index 911ad5f..e62ef18 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ To see how each extractor can be used with `Valid`, please refer to the example ## Argument-Based Validation -Here's a basic example of using the `ValidEx` extractor to validate data in a `Form` using arguments: +Here are the examples of using the `ValidEx` / `Garde` extractor to validate data in a `Form` using arguments: ```rust,no_run #[cfg(feature = "validator")] @@ -197,15 +197,63 @@ mod validator_example { }) // NOTE: The PagerValidArgs can also be stored in a XxxState, // make sure it implements FromRef. + // Consider using Arc to reduce deep copying costs. } } #[cfg(feature = "garde")] mod garde_example { - use axum::Router; + use axum::routing::post; + use axum::{Form, Router}; + use axum_valid::Garde; + use garde::Validate; + use serde::Deserialize; + use std::ops::{RangeFrom, RangeInclusive}; + + #[derive(Debug, Validate, Deserialize)] + #[garde(context(PagerValidContext))] + pub struct Pager { + #[garde(custom(validate_page_size))] + pub page_size: usize, + #[garde(custom(validate_page_no))] + pub page_no: usize, + } + + fn validate_page_size(v: &usize, args: &PagerValidContext) -> garde::Result { + args.page_size_range + .contains(&v) + .then_some(()) + .ok_or_else(|| garde::Error::new("page_size is out of range")) + } + + fn validate_page_no(v: &usize, args: &PagerValidContext) -> garde::Result { + args.page_no_range + .contains(&v) + .then_some(()) + .ok_or_else(|| garde::Error::new("page_no is out of range")) + } + + #[derive(Debug, Clone)] + pub struct PagerValidContext { + page_size_range: RangeInclusive, + page_no_range: RangeFrom, + } + + pub async fn pager_from_form_garde(Garde(Form(pager)): Garde>) { + assert!((1..=50).contains(&pager.page_size)); + assert!((1..).contains(&pager.page_no)); + } pub fn router() -> Router { Router::new() + .route("/form", post(pager_from_form_garde)) + .with_state(PagerValidContext { + page_size_range: 1..=50, + page_no_range: 1.., + }) + // NOTE: The PagerValidContext can also be stored in a XxxState, + // make sure it implements FromRef. + // Consider using Arc to reduce deep copying costs. } } @@ -228,26 +276,30 @@ Current module documentation predominantly showcases `Valid` examples, the usage ## Features -| Feature | Description | Module | Default | Example | Tests | -|------------------|------------------------------------------------------------------------------------------------------|-----------------------------------------|---------|---------|-------| -| default | Enables support for `Path`, `Query`, `Json` and `Form` | [`path`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ | -| json | Enables support for `Json` | [`json`] | ✅ | ✅ | ✅ | -| query | Enables support for `Query` | [`query`] | ✅ | ✅ | ✅ | -| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ | -| typed_header | Enables support for `TypedHeader` | [`typed_header`] | ❌ | ✅ | ✅ | -| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | ❌ | ✅ | ✅ | -| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | ❌ | ✅ | ✅ | -| yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ | -| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ | -| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ | -| extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ | -| extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ | -| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ | -| all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ | -| all_types | Enables support for all extractors above | N/A | ❌ | ✅ | ✅ | -| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | [`VALIDATION_ERROR_STATUS`] | ❌ | ✅ | ✅ | -| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | N/A | ❌ | ✅ | ✅ | -| full | Enables all features | N/A | ❌ | ✅ | ✅ | +| Feature | Description | Module | Default | Example | Tests | +|------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|---------|---------|-------| +| default | Enables `validator` and support for `Query`, `Json` and `Form` | [`validator`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ | +| validator | Enables `validator` | [`validator`] | ✅ | ✅ | ✅ | +| garde | Enables `garde` | [`garde`] | ❌ | ✅ | ✅ | +| basic | Enables support for `Query`, `Json` and `Form` | [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ | +| json | Enables support for `Json` | [`json`] | ✅ | ✅ | ✅ | +| query | Enables support for `Query` | [`query`] | ✅ | ✅ | ✅ | +| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ | +| typed_header | Enables support for `TypedHeader` | [`typed_header`] | ❌ | ✅ | ✅ | +| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | ❌ | ✅ | ✅ | +| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | ❌ | ✅ | ✅ | +| yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ | +| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ | +| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ | +| extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ | +| extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ | +| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ | +| all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ | +| all_types | Enables support for all extractors above | N/A | ❌ | ✅ | ✅ | +| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | [`VALIDATION_ERROR_STATUS`] | ❌ | ✅ | ✅ | +| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | N/A | ❌ | ✅ | ✅ | +| full | Enables `all_types`, `422` and `into_json` | N/A | ❌ | ✅ | ✅ | +| full_garde | Enables `garde`, `all_types`, `422` and `into_json`. Consider using `default-features = false` to exclude default `validator` support | N/A | ❌ | ✅ | ✅ | ## Compatibility @@ -261,6 +313,7 @@ This project is licensed under the MIT License. * [axum](https://crates.io/crates/axum) * [validator](https://crates.io/crates/validator) +* [garde](https://crates.io/crates/garde) * [serde](https://crates.io/crates/serde) * [axum-extra](https://crates.io/crates/axum-extra) * [axum-yaml](https://crates.io/crates/axum-yaml) diff --git a/src/extra.rs b/src/extra.rs index ce83172..47f1d21 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -26,54 +26,114 @@ //! #### Example //! //! ```no_run -//! #![cfg(feature = "validator")] +//! #[cfg(feature = "validator")] +//! mod validator_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::Cached; +//! use axum_valid::Valid; +//! use validator::Validate; //! -//! use axum::extract::FromRequestParts; -//! use axum::http::request::Parts; -//! use axum::response::{IntoResponse, Response}; -//! use axum::routing::post; -//! use axum::Router; -//! use axum_extra::extract::Cached; -//! use axum_valid::Valid; -//! use validator::Validate; -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let router = Router::new().route("/cached", post(handler)); -//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) -//! .serve(router.into_make_service()) -//! .await?; -//! Ok(()) -//! } -//! async fn handler(Valid(Cached(parameter)): Valid>) { -//! assert!(parameter.validate().is_ok()); -//! } -//! #[derive(Validate, Clone)] -//! pub struct Parameter { -//! #[validate(range(min = 5, max = 10))] -//! pub v0: i32, -//! #[validate(length(min = 1, max = 10))] -//! pub v1: String, -//! } +//! pub fn router() -> Router { +//! Router::new().route("/cached", post(handler)) +//! } //! -//! pub struct ParameterRejection; +//! async fn handler(Valid(Cached(parameter)): Valid>) { +//! assert!(parameter.validate().is_ok()); +//! } //! -//! impl IntoResponse for ParameterRejection { -//! fn into_response(self) -> Response { -//! todo!() +//! #[derive(Validate, Clone)] +//! pub struct Parameter { +//! #[validate(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[validate(length(min = 1, max = 10))] +//! pub v1: String, +//! } +//! +//! pub struct ParameterRejection; +//! +//! impl IntoResponse for ParameterRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! } +//! #[cfg(feature = "garde")] +//! mod garde_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::Cached; +//! use axum_valid::Garde; +//! use garde::Validate; +//! +//! pub fn router() -> Router { +//! Router::new().route("/cached", post(handler)) +//! } +//! +//! async fn handler(Garde(Cached(parameter)): Garde>) { +//! assert!(parameter.validate(&()).is_ok()); +//! } +//! +//! #[derive(Validate, Clone)] +//! pub struct Parameter { +//! #[garde(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[garde(length(min = 1, max = 10))] +//! pub v1: String, +//! } +//! +//! pub struct ParameterRejection; +//! +//! impl IntoResponse for ParameterRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } //! } //! } //! -//! #[axum::async_trait] -//! impl FromRequestParts for Parameter -//! where -//! S: Send + Sync, -//! { -//! type Rejection = ParameterRejection; -//! -//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { -//! todo!() -//! } -//! } +//! # #[tokio::main] +//! # async fn main() -> anyhow::Result<()> { +//! # use axum::Router; +//! # let router = Router::new(); +//! # #[cfg(feature = "validator")] +//! # let router = router.nest("/validator", validator_example::router()); +//! # #[cfg(feature = "garde")] +//! # let router = router.nest("/garde", garde_example::router()); +//! # axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) +//! # .serve(router.into_make_service()) +//! # .await?; +//! # Ok(()) +//! # } //! ``` //! //! ### `Valid>` @@ -87,60 +147,125 @@ //! #### Example //! //! ```no_run -//! #![cfg(feature = "validator")] +//! #[cfg(feature = "validator")] +//! mod validator_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::http::StatusCode; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::WithRejection; +//! use axum_valid::Valid; +//! use validator::Validate; //! -//! use axum::extract::FromRequestParts; -//! use axum::http::request::Parts; -//! use axum::http::StatusCode; -//! use axum::response::{IntoResponse, Response}; -//! use axum::routing::post; -//! use axum::Router; -//! use axum_extra::extract::WithRejection; -//! use axum_valid::Valid; -//! use validator::Validate; -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let router = Router::new().route("/valid_with_rejection", post(handler)); -//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) -//! .serve(router.into_make_service()) -//! .await?; -//! Ok(()) -//! } -//! async fn handler( -//! Valid(WithRejection(parameter, _)): Valid< -//! WithRejection, -//! >, -//! ) { -//! assert!(parameter.validate().is_ok()); -//! } +//! pub fn router() -> Router { +//! Router::new().route("/valid_with_rejection", post(handler)) +//! } //! -//! #[derive(Validate)] -//! pub struct Parameter { -//! #[validate(range(min = 5, max = 10))] -//! pub v0: i32, -//! #[validate(length(min = 1, max = 10))] -//! pub v1: String, -//! } +//! async fn handler( +//! Valid(WithRejection(parameter, _)): Valid< +//! WithRejection, +//! >, +//! ) { +//! assert!(parameter.validate().is_ok()); +//! } //! -//! pub struct ValidWithRejectionRejection; +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[validate(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[validate(length(min = 1, max = 10))] +//! pub v1: String, +//! } //! -//! impl IntoResponse for ValidWithRejectionRejection { -//! fn into_response(self) -> Response { -//! StatusCode::BAD_REQUEST.into_response() +//! pub struct ValidWithRejectionRejection; +//! +//! impl IntoResponse for ValidWithRejectionRejection { +//! fn into_response(self) -> Response { +//! StatusCode::BAD_REQUEST.into_response() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ValidWithRejectionRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } //! } //! } //! -//! #[axum::async_trait] -//! impl FromRequestParts for Parameter -//! where -//! S: Send + Sync, -//! { -//! type Rejection = ValidWithRejectionRejection; +//! #[cfg(feature = "garde")] +//! mod garde_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::http::StatusCode; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::WithRejection; +//! use axum_valid::Garde; +//! use garde::Validate; //! -//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { -//! todo!() +//! pub fn router() -> Router { +//! Router::new().route("/valid_with_rejection", post(handler)) +//! } +//! +//! async fn handler( +//! Garde(WithRejection(parameter, _)): Garde< +//! WithRejection, +//! >, +//! ) { +//! assert!(parameter.validate(&()).is_ok()); +//! } +//! +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[garde(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[garde(length(min = 1, max = 10))] +//! pub v1: String, +//! } +//! +//! pub struct ValidWithRejectionRejection; +//! +//! impl IntoResponse for ValidWithRejectionRejection { +//! fn into_response(self) -> Response { +//! StatusCode::BAD_REQUEST.into_response() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ValidWithRejectionRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } //! } //! } +//! +//! # #[tokio::main] +//! # async fn main() -> anyhow::Result<()> { +//! # use axum::Router; +//! # let router = Router::new(); +//! # #[cfg(feature = "validator")] +//! # let router = router.nest("/validator", validator_example::router()); +//! # #[cfg(feature = "garde")] +//! # let router = router.nest("/garde", garde_example::router()); +//! # axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) +//! # .serve(router.into_make_service()) +//! # .await?; +//! # Ok(()) +//! # } //! ``` //! //! ### `WithRejection, R>` @@ -155,80 +280,153 @@ //! #### Example //! //! ```no_run -//! #![cfg(feature = "validator")] +//! #[cfg(feature = "validator")] +//! mod validator_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::WithRejection; +//! use axum_valid::{HasValidate, Valid, ValidRejection}; +//! use validator::Validate; //! -//! use axum::extract::FromRequestParts; -//! use axum::http::request::Parts; -//! use axum::response::{IntoResponse, Response}; -//! use axum::routing::post; -//! use axum::Router; -//! use axum_extra::extract::WithRejection; -//! use axum_valid::{HasValidate, Valid, ValidRejection}; -//! use validator::Validate; -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let router = Router::new().route("/with_rejection_valid", post(handler)); -//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) -//! .serve(router.into_make_service()) -//! .await?; -//! Ok(()) -//! } -//! async fn handler( -//! WithRejection(Valid(parameter), _): WithRejection< -//! Valid, -//! WithRejectionValidRejection, -//! >, -//! ) { -//! assert!(parameter.validate().is_ok()); -//! } +//! pub fn router() -> Router { +//! Router::new().route("/with_rejection_valid", post(handler)) +//! } //! -//! #[derive(Validate)] -//! pub struct Parameter { -//! #[validate(range(min = 5, max = 10))] -//! pub v0: i32, -//! #[validate(length(min = 1, max = 10))] -//! pub v1: String, -//! } +//! async fn handler( +//! WithRejection(Valid(parameter), _): WithRejection< +//! Valid, +//! WithRejectionValidRejection, +//! >, +//! ) { +//! assert!(parameter.validate().is_ok()); +//! } //! -//! impl HasValidate for Parameter { -//! type Validate = Self; +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[validate(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[validate(length(min = 1, max = 10))] +//! pub v1: String, +//! } //! -//! fn get_validate(&self) -> &Self::Validate { -//! self +//! impl HasValidate for Parameter { +//! type Validate = Self; +//! +//! fn get_validate(&self) -> &Self::Validate { +//! self +//! } +//! } +//! +//! pub struct ParameterRejection; +//! +//! impl IntoResponse for ParameterRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! +//! pub struct WithRejectionValidRejection; +//! +//! impl From> for WithRejectionValidRejection { +//! fn from(_inner: ValidRejection) -> Self { +//! todo!() +//! } +//! } +//! +//! impl IntoResponse for WithRejectionValidRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } //! } //! } //! -//! pub struct ParameterRejection; +//! #[cfg(feature = "garde")] +//! mod garde_example { +//! use axum::extract::FromRequestParts; +//! use axum::http::request::Parts; +//! use axum::response::{IntoResponse, Response}; +//! use axum::routing::post; +//! use axum::Router; +//! use axum_extra::extract::WithRejection; +//! use axum_valid::{HasValidate, Garde, GardeRejection}; +//! use garde::Validate; //! -//! impl IntoResponse for ParameterRejection { -//! fn into_response(self) -> Response { -//! todo!() +//! pub fn router() -> Router { +//! Router::new().route("/with_rejection_valid", post(handler)) //! } -//! } //! -//! #[axum::async_trait] -//! impl FromRequestParts for Parameter -//! where -//! S: Send + Sync, -//! { -//! type Rejection = ParameterRejection; -//! -//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { -//! todo!() +//! async fn handler( +//! WithRejection(Garde(parameter), _): WithRejection< +//! Garde, +//! WithRejectionGardeRejection, +//! >, +//! ) { +//! assert!(parameter.validate(&()).is_ok()); //! } -//! } //! -//! pub struct WithRejectionValidRejection; -//! -//! impl From> for WithRejectionValidRejection { -//! fn from(_inner: ValidRejection) -> Self { -//! todo!() +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[garde(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[garde(length(min = 1, max = 10))] +//! pub v1: String, //! } -//! } //! -//! impl IntoResponse for WithRejectionValidRejection { -//! fn into_response(self) -> Response { -//! todo!() +//! impl HasValidate for Parameter { +//! type Validate = Self; +//! +//! fn get_validate(&self) -> &Self::Validate { +//! self +//! } +//! } +//! +//! pub struct ParameterRejection; +//! +//! impl IntoResponse for ParameterRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } +//! } +//! +//! #[axum::async_trait] +//! impl FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! +//! pub struct WithRejectionGardeRejection; +//! +//! impl From> for WithRejectionGardeRejection { +//! fn from(_inner: GardeRejection) -> Self { +//! todo!() +//! } +//! } +//! +//! impl IntoResponse for WithRejectionGardeRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } //! } //! } //! ``` diff --git a/src/extra/protobuf.rs b/src/extra/protobuf.rs index 7f38956..570f173 100644 --- a/src/extra/protobuf.rs +++ b/src/extra/protobuf.rs @@ -63,10 +63,10 @@ //! //! #[derive(Validate, prost::Message)] //! pub struct Parameter { -//! #[validate(range(min = 5, max = 10))] +//! #[garde(range(min = 5, max = 10))] //! #[prost(int32, tag = "1")] //! pub v0: i32, -//! #[validate(length(min = 1, max = 10))] +//! #[garde(length(min = 1, max = 10))] //! #[prost(string, tag = "2")] //! pub v1: String, //! } diff --git a/src/extra/typed_path.rs b/src/extra/typed_path.rs index 3e26674..7a35af2 100644 --- a/src/extra/typed_path.rs +++ b/src/extra/typed_path.rs @@ -12,41 +12,87 @@ //! ## Example //! //! ```no_run -//! use axum::Router; -//! use axum_extra::routing::{RouterExt, TypedPath}; -//! use axum_valid::{HasValidate, Valid}; -//! use serde::Deserialize; -//! use validator::Validate; +//! #[cfg(feature = "validator")] +//! mod validator_example { +//! use axum::Router; +//! use axum_extra::routing::{RouterExt, TypedPath}; +//! use axum_valid::{HasValidate, Valid}; +//! use serde::Deserialize; +//! use validator::Validate; //! -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let router = Router::new().typed_get(handler); -//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) -//! .serve(router.into_make_service()) -//! .await?; -//! Ok(()) -//! } +//! pub fn router() -> Router { +//! Router::new().typed_get(handler) +//! } //! -//! async fn handler(parameter: Valid) { -//! assert!(parameter.validate().is_ok()); -//! } +//! async fn handler(parameter: Valid) { +//! assert!(parameter.validate().is_ok()); +//! } //! -//! #[derive(TypedPath, Deserialize, Validate)] -//! #[typed_path("/extra_typed_path/:v0/:v1")] -//! struct Parameter { -//! #[validate(range(min = 5, max = 10))] -//! v0: i32, -//! #[validate(length(min = 1, max = 10))] -//! v1: String, -//! } +//! #[derive(TypedPath, Deserialize, Validate)] +//! #[typed_path("/extra_typed_path/:v0/:v1")] +//! struct Parameter { +//! #[validate(range(min = 5, max = 10))] +//! v0: i32, +//! #[validate(length(min = 1, max = 10))] +//! v1: String, +//! } //! -//! impl HasValidate for Parameter { -//! type Validate = Self; +//! impl HasValidate for Parameter { +//! type Validate = Self; //! -//! fn get_validate(&self) -> &Self::Validate { -//! self +//! fn get_validate(&self) -> &Self::Validate { +//! self +//! } //! } //! } +//! +//! #[cfg(feature = "garde")] +//! mod garde_example { +//! use axum::Router; +//! use axum_extra::routing::{RouterExt, TypedPath}; +//! use axum_valid::{HasValidate, Garde}; +//! use serde::Deserialize; +//! use garde::Validate; +//! +//! pub fn router() -> Router { +//! Router::new().typed_get(handler) +//! } +//! +//! async fn handler(parameter: Garde) { +//! assert!(parameter.validate(&()).is_ok()); +//! } +//! +//! #[derive(TypedPath, Deserialize, Validate)] +//! #[typed_path("/extra_typed_path/:v0/:v1")] +//! struct Parameter { +//! #[garde(range(min = 5, max = 10))] +//! v0: i32, +//! #[garde(length(min = 1, max = 10))] +//! v1: String, +//! } +//! +//! impl HasValidate for Parameter { +//! type Validate = Self; +//! +//! fn get_validate(&self) -> &Self::Validate { +//! self +//! } +//! } +//! } +//! +//! # #[tokio::main] +//! # async fn main() -> anyhow::Result<()> { +//! # use axum::Router; +//! # let router = Router::new(); +//! # #[cfg(feature = "validator")] +//! # let router = router.nest("/validator", validator_example::router()); +//! # #[cfg(feature = "garde")] +//! # let router = router.nest("/garde", garde_example::router()); +//! # axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) +//! # .serve(router.into_make_service()) +//! # .await?; +//! # Ok(()) +//! # } //! ``` #[cfg(feature = "garde")] diff --git a/src/garde.rs b/src/garde.rs index 7c3c3ff..7bb3f1a 100644 --- a/src/garde.rs +++ b/src/garde.rs @@ -1,4 +1,9 @@ //! # Garde support +//! +//! ## Feature +//! +//! Enable the `garde` feature to use `Garde`. +//! #[cfg(test)] pub mod test; @@ -40,7 +45,7 @@ impl Display for Garde { } impl Garde { - /// Consumes the `ValidEx` and returns the validated data within. + /// Consumes the `Garde` and returns the validated data within. /// /// This returns the `E` type which represents the data that has been /// successfully validated. @@ -60,14 +65,14 @@ fn response_builder(ve: garde::Report) -> Response { } } -/// `ValidRejection` is returned when the `Valid` extractor fails. +/// `GardeRejection` is returned when the `Garde` extractor fails. /// -/// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation -/// logic itself (encapsulated in `Valid`), and errors that may arise within the inner extractor (represented by `Inner`). +/// This enumeration captures two types of errors that can occur when using `Garde`: errors related to the validation +/// logic itself (encapsulated in `Garde`), and errors that may arise within the inner extractor (represented by `Inner`). /// #[derive(Debug)] pub enum GardeRejection { - /// `Valid` variant captures errors related to the validation logic. It contains `garde::Report` + /// `Report` variant captures errors related to the validation logic. It contains `garde::Report` /// which is a collection of validation failures for each field. Report(garde::Report), /// `Inner` variant represents potential errors that might occur within the inner extractor. @@ -152,6 +157,8 @@ where #[cfg(test)] mod tests { use super::*; + use garde::{Path, Report}; + use std::io; const GARDE: &str = "garde"; @@ -163,6 +170,34 @@ mod tests { inner.push_str(GARDE); v.deref_mut().push_str(GARDE); assert_eq!(&inner, v.deref()); + println!("{}", v); assert_eq!(inner, v.into_inner()); } + + #[test] + fn display_error() { + // ValidRejection::Valid Display + let mut report = Report::new(); + report.append(Path::empty(), garde::Error::new(GARDE)); + let s = report.to_string(); + let vr = GardeRejection::::Report(report); + assert_eq!(vr.to_string(), s); + + // ValidRejection::Inner Display + let inner = String::from(GARDE); + let vr = GardeRejection::::Inner(inner.clone()); + assert_eq!(inner.to_string(), vr.to_string()); + + // ValidRejection::Valid Error + let mut report = Report::new(); + report.append(Path::empty(), garde::Error::new(GARDE)); + let vr = GardeRejection::::Report(report); + assert!(matches!(vr.source(), Some(source) if source.downcast_ref::().is_some())); + + // ValidRejection::Valid Error + let vr = GardeRejection::::Inner(io::Error::new(io::ErrorKind::Other, GARDE)); + assert!( + matches!(vr.source(), Some(source) if source.downcast_ref::().is_some()) + ); + } } diff --git a/src/typed_header.rs b/src/typed_header.rs index 8a71193..4dfd05e 100644 --- a/src/typed_header.rs +++ b/src/typed_header.rs @@ -12,55 +12,111 @@ //! ## Example //! //! ```no_run -//! #![cfg(feature = "validator")] +//! #[cfg(feature = "validator")] +//! mod validator_example { +//! use axum::headers::{Error, Header, HeaderValue}; +//! use axum::http::HeaderName; +//! use axum::routing::post; +//! use axum::{Router, TypedHeader}; +//! use axum_valid::Valid; +//! use validator::Validate; //! -//! use axum::headers::{Error, Header, HeaderValue}; -//! use axum::http::HeaderName; -//! use axum::routing::post; -//! use axum::{Router, TypedHeader}; -//! use axum_valid::Valid; -//! use validator::Validate; -//! -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let router = Router::new().route("/typed_header", post(handler)); -//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) -//! .serve(router.into_make_service()) -//! .await?; -//! Ok(()) -//! } -//! -//! async fn handler(Valid(TypedHeader(parameter)): Valid>) { -//! assert!(parameter.validate().is_ok()); -//! } -//! -//! #[derive(Validate)] -//! pub struct Parameter { -//! #[validate(range(min = 5, max = 10))] -//! pub v0: i32, -//! #[validate(length(min = 1, max = 10))] -//! pub v1: String, -//! } -//! -//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header"); -//! -//! impl Header for Parameter { -//! fn name() -> &'static HeaderName { -//! &HEADER_NAME +//! pub fn router() -> Router { +//! Router::new().route("/typed_header", post(handler)) //! } //! -//! fn decode<'i, I>(_values: &mut I) -> Result -//! where -//! Self: Sized, -//! I: Iterator, -//! { -//! todo!() +//! async fn handler(Valid(TypedHeader(parameter)): Valid>) { +//! assert!(parameter.validate().is_ok()); //! } //! -//! fn encode>(&self, _values: &mut E) { -//! todo!() +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[validate(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[validate(length(min = 1, max = 10))] +//! pub v1: String, +//! } +//! +//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header"); +//! +//! impl Header for Parameter { +//! fn name() -> &'static HeaderName { +//! &HEADER_NAME +//! } +//! +//! fn decode<'i, I>(_values: &mut I) -> Result +//! where +//! Self: Sized, +//! I: Iterator, +//! { +//! todo!() +//! } +//! +//! fn encode>(&self, _values: &mut E) { +//! todo!() +//! } //! } //! } +//! +//! #[cfg(feature = "garde")] +//! mod garde_example { +//! use axum::headers::{Error, Header, HeaderValue}; +//! use axum::http::HeaderName; +//! use axum::routing::post; +//! use axum::{Router, TypedHeader}; +//! use axum_valid::Garde; +//! use garde::Validate; +//! +//! pub fn router() -> Router { +//! Router::new().route("/typed_header", post(handler)) +//! } +//! +//! async fn handler(Garde(TypedHeader(parameter)): Garde>) { +//! assert!(parameter.validate(&()).is_ok()); +//! } +//! +//! #[derive(Validate)] +//! pub struct Parameter { +//! #[garde(range(min = 5, max = 10))] +//! pub v0: i32, +//! #[garde(length(min = 1, max = 10))] +//! pub v1: String, +//! } +//! +//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header"); +//! +//! impl Header for Parameter { +//! fn name() -> &'static HeaderName { +//! &HEADER_NAME +//! } +//! +//! fn decode<'i, I>(_values: &mut I) -> Result +//! where +//! Self: Sized, +//! I: Iterator, +//! { +//! todo!() +//! } +//! +//! fn encode>(&self, _values: &mut E) { +//! todo!() +//! } +//! } +//! } +//! +//! # #[tokio::main] +//! # async fn main() -> anyhow::Result<()> { +//! # use axum::Router; +//! # let router = Router::new(); +//! # #[cfg(feature = "validator")] +//! # let router = router.nest("/validator", validator_example::router()); +//! # #[cfg(feature = "garde")] +//! # let router = router.nest("/garde", garde_example::router()); +//! # axum::Server::bind(&([0u8, 0, 0, 0], 8080).into()) +//! # .serve(router.into_make_service()) +//! # .await?; +//! # Ok(()) +//! # } //! ``` use crate::HasValidate; diff --git a/src/validator.rs b/src/validator.rs index c7818fb..dbafa0b 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -1,4 +1,9 @@ //! # Validator support +//! +//! ## Feature +//! +//! Enable the `validator` feature (enabled by default) to use `Valid` and `ValidEx`. +//! #[cfg(test)] pub mod test; @@ -287,6 +292,7 @@ where #[cfg(test)] pub mod tests { use super::*; + use std::fmt::Formatter; use std::io; use validator::ValidationError; const TEST: &str = "test"; @@ -299,6 +305,7 @@ pub mod tests { inner.push_str(TEST); v.deref_mut().push_str(TEST); assert_eq!(&inner, v.deref()); + println!("{}", v); assert_eq!(inner, v.into_inner()); } @@ -316,12 +323,18 @@ pub mod tests { Ok(()) } - #[derive(Validate)] + #[derive(Debug, Validate)] struct Data { #[validate(custom(function = "validate", arg = "i32"))] v: i32, } + impl Display for Data { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } + } + struct DataVA { a: i32, } @@ -337,6 +350,7 @@ pub mod tests { let data = Data { v: 12 }; let args = DataVA { a: 123 }; let ve = ValidEx(data, args); + println!("{}", ve); assert_eq!(ve.v, 12); let a = ve.arguments(); assert_eq!(a, 123);