update README.md and doc tests

This commit is contained in:
gengteng
2023-10-09 14:57:58 +08:00
parent c421807bfc
commit cc4cd9f70c
9 changed files with 662 additions and 248 deletions

View File

@@ -8,6 +8,18 @@
### Fixed ### 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) ## axum-valid 0.9.0 (2023-09-29)
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "axum-valid" 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." description = "Provides validation extractors for your Axum application to validate data using validator, garde, or both."
authors = ["GengTeng <me@gteng.org>"] authors = ["GengTeng <me@gteng.org>"]
license = "MIT" license = "MIT"
@@ -21,7 +21,7 @@ categories = [
edition = "2021" edition = "2021"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all_types"] features = ["all_types", "garde"]
[dependencies] [dependencies]
axum = { version = "0.6.20", default-features = false } axum = { version = "0.6.20", default-features = false }

View File

@@ -126,7 +126,7 @@ To see how each extractor can be used with `Valid`, please refer to the example
## Argument-Based Validation ## 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 ```rust,no_run
#[cfg(feature = "validator")] #[cfg(feature = "validator")]
@@ -197,15 +197,63 @@ mod validator_example {
}) })
// NOTE: The PagerValidArgs can also be stored in a XxxState, // NOTE: The PagerValidArgs can also be stored in a XxxState,
// make sure it implements FromRef<XxxState>. // make sure it implements FromRef<XxxState>.
// Consider using Arc to reduce deep copying costs.
} }
} }
#[cfg(feature = "garde")] #[cfg(feature = "garde")]
mod garde_example { 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<usize>,
page_no_range: RangeFrom<usize>,
}
pub async fn pager_from_form_garde(Garde(Form(pager)): Garde<Form<Pager>>) {
assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no));
}
pub fn router() -> Router { pub fn router() -> Router {
Router::new() 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<XxxState>.
// Consider using Arc to reduce deep copying costs.
} }
} }
@@ -228,26 +276,30 @@ Current module documentation predominantly showcases `Valid` examples, the usage
## Features ## Features
| Feature | Description | Module | Default | Example | Tests | | Feature | Description | Module | Default | Example | Tests |
|------------------|------------------------------------------------------------------------------------------------------|-----------------------------------------|---------|---------|-------| |------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|---------|---------|-------|
| default | Enables support for `Path`, `Query`, `Json` and `Form` | [`path`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ | | default | Enables `validator` and support for `Query`, `Json` and `Form` | [`validator`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
| json | Enables support for `Json` | [`json`] | ✅ | ✅ | ✅ | | validator | Enables `validator` | [`validator`] | ✅ | ✅ | ✅ |
| query | Enables support for `Query` | [`query`] | | ✅ | ✅ | | garde | Enables `garde` | [`garde`] | | ✅ | ✅ |
| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ | | basic | Enables support for `Query`, `Json` and `Form` | [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
| typed_header | Enables support for `TypedHeader` | [`typed_header`] | | ✅ | ✅ | | json | Enables support for `Json` | [`json`] | | ✅ | ✅ |
| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | | ✅ | ✅ | | query | Enables support for `Query` | [`query`] | | ✅ | ✅ |
| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | | ✅ | ✅ | | form | Enables support for `Form` | [`form`] | | ✅ | ✅ |
| yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ | | typed_header | Enables support for `TypedHeader` | [`typed_header`] | ❌ | ✅ | ✅ |
| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ | | typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | ❌ | ✅ | ✅ |
| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ | | msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | ❌ | ✅ | ✅ |
| extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ | | yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ |
| extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ | | extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ |
| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ | | extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ |
| all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ | | extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ |
| all_types | Enables support for all extractors above | N/A | ❌ | ✅ | ✅ | | extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ |
| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | [`VALIDATION_ERROR_STATUS`] | ❌ | ✅ | ✅ | | extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ |
| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | N/A | ❌ | ✅ | ✅ | | all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ |
| full | Enables all features | 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 ## Compatibility
@@ -261,6 +313,7 @@ This project is licensed under the MIT License.
* [axum](https://crates.io/crates/axum) * [axum](https://crates.io/crates/axum)
* [validator](https://crates.io/crates/validator) * [validator](https://crates.io/crates/validator)
* [garde](https://crates.io/crates/garde)
* [serde](https://crates.io/crates/serde) * [serde](https://crates.io/crates/serde)
* [axum-extra](https://crates.io/crates/axum-extra) * [axum-extra](https://crates.io/crates/axum-extra)
* [axum-yaml](https://crates.io/crates/axum-yaml) * [axum-yaml](https://crates.io/crates/axum-yaml)

View File

@@ -26,54 +26,114 @@
//! #### Example //! #### Example
//! //!
//! ```no_run //! ```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; //! pub fn router() -> Router {
//! use axum::http::request::Parts; //! Router::new().route("/cached", post(handler))
//! 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<Cached<Parameter>>) {
//! 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 struct ParameterRejection; //! async fn handler(Valid(Cached(parameter)): Valid<Cached<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//! //!
//! impl IntoResponse for ParameterRejection { //! #[derive(Validate, Clone)]
//! fn into_response(self) -> Response { //! pub struct Parameter {
//! todo!() //! #[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<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! 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<Cached<Parameter>>) {
//! 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<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! } //! }
//! } //! }
//! //!
//! #[axum::async_trait] //! # #[tokio::main]
//! impl<S> FromRequestParts<S> for Parameter //! # async fn main() -> anyhow::Result<()> {
//! where //! # use axum::Router;
//! S: Send + Sync, //! # let router = Router::new();
//! { //! # #[cfg(feature = "validator")]
//! type Rejection = ParameterRejection; //! # let router = router.nest("/validator", validator_example::router());
//! //! # #[cfg(feature = "garde")]
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> { //! # let router = router.nest("/garde", garde_example::router());
//! todo!() //! # axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! } //! # .serve(router.into_make_service())
//! } //! # .await?;
//! # Ok(())
//! # }
//! ``` //! ```
//! //!
//! ### `Valid<WithRejection<T, R>>` //! ### `Valid<WithRejection<T, R>>`
@@ -87,60 +147,125 @@
//! #### Example //! #### Example
//! //!
//! ```no_run //! ```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; //! pub fn router() -> Router {
//! use axum::http::request::Parts; //! Router::new().route("/valid_with_rejection", post(handler))
//! 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<Parameter, ValidWithRejectionRejection>,
//! >,
//! ) {
//! assert!(parameter.validate().is_ok());
//! }
//! //!
//! #[derive(Validate)] //! async fn handler(
//! pub struct Parameter { //! Valid(WithRejection(parameter, _)): Valid<
//! #[validate(range(min = 5, max = 10))] //! WithRejection<Parameter, ValidWithRejectionRejection>,
//! pub v0: i32, //! >,
//! #[validate(length(min = 1, max = 10))] //! ) {
//! pub v1: String, //! 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 { //! pub struct ValidWithRejectionRejection;
//! fn into_response(self) -> Response { //!
//! StatusCode::BAD_REQUEST.into_response() //! impl IntoResponse for ValidWithRejectionRejection {
//! fn into_response(self) -> Response {
//! StatusCode::BAD_REQUEST.into_response()
//! }
//! }
//!
//! #[axum::async_trait]
//! impl<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ValidWithRejectionRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! } //! }
//! } //! }
//! //!
//! #[axum::async_trait] //! #[cfg(feature = "garde")]
//! impl<S> FromRequestParts<S> for Parameter //! mod garde_example {
//! where //! use axum::extract::FromRequestParts;
//! S: Send + Sync, //! use axum::http::request::Parts;
//! { //! use axum::http::StatusCode;
//! type Rejection = ValidWithRejectionRejection; //! 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<Self, Self::Rejection> { //! pub fn router() -> Router {
//! todo!() //! Router::new().route("/valid_with_rejection", post(handler))
//! }
//!
//! async fn handler(
//! Garde(WithRejection(parameter, _)): Garde<
//! WithRejection<Parameter, ValidWithRejectionRejection>,
//! >,
//! ) {
//! 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<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ValidWithRejectionRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! 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<Valid<T>, R>` //! ### `WithRejection<Valid<T>, R>`
@@ -155,80 +280,153 @@
//! #### Example //! #### Example
//! //!
//! ```no_run //! ```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; //! pub fn router() -> Router {
//! use axum::http::request::Parts; //! Router::new().route("/with_rejection_valid", post(handler))
//! 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<Parameter>,
//! WithRejectionValidRejection,
//! >,
//! ) {
//! assert!(parameter.validate().is_ok());
//! }
//! //!
//! #[derive(Validate)] //! async fn handler(
//! pub struct Parameter { //! WithRejection(Valid(parameter), _): WithRejection<
//! #[validate(range(min = 5, max = 10))] //! Valid<Parameter>,
//! pub v0: i32, //! WithRejectionValidRejection,
//! #[validate(length(min = 1, max = 10))] //! >,
//! pub v1: String, //! ) {
//! } //! assert!(parameter.validate().is_ok());
//! }
//! //!
//! impl HasValidate for Parameter { //! #[derive(Validate)]
//! type Validate = Self; //! 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 { //! impl HasValidate for Parameter {
//! self //! 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<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! }
//!
//! pub struct WithRejectionValidRejection;
//!
//! impl From<ValidRejection<ParameterRejection>> for WithRejectionValidRejection {
//! fn from(_inner: ValidRejection<ParameterRejection>) -> 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 { //! pub fn router() -> Router {
//! fn into_response(self) -> Response { //! Router::new().route("/with_rejection_valid", post(handler))
//! todo!()
//! } //! }
//! }
//! //!
//! #[axum::async_trait] //! async fn handler(
//! impl<S> FromRequestParts<S> for Parameter //! WithRejection(Garde(parameter), _): WithRejection<
//! where //! Garde<Parameter>,
//! S: Send + Sync, //! WithRejectionGardeRejection,
//! { //! >,
//! type Rejection = ParameterRejection; //! ) {
//! //! assert!(parameter.validate(&()).is_ok());
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! } //! }
//! }
//! //!
//! pub struct WithRejectionValidRejection; //! #[derive(Validate)]
//! //! pub struct Parameter {
//! impl From<ValidRejection<ParameterRejection>> for WithRejectionValidRejection { //! #[garde(range(min = 5, max = 10))]
//! fn from(_inner: ValidRejection<ParameterRejection>) -> Self { //! pub v0: i32,
//! todo!() //! #[garde(length(min = 1, max = 10))]
//! pub v1: String,
//! } //! }
//! }
//! //!
//! impl IntoResponse for WithRejectionValidRejection { //! impl HasValidate for Parameter {
//! fn into_response(self) -> Response { //! type Validate = Self;
//! todo!() //!
//! fn get_validate(&self) -> &Self::Validate {
//! self
//! }
//! }
//!
//! pub struct ParameterRejection;
//!
//! impl IntoResponse for ParameterRejection {
//! fn into_response(self) -> Response {
//! todo!()
//! }
//! }
//!
//! #[axum::async_trait]
//! impl<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! }
//!
//! pub struct WithRejectionGardeRejection;
//!
//! impl From<GardeRejection<ParameterRejection>> for WithRejectionGardeRejection {
//! fn from(_inner: GardeRejection<ParameterRejection>) -> Self {
//! todo!()
//! }
//! }
//!
//! impl IntoResponse for WithRejectionGardeRejection {
//! fn into_response(self) -> Response {
//! todo!()
//! }
//! } //! }
//! } //! }
//! ``` //! ```

View File

@@ -63,10 +63,10 @@
//! //!
//! #[derive(Validate, prost::Message)] //! #[derive(Validate, prost::Message)]
//! pub struct Parameter { //! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))] //! #[garde(range(min = 5, max = 10))]
//! #[prost(int32, tag = "1")] //! #[prost(int32, tag = "1")]
//! pub v0: i32, //! pub v0: i32,
//! #[validate(length(min = 1, max = 10))] //! #[garde(length(min = 1, max = 10))]
//! #[prost(string, tag = "2")] //! #[prost(string, tag = "2")]
//! pub v1: String, //! pub v1: String,
//! } //! }

View File

@@ -12,41 +12,87 @@
//! ## Example //! ## Example
//! //!
//! ```no_run //! ```no_run
//! use axum::Router; //! #[cfg(feature = "validator")]
//! use axum_extra::routing::{RouterExt, TypedPath}; //! mod validator_example {
//! use axum_valid::{HasValidate, Valid}; //! use axum::Router;
//! use serde::Deserialize; //! use axum_extra::routing::{RouterExt, TypedPath};
//! use validator::Validate; //! use axum_valid::{HasValidate, Valid};
//! use serde::Deserialize;
//! use validator::Validate;
//! //!
//! #[tokio::main] //! pub fn router() -> Router {
//! async fn main() -> anyhow::Result<()> { //! Router::new().typed_get(handler)
//! let router = Router::new().typed_get(handler); //! }
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//! //!
//! async fn handler(parameter: Valid<Parameter>) { //! async fn handler(parameter: Valid<Parameter>) {
//! assert!(parameter.validate().is_ok()); //! assert!(parameter.validate().is_ok());
//! } //! }
//! //!
//! #[derive(TypedPath, Deserialize, Validate)] //! #[derive(TypedPath, Deserialize, Validate)]
//! #[typed_path("/extra_typed_path/:v0/:v1")] //! #[typed_path("/extra_typed_path/:v0/:v1")]
//! struct Parameter { //! struct Parameter {
//! #[validate(range(min = 5, max = 10))] //! #[validate(range(min = 5, max = 10))]
//! v0: i32, //! v0: i32,
//! #[validate(length(min = 1, max = 10))] //! #[validate(length(min = 1, max = 10))]
//! v1: String, //! v1: String,
//! } //! }
//! //!
//! impl HasValidate for Parameter { //! impl HasValidate for Parameter {
//! type Validate = Self; //! type Validate = Self;
//! //!
//! fn get_validate(&self) -> &Self::Validate { //! fn get_validate(&self) -> &Self::Validate {
//! self //! 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<Parameter>) {
//! 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")] #[cfg(feature = "garde")]

View File

@@ -1,4 +1,9 @@
//! # Garde support //! # Garde support
//!
//! ## Feature
//!
//! Enable the `garde` feature to use `Garde<E>`.
//!
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
@@ -40,7 +45,7 @@ impl<T: Display> Display for Garde<T> {
} }
impl<E> Garde<E> { impl<E> Garde<E> {
/// 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 /// This returns the `E` type which represents the data that has been
/// successfully validated. /// 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 /// This enumeration captures two types of errors that can occur when using `Garde`: errors related to the validation
/// logic itself (encapsulated in `Valid`), and errors that may arise within the inner extractor (represented by `Inner`). /// logic itself (encapsulated in `Garde`), and errors that may arise within the inner extractor (represented by `Inner`).
/// ///
#[derive(Debug)] #[derive(Debug)]
pub enum GardeRejection<E> { pub enum GardeRejection<E> {
/// `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. /// which is a collection of validation failures for each field.
Report(garde::Report), Report(garde::Report),
/// `Inner` variant represents potential errors that might occur within the inner extractor. /// `Inner` variant represents potential errors that might occur within the inner extractor.
@@ -152,6 +157,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use garde::{Path, Report};
use std::io;
const GARDE: &str = "garde"; const GARDE: &str = "garde";
@@ -163,6 +170,34 @@ mod tests {
inner.push_str(GARDE); inner.push_str(GARDE);
v.deref_mut().push_str(GARDE); v.deref_mut().push_str(GARDE);
assert_eq!(&inner, v.deref()); assert_eq!(&inner, v.deref());
println!("{}", v);
assert_eq!(inner, v.into_inner()); 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::<String>::Report(report);
assert_eq!(vr.to_string(), s);
// ValidRejection::Inner Display
let inner = String::from(GARDE);
let vr = GardeRejection::<String>::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::<io::Error>::Report(report);
assert!(matches!(vr.source(), Some(source) if source.downcast_ref::<Report>().is_some()));
// ValidRejection::Valid Error
let vr = GardeRejection::<io::Error>::Inner(io::Error::new(io::ErrorKind::Other, GARDE));
assert!(
matches!(vr.source(), Some(source) if source.downcast_ref::<io::Error>().is_some())
);
}
} }

View File

@@ -12,55 +12,111 @@
//! ## Example //! ## Example
//! //!
//! ```no_run //! ```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}; //! pub fn router() -> Router {
//! use axum::http::HeaderName; //! Router::new().route("/typed_header", post(handler))
//! 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<TypedHeader<Parameter>>) {
//! 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
//! } //! }
//! //!
//! fn decode<'i, I>(_values: &mut I) -> Result<Self, Error> //! async fn handler(Valid(TypedHeader(parameter)): Valid<TypedHeader<Parameter>>) {
//! where //! assert!(parameter.validate().is_ok());
//! Self: Sized,
//! I: Iterator<Item = &'i HeaderValue>,
//! {
//! todo!()
//! } //! }
//! //!
//! fn encode<E: Extend<HeaderValue>>(&self, _values: &mut E) { //! #[derive(Validate)]
//! todo!() //! 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<Self, Error>
//! where
//! Self: Sized,
//! I: Iterator<Item = &'i HeaderValue>,
//! {
//! todo!()
//! }
//!
//! fn encode<E: Extend<HeaderValue>>(&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<TypedHeader<Parameter>>) {
//! 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<Self, Error>
//! where
//! Self: Sized,
//! I: Iterator<Item = &'i HeaderValue>,
//! {
//! todo!()
//! }
//!
//! fn encode<E: Extend<HeaderValue>>(&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; use crate::HasValidate;

View File

@@ -1,4 +1,9 @@
//! # Validator support //! # Validator support
//!
//! ## Feature
//!
//! Enable the `validator` feature (enabled by default) to use `Valid<E>` and `ValidEx<E, A>`.
//!
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
@@ -287,6 +292,7 @@ where
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use std::fmt::Formatter;
use std::io; use std::io;
use validator::ValidationError; use validator::ValidationError;
const TEST: &str = "test"; const TEST: &str = "test";
@@ -299,6 +305,7 @@ pub mod tests {
inner.push_str(TEST); inner.push_str(TEST);
v.deref_mut().push_str(TEST); v.deref_mut().push_str(TEST);
assert_eq!(&inner, v.deref()); assert_eq!(&inner, v.deref());
println!("{}", v);
assert_eq!(inner, v.into_inner()); assert_eq!(inner, v.into_inner());
} }
@@ -316,12 +323,18 @@ pub mod tests {
Ok(()) Ok(())
} }
#[derive(Validate)] #[derive(Debug, Validate)]
struct Data { struct Data {
#[validate(custom(function = "validate", arg = "i32"))] #[validate(custom(function = "validate", arg = "i32"))]
v: i32, v: i32,
} }
impl Display for Data {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
struct DataVA { struct DataVA {
a: i32, a: i32,
} }
@@ -337,6 +350,7 @@ pub mod tests {
let data = Data { v: 12 }; let data = Data { v: 12 };
let args = DataVA { a: 123 }; let args = DataVA { a: 123 };
let ve = ValidEx(data, args); let ve = ValidEx(data, args);
println!("{}", ve);
assert_eq!(ve.v, 12); assert_eq!(ve.v, 12);
let a = ve.arguments(); let a = ve.arguments();
assert_eq!(a, 123); assert_eq!(a, 123);