refactor doc tests
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "axum-valid"
|
name = "axum-valid"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
description = "Provide validator extractor for your axum application."
|
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"
|
||||||
homepage = "https://github.com/gengteng/axum-valid"
|
homepage = "https://github.com/gengteng/axum-valid"
|
||||||
|
|||||||
285
README.md
285
README.md
@@ -7,11 +7,15 @@
|
|||||||
[](https://github.com/gengteng/axum-valid/actions/workflows/ci.yml)
|
[](https://github.com/gengteng/axum-valid/actions/workflows/ci.yml)
|
||||||
[](https://coveralls.io/github/gengteng/axum-valid?branch=main)
|
[](https://coveralls.io/github/gengteng/axum-valid?branch=main)
|
||||||
|
|
||||||
This crate provides a `Valid` type for use with `Json`, `Path`, `Query`, and `Form` extractors to validate entities implementing the `Validate` trait from the `validator` crate.
|
This crate provides data validation capabilities for Axum based on the `validator` and `garde` crates.
|
||||||
|
|
||||||
A `ValidEx` type is also available. Similar to `Valid`, `ValidEx` can execute validations requiring extra arguments with types that implement the `ValidateArgs` trait from the `validator` crate.
|
`validator` support is included by default. To use `garde`, enable it via the `garde` feature. `garde` alone can also be enabled by using `default-features = false`.
|
||||||
|
|
||||||
Additional extractors such as `TypedHeader`, `MsgPack`, `Yaml`, and others are supported through optional features. The complete list of supported extractors can be found in the Features section below.
|
The `Valid` type enables validation using `validator` for extractors like `Json`, `Path`, `Query` and `Form`. For validations requiring extra arguments, the `ValidEx` type is offered.
|
||||||
|
|
||||||
|
The `Garde` type supports both argument and non-argument validations using `garde` in a unified way.
|
||||||
|
|
||||||
|
Additional extractors like `TypedHeader`, `MsgPack` and `Yaml` are also supported through optional features. Refer to `Features` for details.
|
||||||
|
|
||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
@@ -20,43 +24,101 @@ cargo add axum-valid
|
|||||||
```
|
```
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use validator::Validate;
|
#[cfg(feature = "validator")]
|
||||||
use serde::Deserialize;
|
mod validator_example {
|
||||||
use axum_valid::Valid;
|
use validator::Validate;
|
||||||
use axum::extract::Query;
|
use serde::Deserialize;
|
||||||
use axum::{Json, Router};
|
use axum_valid::Valid;
|
||||||
use axum::routing::{get, post};
|
use axum::extract::Query;
|
||||||
|
use axum::{Json, Router};
|
||||||
#[derive(Debug, Validate, Deserialize)]
|
use axum::routing::{get, post};
|
||||||
pub struct Pager {
|
|
||||||
#[validate(range(min = 1, max = 50))]
|
#[derive(Debug, Validate, Deserialize)]
|
||||||
pub page_size: usize,
|
pub struct Pager {
|
||||||
#[validate(range(min = 1))]
|
#[validate(range(min = 1, max = 50))]
|
||||||
pub page_no: usize,
|
pub page_size: usize,
|
||||||
|
#[validate(range(min = 1))]
|
||||||
|
pub page_no: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pager_from_query(
|
||||||
|
Valid(Query(pager)): Valid<Query<Pager>>,
|
||||||
|
) {
|
||||||
|
assert!((1..=50).contains(&pager.page_size));
|
||||||
|
assert!((1..).contains(&pager.page_no));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pager_from_json(
|
||||||
|
pager: Valid<Json<Pager>>,
|
||||||
|
) {
|
||||||
|
assert!((1..=50).contains(&pager.page_size));
|
||||||
|
assert!((1..).contains(&pager.page_no));
|
||||||
|
// NOTE: support automatic dereferencing
|
||||||
|
println!("page_no: {}, page_size: {}", pager.page_no, pager.page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/query", get(pager_from_query))
|
||||||
|
.route("/json", post(pager_from_json));
|
||||||
|
axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
.serve(router.into_make_service())
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pager_from_query(
|
#[cfg(feature = "garde")]
|
||||||
Valid(Query(pager)): Valid<Query<Pager>>,
|
mod garde_example {
|
||||||
) {
|
use garde::Validate;
|
||||||
assert!((1..=50).contains(&pager.page_size));
|
use serde::Deserialize;
|
||||||
assert!((1..).contains(&pager.page_no));
|
use axum_valid::Garde;
|
||||||
|
use axum::extract::Query;
|
||||||
|
use axum::{Json, Router};
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
|
||||||
|
#[derive(Debug, Validate, Deserialize)]
|
||||||
|
pub struct Pager {
|
||||||
|
#[garde(range(min = 1, max = 50))]
|
||||||
|
pub page_size: usize,
|
||||||
|
#[garde(range(min = 1))]
|
||||||
|
pub page_no: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pager_from_query(
|
||||||
|
Garde(Query(pager)): Garde<Query<Pager>>,
|
||||||
|
) {
|
||||||
|
assert!((1..=50).contains(&pager.page_size));
|
||||||
|
assert!((1..).contains(&pager.page_no));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pager_from_json(
|
||||||
|
pager: Garde<Json<Pager>>,
|
||||||
|
) {
|
||||||
|
assert!((1..=50).contains(&pager.page_size));
|
||||||
|
assert!((1..).contains(&pager.page_no));
|
||||||
|
// NOTE: support automatic dereferencing
|
||||||
|
println!("page_no: {}, page_size: {}", pager.page_no, pager.page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/query", get(pager_from_query))
|
||||||
|
.route("/json", post(pager_from_json));
|
||||||
|
axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
.serve(router.into_make_service())
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pager_from_json(
|
fn main() -> anyhow::Result<()> {
|
||||||
Valid(Json(pager)): Valid<Json<Pager>>,
|
#[cfg(feature = "validator")]
|
||||||
) {
|
validator_example::launch()?;
|
||||||
assert!((1..=50).contains(&pager.page_size));
|
#[cfg(feature = "garde")]
|
||||||
assert!((1..).contains(&pager.page_no));
|
garde_example::launch()?;
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> anyhow::Result<()> {
|
|
||||||
let router = Router::new()
|
|
||||||
.route("/query", get(pager_from_query))
|
|
||||||
.route("/json", post(pager_from_json));
|
|
||||||
axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
|
||||||
.serve(router.into_make_service())
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -70,77 +132,96 @@ To see how each extractor can be used with `Valid`, please refer to the example
|
|||||||
Here's a basic example of using the `ValidEx` extractor to validate data in a `Form` using arguments:
|
Here's a basic example of using the `ValidEx` extractor to validate data in a `Form` using arguments:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use axum::routing::post;
|
#[cfg(feature = "validator")]
|
||||||
use axum::{Form, Router};
|
mod validator_example {
|
||||||
use axum_valid::{Arguments, ValidEx};
|
use axum::routing::post;
|
||||||
use serde::Deserialize;
|
use axum::{Form, Router};
|
||||||
use std::ops::{RangeFrom, RangeInclusive};
|
use axum_valid::{Arguments, ValidEx};
|
||||||
use validator::{Validate, ValidateArgs, ValidationError};
|
use serde::Deserialize;
|
||||||
|
use std::ops::{RangeFrom, RangeInclusive};
|
||||||
// NOTE: When some fields use custom validation functions with arguments,
|
use validator::{Validate, ValidateArgs, ValidationError};
|
||||||
// `#[derive(Validate)]` will implement `ValidateArgs` instead of `Validate` for the type.
|
|
||||||
// The validation arguments will be a tuple of all the field validation args.
|
// NOTE: When some fields use custom validation functions with arguments,
|
||||||
// In this example it is (&RangeInclusive<usize>, &RangeFrom<usize>).
|
// `#[derive(Validate)]` will implement `ValidateArgs` instead of `Validate` for the type.
|
||||||
// For more detailed information and understanding of `ValidateArgs` and their argument types,
|
// The validation arguments will be a tuple of all the field validation args.
|
||||||
// please refer to the `validator` crate documentation.
|
// In this example it is (&RangeInclusive<usize>, &RangeFrom<usize>).
|
||||||
#[derive(Debug, Validate, Deserialize)]
|
// For more detailed information and understanding of `ValidateArgs` and their argument types,
|
||||||
pub struct Pager {
|
// please refer to the `validator` crate documentation.
|
||||||
#[validate(custom(function = "validate_page_size", arg = "&'v_a RangeInclusive<usize>"))]
|
#[derive(Debug, Validate, Deserialize)]
|
||||||
pub page_size: usize,
|
pub struct Pager {
|
||||||
#[validate(custom(function = "validate_page_no", arg = "&'v_a RangeFrom<usize>"))]
|
#[validate(custom(function = "validate_page_size", arg = "&'v_a RangeInclusive<usize>"))]
|
||||||
pub page_no: usize,
|
pub page_size: usize,
|
||||||
}
|
#[validate(custom(function = "validate_page_no", arg = "&'v_a RangeFrom<usize>"))]
|
||||||
|
pub page_no: usize,
|
||||||
fn validate_page_size(v: usize, args: &RangeInclusive<usize>) -> Result<(), ValidationError> {
|
}
|
||||||
args.contains(&v)
|
|
||||||
.then_some(())
|
fn validate_page_size(v: usize, args: &RangeInclusive<usize>) -> Result<(), ValidationError> {
|
||||||
.ok_or_else(|| ValidationError::new("page_size is out of range"))
|
args.contains(&v)
|
||||||
}
|
.then_some(())
|
||||||
|
.ok_or_else(|| ValidationError::new("page_size is out of range"))
|
||||||
fn validate_page_no(v: usize, args: &RangeFrom<usize>) -> Result<(), ValidationError> {
|
}
|
||||||
args.contains(&v)
|
|
||||||
.then_some(())
|
fn validate_page_no(v: usize, args: &RangeFrom<usize>) -> Result<(), ValidationError> {
|
||||||
.ok_or_else(|| ValidationError::new("page_no is out of range"))
|
args.contains(&v)
|
||||||
}
|
.then_some(())
|
||||||
|
.ok_or_else(|| ValidationError::new("page_no is out of range"))
|
||||||
// NOTE: Clone is required
|
}
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PagerValidArgs {
|
// NOTE: Clone is required
|
||||||
page_size_range: RangeInclusive<usize>,
|
#[derive(Debug, Clone)]
|
||||||
page_no_range: RangeFrom<usize>,
|
pub struct PagerValidArgs {
|
||||||
}
|
page_size_range: RangeInclusive<usize>,
|
||||||
|
page_no_range: RangeFrom<usize>,
|
||||||
// NOTE: This implementation allows PagerValidArgs to be the second member of ValidEx, and provides arguments for actual validation.
|
}
|
||||||
// The type mapping <Pager as ValidateArgs<'a>>::Args represents the combination of validators applied on each field of Pager.
|
|
||||||
// get() method returns the validating arguments to be used during validation.
|
// NOTE: This implementation allows PagerValidArgs to be the second member of ValidEx, and provides arguments for actual validation.
|
||||||
impl<'a> Arguments<'a> for PagerValidArgs {
|
// The type mapping <Pager as ValidateArgs<'a>>::Args represents the combination of validators applied on each field of Pager.
|
||||||
type T = Pager;
|
// get() method returns the validating arguments to be used during validation.
|
||||||
|
impl<'a> Arguments<'a> for PagerValidArgs {
|
||||||
// NOTE: <Pager as ValidateArgs<'a>>::Args == (&RangeInclusive<usize>, &RangeFrom<usize>)
|
type T = Pager;
|
||||||
fn get(&'a self) -> <Pager as ValidateArgs<'a>>::Args {
|
|
||||||
(&self.page_size_range, &self.page_no_range)
|
// NOTE: <Pager as ValidateArgs<'a>>::Args == (&RangeInclusive<usize>, &RangeFrom<usize>)
|
||||||
|
fn get(&'a self) -> <Pager as ValidateArgs<'a>>::Args {
|
||||||
|
(&self.page_size_range, &self.page_no_range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pager_from_form_ex(ValidEx(Form(pager), _): ValidEx<Form<Pager>, PagerValidArgs>) {
|
||||||
|
assert!((1..=50).contains(&pager.page_size));
|
||||||
|
assert!((1..).contains(&pager.page_no));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/form", post(pager_from_form_ex))
|
||||||
|
.with_state(PagerValidArgs {
|
||||||
|
page_size_range: 1..=50,
|
||||||
|
page_no_range: 1..,
|
||||||
|
});
|
||||||
|
// NOTE: The PagerValidArgs can also be stored in a XxxState,
|
||||||
|
// make sure it implements FromRef<XxxState>.
|
||||||
|
|
||||||
|
axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
.serve(router.into_make_service())
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pager_from_form_ex(ValidEx(Form(pager), _): ValidEx<Form<Pager>, PagerValidArgs>) {
|
#[cfg(feature = "garde")]
|
||||||
assert!((1..=50).contains(&pager.page_size));
|
mod garde_example {
|
||||||
assert!((1..).contains(&pager.page_no));
|
#[tokio::main]
|
||||||
|
pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> anyhow::Result<()> {
|
||||||
async fn main() -> anyhow::Result<()> {
|
#[cfg(feature = "validator")]
|
||||||
let router = Router::new()
|
validator_example::launch()?;
|
||||||
.route("/form", post(pager_from_form_ex))
|
#[cfg(feature = "garde")]
|
||||||
.with_state(PagerValidArgs {
|
garde_example::launch()?;
|
||||||
page_size_range: 1..=50,
|
|
||||||
page_no_range: 1..,
|
|
||||||
});
|
|
||||||
// NOTE: The PagerValidArgs can also be stored in a XxxState,
|
|
||||||
// make sure it implements FromRef<XxxState>.
|
|
||||||
|
|
||||||
axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
|
||||||
.serve(router.into_make_service())
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
27
src/extra.rs
27
src/extra.rs
@@ -283,6 +283,8 @@ impl<'v, T: ValidateArgs<'v>, R> HasValidateArgs<'v> for WithRejection<T, R> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{Rejection, ValidTest};
|
use crate::tests::{Rejection, ValidTest};
|
||||||
|
#[cfg(feature = "garde")]
|
||||||
|
use crate::Garde;
|
||||||
#[cfg(feature = "validator")]
|
#[cfg(feature = "validator")]
|
||||||
use crate::Valid;
|
use crate::Valid;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
@@ -347,4 +349,29 @@ mod tests {
|
|||||||
T::set_invalid_request(builder)
|
T::set_invalid_request(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "garde")]
|
||||||
|
impl<T: ValidTest, R> ValidTest for WithRejection<Garde<T>, R> {
|
||||||
|
// just use `418 I'm a teapot` to test
|
||||||
|
const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
|
||||||
|
// If `WithRejection` is the outermost extractor,
|
||||||
|
// the error code returned will always be the one provided by WithRejection.
|
||||||
|
const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT;
|
||||||
|
// If `WithRejection` is the outermost extractor,
|
||||||
|
// the returned body may not be in JSON format.
|
||||||
|
const JSON_SERIALIZABLE: bool = false;
|
||||||
|
|
||||||
|
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
T::set_valid_request(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
// invalid requests will cause the Valid extractor to fail.
|
||||||
|
T::set_invalid_request(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
T::set_invalid_request(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
89
src/form.rs
89
src/form.rs
@@ -12,32 +12,73 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! #![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
|
//! mod validator_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::Form;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_valid::Valid;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use validator::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Valid(Form(parameter)): Valid<Form<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[validate(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[validate(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! use axum::routing::post;
|
//! #[cfg(feature = "garde")]
|
||||||
//! use axum::Form;
|
//! mod garde_example {
|
||||||
//! use axum::Router;
|
//! use axum::routing::post;
|
||||||
//! use axum_valid::Valid;
|
//! use axum::Form;
|
||||||
//! use serde::Deserialize;
|
//! use axum::Router;
|
||||||
//! use validator::Validate;
|
//! use axum_valid::Garde;
|
||||||
//! #[tokio::main]
|
//! use serde::Deserialize;
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! use garde::Validate;
|
||||||
//! let router = Router::new().route("/form", post(handler));
|
//! #[tokio::main]
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
//! .serve(router.into_make_service())
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
//! .await?;
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
//! Ok(())
|
//! .serve(router.into_make_service())
|
||||||
//! }
|
//! .await?;
|
||||||
//! async fn handler(Valid(Form(parameter)): Valid<Form<Parameter>>) {
|
//! Ok(())
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! }
|
||||||
//! }
|
//! async fn handler(Garde(Form(parameter)): Garde<Form<Parameter>>) {
|
||||||
//! #[derive(Validate, Deserialize)]
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
//! pub struct Parameter {
|
//! // Support automatic dereferencing
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
//! pub v0: i32,
|
//! }
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
//! #[derive(Validate, Deserialize)]
|
||||||
//! pub v1: String,
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
|
//!
|
||||||
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
|
//! # #[cfg(feature = "validator")]
|
||||||
|
//! # validator_example::launch()?;
|
||||||
|
//! # #[cfg(feature = "garde")]
|
||||||
|
//! # garde_example::launch()?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//! # Garde support
|
//! # Garde support
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
use crate::{HasValidate, VALIDATION_ERROR_STATUS};
|
use crate::{HasValidate, VALIDATION_ERROR_STATUS};
|
||||||
use axum::async_trait;
|
use axum::async_trait;
|
||||||
use axum::extract::{FromRef, FromRequest, FromRequestParts};
|
use axum::extract::{FromRef, FromRequest, FromRequestParts};
|
||||||
|
|||||||
890
src/garde/test.rs
Normal file
890
src/garde/test.rs
Normal file
@@ -0,0 +1,890 @@
|
|||||||
|
#![cfg(feature = "garde")]
|
||||||
|
|
||||||
|
use crate::tests::{ValidTest, ValidTestParameter};
|
||||||
|
use crate::{Garde, HasValidate, VALIDATION_ERROR_STATUS};
|
||||||
|
use axum::extract::{FromRef, Path, Query};
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use axum::{Form, Json, Router};
|
||||||
|
use garde::Validate;
|
||||||
|
use hyper::Method;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use reqwest::{StatusCode, Url};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::any::type_name;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Validate, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "extra_protobuf", derive(prost::Message))]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "typed_multipart",
|
||||||
|
derive(axum_typed_multipart::TryFromMultipart)
|
||||||
|
)]
|
||||||
|
pub struct ParametersGarde {
|
||||||
|
#[garde(range(min = 5, max = 10))]
|
||||||
|
#[cfg_attr(feature = "extra_protobuf", prost(int32, tag = "1"))]
|
||||||
|
v0: i32,
|
||||||
|
#[garde(length(min = 1, max = 10))]
|
||||||
|
#[cfg_attr(feature = "extra_protobuf", prost(string, tag = "2"))]
|
||||||
|
v1: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALID_PARAMETERS: Lazy<ParametersGarde> = Lazy::new(|| ParametersGarde {
|
||||||
|
v0: 5,
|
||||||
|
v1: String::from("0123456789"),
|
||||||
|
});
|
||||||
|
|
||||||
|
static INVALID_PARAMETERS: Lazy<ParametersGarde> = Lazy::new(|| ParametersGarde {
|
||||||
|
v0: 6,
|
||||||
|
v1: String::from("01234567890"),
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, FromRef, Default)]
|
||||||
|
struct MyState {
|
||||||
|
no_argument_context: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidTestParameter for ParametersGarde {
|
||||||
|
fn valid() -> &'static Self {
|
||||||
|
VALID_PARAMETERS.deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error() -> &'static [(&'static str, &'static str)] {
|
||||||
|
&[("not_v0_or_v1", "value")]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid() -> &'static Self {
|
||||||
|
INVALID_PARAMETERS.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasValidate for ParametersGarde {
|
||||||
|
type Validate = ParametersGarde;
|
||||||
|
|
||||||
|
fn get_validate(&self) -> &Self::Validate {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_main() -> anyhow::Result<()> {
|
||||||
|
let router = Router::new()
|
||||||
|
.route(route::PATH, get(extract_path))
|
||||||
|
.route(route::QUERY, get(extract_query))
|
||||||
|
.route(route::FORM, post(extract_form))
|
||||||
|
.route(route::JSON, post(extract_json));
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_header")]
|
||||||
|
let router = router.route(
|
||||||
|
typed_header::route::TYPED_HEADER,
|
||||||
|
post(typed_header::extract_typed_header),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_multipart")]
|
||||||
|
let router = router
|
||||||
|
.route(
|
||||||
|
typed_multipart::route::TYPED_MULTIPART,
|
||||||
|
post(typed_multipart::extract_typed_multipart),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
typed_multipart::route::BASE_MULTIPART,
|
||||||
|
post(typed_multipart::extract_base_multipart),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "extra")]
|
||||||
|
let router = router
|
||||||
|
.route(extra::route::CACHED, post(extra::extract_cached))
|
||||||
|
.route(
|
||||||
|
extra::route::WITH_REJECTION,
|
||||||
|
post(extra::extract_with_rejection),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
extra::route::WITH_REJECTION_GARDE,
|
||||||
|
post(extra::extract_with_rejection_valid),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_typed_path")]
|
||||||
|
let router = router.route(
|
||||||
|
extra_typed_path::route::EXTRA_TYPED_PATH,
|
||||||
|
get(extra_typed_path::extract_extra_typed_path),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_query")]
|
||||||
|
let router = router.route(
|
||||||
|
extra_query::route::EXTRA_QUERY,
|
||||||
|
post(extra_query::extract_extra_query),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_form")]
|
||||||
|
let router = router.route(
|
||||||
|
extra_form::route::EXTRA_FORM,
|
||||||
|
post(extra_form::extract_extra_form),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_protobuf")]
|
||||||
|
let router = router.route(
|
||||||
|
extra_protobuf::route::EXTRA_PROTOBUF,
|
||||||
|
post(extra_protobuf::extract_extra_protobuf),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
let router = router.route(yaml::route::YAML, post(yaml::extract_yaml));
|
||||||
|
|
||||||
|
#[cfg(feature = "msgpack")]
|
||||||
|
let router = router
|
||||||
|
.route(msgpack::route::MSGPACK, post(msgpack::extract_msgpack))
|
||||||
|
.route(
|
||||||
|
msgpack::route::MSGPACK_RAW,
|
||||||
|
post(msgpack::extract_msgpack_raw),
|
||||||
|
);
|
||||||
|
|
||||||
|
let router = router.with_state(MyState::default());
|
||||||
|
|
||||||
|
let server = axum::Server::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16)))
|
||||||
|
.serve(router.into_make_service());
|
||||||
|
let server_addr = server.local_addr();
|
||||||
|
println!("Axum server address: {}.", server_addr);
|
||||||
|
|
||||||
|
let (server_guard, close) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let server_handle = tokio::spawn(server.with_graceful_shutdown(async move {
|
||||||
|
let _ = close.await;
|
||||||
|
}));
|
||||||
|
|
||||||
|
let server_url = format!("http://{}", server_addr);
|
||||||
|
let test_executor = TestExecutor::from(Url::parse(&format!("http://{}", server_addr))?);
|
||||||
|
|
||||||
|
async fn test_extra_path(
|
||||||
|
test_executor: &TestExecutor,
|
||||||
|
route: &str,
|
||||||
|
server_url: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let path_type_name = type_name::<Path<ParametersGarde>>();
|
||||||
|
let valid_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!(
|
||||||
|
"{}/{route}/{}/{}",
|
||||||
|
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
valid_path_response.status(),
|
||||||
|
StatusCode::OK,
|
||||||
|
"Valid '{}' test failed.",
|
||||||
|
path_type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!("{}/{route}/not_i32/path", server_url))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
error_path_response.status(),
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"Error '{}' test failed.",
|
||||||
|
path_type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let invalid_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!(
|
||||||
|
"{}/{route}/{}/{}",
|
||||||
|
server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
invalid_path_response.status(),
|
||||||
|
VALIDATION_ERROR_STATUS,
|
||||||
|
"Invalid '{}' test failed.",
|
||||||
|
path_type_name
|
||||||
|
);
|
||||||
|
#[cfg(feature = "into_json")]
|
||||||
|
check_json(path_type_name, invalid_path_response).await;
|
||||||
|
println!("All {} tests passed.", path_type_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
test_extra_path(&test_executor, "path", &server_url).await?;
|
||||||
|
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<Query<ParametersGarde>>(Method::GET, route::QUERY)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<Form<ParametersGarde>>(Method::POST, route::FORM)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<Json<ParametersGarde>>(Method::POST, route::JSON)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_header")]
|
||||||
|
{
|
||||||
|
use axum::TypedHeader;
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<TypedHeader<ParametersGarde>>(
|
||||||
|
Method::POST,
|
||||||
|
typed_header::route::TYPED_HEADER,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_multipart")]
|
||||||
|
{
|
||||||
|
use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError};
|
||||||
|
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<BaseMultipart<ParametersGarde, TypedMultipartError>>(
|
||||||
|
Method::POST,
|
||||||
|
typed_multipart::route::BASE_MULTIPART,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Garde
|
||||||
|
test_executor
|
||||||
|
.execute::<TypedMultipart<ParametersGarde>>(
|
||||||
|
Method::POST,
|
||||||
|
typed_multipart::route::TYPED_MULTIPART,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra")]
|
||||||
|
{
|
||||||
|
use axum_extra::extract::{Cached, WithRejection};
|
||||||
|
use extra::{
|
||||||
|
GardeWithRejectionRejection, ParametersRejection, WithRejectionGardeRejection,
|
||||||
|
};
|
||||||
|
test_executor
|
||||||
|
.execute::<Cached<ParametersGarde>>(Method::POST, extra::route::CACHED)
|
||||||
|
.await?;
|
||||||
|
test_executor
|
||||||
|
.execute::<WithRejection<ParametersGarde, GardeWithRejectionRejection>>(
|
||||||
|
Method::POST,
|
||||||
|
extra::route::WITH_REJECTION,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
test_executor
|
||||||
|
.execute::<WithRejection<Garde<ParametersGarde>, WithRejectionGardeRejection<ParametersRejection>>>(
|
||||||
|
Method::POST,
|
||||||
|
extra::route::WITH_REJECTION_GARDE,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_typed_path")]
|
||||||
|
{
|
||||||
|
async fn test_extra_typed_path(
|
||||||
|
test_executor: &TestExecutor,
|
||||||
|
route: &str,
|
||||||
|
server_url: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let extra_typed_path_type_name = "T: TypedPath";
|
||||||
|
let valid_extra_typed_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!(
|
||||||
|
"{}/{route}/{}/{}",
|
||||||
|
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
valid_extra_typed_path_response.status(),
|
||||||
|
StatusCode::OK,
|
||||||
|
"Garde '{}' test failed.",
|
||||||
|
extra_typed_path_type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_extra_typed_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!("{}/{route}/not_i32/path", server_url))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
error_extra_typed_path_response.status(),
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"Error '{}' test failed.",
|
||||||
|
extra_typed_path_type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let invalid_extra_typed_path_response = test_executor
|
||||||
|
.client()
|
||||||
|
.get(format!(
|
||||||
|
"{}/{route}/{}/{}",
|
||||||
|
server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
invalid_extra_typed_path_response.status(),
|
||||||
|
VALIDATION_ERROR_STATUS,
|
||||||
|
"Invalid '{}' test failed.",
|
||||||
|
extra_typed_path_type_name
|
||||||
|
);
|
||||||
|
#[cfg(feature = "into_json")]
|
||||||
|
check_json(
|
||||||
|
extra_typed_path_type_name,
|
||||||
|
invalid_extra_typed_path_response,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("All {} tests passed.", extra_typed_path_type_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
test_extra_typed_path(&test_executor, "extra_typed_path", &server_url).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_query")]
|
||||||
|
{
|
||||||
|
use axum_extra::extract::Query;
|
||||||
|
test_executor
|
||||||
|
.execute::<Query<ParametersGarde>>(Method::POST, extra_query::route::EXTRA_QUERY)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_form")]
|
||||||
|
{
|
||||||
|
use axum_extra::extract::Form;
|
||||||
|
test_executor
|
||||||
|
.execute::<Form<ParametersGarde>>(Method::POST, extra_form::route::EXTRA_FORM)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_protobuf")]
|
||||||
|
{
|
||||||
|
use axum_extra::protobuf::Protobuf;
|
||||||
|
test_executor
|
||||||
|
.execute::<Protobuf<ParametersGarde>>(
|
||||||
|
Method::POST,
|
||||||
|
extra_protobuf::route::EXTRA_PROTOBUF,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
{
|
||||||
|
use axum_yaml::Yaml;
|
||||||
|
test_executor
|
||||||
|
.execute::<Yaml<ParametersGarde>>(Method::POST, yaml::route::YAML)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "msgpack")]
|
||||||
|
{
|
||||||
|
use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||||
|
test_executor
|
||||||
|
.execute::<MsgPack<ParametersGarde>>(Method::POST, msgpack::route::MSGPACK)
|
||||||
|
.await?;
|
||||||
|
test_executor
|
||||||
|
.execute::<MsgPackRaw<ParametersGarde>>(Method::POST, msgpack::route::MSGPACK_RAW)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(server_guard);
|
||||||
|
server_handle.await??;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TestExecutor {
|
||||||
|
client: reqwest::Client,
|
||||||
|
server_url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Url> for TestExecutor {
|
||||||
|
fn from(server_url: Url) -> Self {
|
||||||
|
Self {
|
||||||
|
client: Default::default(),
|
||||||
|
server_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestExecutor {
|
||||||
|
/// Execute all tests
|
||||||
|
pub async fn execute<T: ValidTest>(&self, method: Method, route: &str) -> anyhow::Result<()> {
|
||||||
|
let url = {
|
||||||
|
let mut url_builder = self.server_url.clone();
|
||||||
|
url_builder.set_path(route);
|
||||||
|
url_builder
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_name = type_name::<T>();
|
||||||
|
|
||||||
|
let valid_builder = self.client.request(method.clone(), url.clone());
|
||||||
|
let valid_response = T::set_valid_request(valid_builder).send().await?;
|
||||||
|
assert_eq!(
|
||||||
|
valid_response.status(),
|
||||||
|
StatusCode::OK,
|
||||||
|
"Garde '{}' test failed.",
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_builder = self.client.request(method.clone(), url.clone());
|
||||||
|
let error_response = T::set_error_request(error_builder).send().await?;
|
||||||
|
assert_eq!(
|
||||||
|
error_response.status(),
|
||||||
|
T::ERROR_STATUS_CODE,
|
||||||
|
"Error '{}' test failed.",
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let invalid_builder = self.client.request(method, url);
|
||||||
|
let invalid_response = T::set_invalid_request(invalid_builder).send().await?;
|
||||||
|
assert_eq!(
|
||||||
|
invalid_response.status(),
|
||||||
|
T::INVALID_STATUS_CODE,
|
||||||
|
"Invalid '{}' test failed.",
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
#[cfg(feature = "into_json")]
|
||||||
|
if T::JSON_SERIALIZABLE {
|
||||||
|
check_json(type_name, invalid_response).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All '{}' tests passed.", type_name);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client(&self) -> &reqwest::Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the response is a json response
|
||||||
|
#[cfg(feature = "into_json")]
|
||||||
|
pub async fn check_json(type_name: &'static str, response: reqwest::Response) {
|
||||||
|
assert_eq!(
|
||||||
|
response.headers()[axum::http::header::CONTENT_TYPE],
|
||||||
|
axum::http::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||||
|
"'{}' rejection into json test failed",
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
assert!(response.json::<serde_json::Value>().await.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
mod route {
|
||||||
|
pub const PATH: &str = "/path/:v0/:v1";
|
||||||
|
pub const QUERY: &str = "/query";
|
||||||
|
pub const FORM: &str = "/form";
|
||||||
|
pub const JSON: &str = "/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_path(Garde(Path(parameters)): Garde<Path<ParametersGarde>>) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_query(Garde(Query(parameters)): Garde<Query<ParametersGarde>>) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_form(Garde(Form(parameters)): Garde<Form<ParametersGarde>>) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_json(Garde(Json(parameters)): Garde<Json<ParametersGarde>>) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_again<V: Validate>(validate: V, context: V::Context) -> StatusCode {
|
||||||
|
// The `Garde` extractor has validated the `parameters` once,
|
||||||
|
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
|
||||||
|
// Let's validate them again to check if the `Garde` extractor works well.
|
||||||
|
// If it works properly, this function will never return `500 INTERNAL SERVER ERROR`
|
||||||
|
match validate.validate(&context) {
|
||||||
|
Ok(_) => StatusCode::OK,
|
||||||
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_header")]
|
||||||
|
mod typed_header {
|
||||||
|
|
||||||
|
pub(crate) mod route {
|
||||||
|
pub const TYPED_HEADER: &str = "/typed_header";
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::headers::{Error, Header, HeaderName, HeaderValue};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::TypedHeader;
|
||||||
|
|
||||||
|
pub static AXUM_VALID_PARAMETERS: HeaderName = HeaderName::from_static("axum-valid-parameters");
|
||||||
|
|
||||||
|
pub(super) async fn extract_typed_header(
|
||||||
|
Garde(TypedHeader(parameters)): Garde<TypedHeader<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for ParametersGarde {
|
||||||
|
fn name() -> &'static HeaderName {
|
||||||
|
&AXUM_VALID_PARAMETERS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
I: Iterator<Item = &'i HeaderValue>,
|
||||||
|
{
|
||||||
|
let value = values.next().ok_or_else(Error::invalid)?;
|
||||||
|
let src = std::str::from_utf8(value.as_bytes()).map_err(|_| Error::invalid())?;
|
||||||
|
let split = src.split(',').collect::<Vec<_>>();
|
||||||
|
match split.as_slice() {
|
||||||
|
[v0, v1] => Ok(ParametersGarde {
|
||||||
|
v0: v0.parse().map_err(|_| Error::invalid())?,
|
||||||
|
v1: v1.to_string(),
|
||||||
|
}),
|
||||||
|
_ => Err(Error::invalid()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
|
||||||
|
let v0 = self.v0.to_string();
|
||||||
|
let mut vec = Vec::with_capacity(v0.len() + 1 + self.v1.len());
|
||||||
|
vec.extend_from_slice(v0.as_bytes());
|
||||||
|
vec.push(b',');
|
||||||
|
vec.extend_from_slice(self.v1.as_bytes());
|
||||||
|
let value = HeaderValue::from_bytes(&vec).expect("Failed to build header");
|
||||||
|
values.extend(::std::iter::once(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parameter_is_header() -> anyhow::Result<()> {
|
||||||
|
let parameter = ParametersGarde {
|
||||||
|
v0: 123456,
|
||||||
|
v1: "111111".to_string(),
|
||||||
|
};
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
parameter.encode(&mut vec);
|
||||||
|
let mut iter = vec.iter();
|
||||||
|
assert_eq!(parameter, ParametersGarde::decode(&mut iter)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "typed_multipart")]
|
||||||
|
mod typed_multipart {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError};
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const TYPED_MULTIPART: &str = "/typed_multipart";
|
||||||
|
pub const BASE_MULTIPART: &str = "/base_multipart";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ParametersGarde> for reqwest::multipart::Form {
|
||||||
|
fn from(value: &ParametersGarde) -> Self {
|
||||||
|
reqwest::multipart::Form::new()
|
||||||
|
.text("v0", value.v0.to_string())
|
||||||
|
.text("v1", value.v1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn extract_typed_multipart(
|
||||||
|
Garde(TypedMultipart(parameters)): Garde<TypedMultipart<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn extract_base_multipart(
|
||||||
|
Garde(BaseMultipart { data, .. }): Garde<
|
||||||
|
BaseMultipart<ParametersGarde, TypedMultipartError>,
|
||||||
|
>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(data, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra")]
|
||||||
|
mod extra {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::tests::{Rejection, ValidTest, ValidTestParameter};
|
||||||
|
use crate::{Garde, GardeRejection};
|
||||||
|
use axum::extract::FromRequestParts;
|
||||||
|
use axum::http::request::Parts;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use axum_extra::extract::{Cached, WithRejection};
|
||||||
|
use reqwest::RequestBuilder;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const CACHED: &str = "/cached";
|
||||||
|
pub const WITH_REJECTION: &str = "/with_rejection";
|
||||||
|
pub const WITH_REJECTION_GARDE: &str = "/with_rejection_garde";
|
||||||
|
}
|
||||||
|
pub const PARAMETERS_HEADER: &str = "parameters-header";
|
||||||
|
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
|
||||||
|
|
||||||
|
// 1.2. Define you own `Rejection` type and implement `IntoResponse` for it.
|
||||||
|
pub enum ParametersRejection {
|
||||||
|
Null,
|
||||||
|
InvalidJson(serde_json::error::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ParametersRejection {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self {
|
||||||
|
ParametersRejection::Null => {
|
||||||
|
(CACHED_REJECTION_STATUS, "My-Data header is missing").into_response()
|
||||||
|
}
|
||||||
|
ParametersRejection::InvalidJson(e) => (
|
||||||
|
CACHED_REJECTION_STATUS,
|
||||||
|
format!("My-Data is not valid json string: {e}"),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.3. Implement your extractor (`FromRequestParts` or `FromRequest`)
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for ParametersGarde
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = ParametersRejection;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
let Some(value) = parts.headers.get(PARAMETERS_HEADER) else {
|
||||||
|
return Err(ParametersRejection::Null);
|
||||||
|
};
|
||||||
|
|
||||||
|
serde_json::from_slice(value.as_bytes()).map_err(ParametersRejection::InvalidJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidTest for ParametersGarde {
|
||||||
|
const ERROR_STATUS_CODE: StatusCode = CACHED_REJECTION_STATUS;
|
||||||
|
|
||||||
|
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
builder.header(
|
||||||
|
PARAMETERS_HEADER,
|
||||||
|
serde_json::to_string(ParametersGarde::valid())
|
||||||
|
.expect("Failed to serialize parameters"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
builder.header(
|
||||||
|
PARAMETERS_HEADER,
|
||||||
|
serde_json::to_string(ParametersGarde::error())
|
||||||
|
.expect("Failed to serialize parameters"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||||
|
builder.header(
|
||||||
|
PARAMETERS_HEADER,
|
||||||
|
serde_json::to_string(ParametersGarde::invalid())
|
||||||
|
.expect("Failed to serialize parameters"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GardeWithRejectionRejection {
|
||||||
|
inner: ParametersRejection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rejection for GardeWithRejectionRejection {
|
||||||
|
const STATUS_CODE: StatusCode = StatusCode::CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for GardeWithRejectionRejection {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let mut response = self.inner.into_response();
|
||||||
|
*response.status_mut() = Self::STATUS_CODE;
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// satisfy the `WithRejection`'s extractor trait bound
|
||||||
|
// R: From<E::Rejection> + IntoResponse
|
||||||
|
impl From<ParametersRejection> for GardeWithRejectionRejection {
|
||||||
|
fn from(inner: ParametersRejection) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_cached(
|
||||||
|
Garde(Cached(parameters)): Garde<Cached<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_with_rejection(
|
||||||
|
Garde(WithRejection(parameters, _)): Garde<
|
||||||
|
WithRejection<ParametersGarde, GardeWithRejectionRejection>,
|
||||||
|
>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WithRejectionGardeRejection<E> {
|
||||||
|
inner: GardeRejection<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<GardeRejection<E>> for WithRejectionGardeRejection<E> {
|
||||||
|
fn from(inner: GardeRejection<E>) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: IntoResponse> IntoResponse for WithRejectionGardeRejection<E> {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let mut res = self.inner.into_response();
|
||||||
|
*res.status_mut() = StatusCode::IM_A_TEAPOT;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_with_rejection_valid(
|
||||||
|
WithRejection(Garde(parameters), _): WithRejection<
|
||||||
|
Garde<ParametersGarde>,
|
||||||
|
WithRejectionGardeRejection<ParametersRejection>,
|
||||||
|
>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_typed_path")]
|
||||||
|
mod extra_typed_path {
|
||||||
|
use super::validate_again;
|
||||||
|
use crate::{Garde, HasValidate};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_extra::routing::TypedPath;
|
||||||
|
use garde::Validate;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const EXTRA_TYPED_PATH: &str = "/extra_typed_path/:v0/:v1";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Validate, TypedPath, Deserialize)]
|
||||||
|
#[typed_path("/extra_typed_path/:v0/:v1")]
|
||||||
|
pub struct TypedPathParam {
|
||||||
|
#[garde(range(min = 5, max = 10))]
|
||||||
|
v0: i32,
|
||||||
|
#[garde(length(min = 1, max = 10))]
|
||||||
|
v1: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasValidate for TypedPathParam {
|
||||||
|
type Validate = Self;
|
||||||
|
|
||||||
|
fn get_validate(&self) -> &Self::Validate {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_extra_typed_path(Garde(param): Garde<TypedPathParam>) -> StatusCode {
|
||||||
|
validate_again(param, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_query")]
|
||||||
|
mod extra_query {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_extra::extract::Query;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const EXTRA_QUERY: &str = "/extra_query";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_extra_query(
|
||||||
|
Garde(Query(parameters)): Garde<Query<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_form")]
|
||||||
|
mod extra_form {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_extra::extract::Form;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const EXTRA_FORM: &str = "/extra_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_extra_form(
|
||||||
|
Garde(Form(parameters)): Garde<Form<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extra_protobuf")]
|
||||||
|
mod extra_protobuf {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_extra::protobuf::Protobuf;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const EXTRA_PROTOBUF: &str = "/extra_protobuf";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_extra_protobuf(
|
||||||
|
Garde(Protobuf(parameters)): Garde<Protobuf<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
mod yaml {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_yaml::Yaml;
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const YAML: &str = "/yaml";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_yaml(Garde(Yaml(parameters)): Garde<Yaml<ParametersGarde>>) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "msgpack")]
|
||||||
|
mod msgpack {
|
||||||
|
use super::{validate_again, ParametersGarde};
|
||||||
|
use crate::Garde;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||||
|
|
||||||
|
pub mod route {
|
||||||
|
pub const MSGPACK: &str = "/msgpack";
|
||||||
|
pub const MSGPACK_RAW: &str = "/msgpack_raw";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn extract_msgpack(
|
||||||
|
Garde(MsgPack(parameters)): Garde<MsgPack<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
pub async fn extract_msgpack_raw(
|
||||||
|
Garde(MsgPackRaw(parameters)): Garde<MsgPackRaw<ParametersGarde>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
validate_again(parameters, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/json.rs
89
src/json.rs
@@ -12,32 +12,73 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! #![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
|
//! mod validator_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::Json;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_valid::Valid;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use validator::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Valid(Json(parameter)): Valid<Json<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[validate(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[validate(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! use axum::routing::post;
|
//! #[cfg(feature = "garde")]
|
||||||
//! use axum::Json;
|
//! mod garde_example {
|
||||||
//! use axum::Router;
|
//! use axum::routing::post;
|
||||||
//! use axum_valid::Valid;
|
//! use axum::Json;
|
||||||
//! use serde::Deserialize;
|
//! use axum::Router;
|
||||||
//! use validator::Validate;
|
//! use axum_valid::Garde;
|
||||||
//! #[tokio::main]
|
//! use serde::Deserialize;
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! use garde::Validate;
|
||||||
//! let router = Router::new().route("/json", post(handler));
|
//! #[tokio::main]
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
//! .serve(router.into_make_service())
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
//! .await?;
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
//! Ok(())
|
//! .serve(router.into_make_service())
|
||||||
//! }
|
//! .await?;
|
||||||
//! async fn handler(Valid(Json(parameter)): Valid<Json<Parameter>>) {
|
//! Ok(())
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! }
|
||||||
//! }
|
//! async fn handler(Garde(Json(parameter)): Garde<Json<Parameter>>) {
|
||||||
//! #[derive(Validate, Deserialize)]
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
//! pub struct Parameter {
|
//! // Support automatic dereferencing
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
//! pub v0: i32,
|
//! }
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
//! #[derive(Validate, Deserialize)]
|
||||||
//! pub v1: String,
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
|
//!
|
||||||
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
|
//! # #[cfg(feature = "validator")]
|
||||||
|
//! # validator_example::launch()?;
|
||||||
|
//! # #[cfg(feature = "garde")]
|
||||||
|
//! # garde_example::launch()?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ pub mod msgpack;
|
|||||||
pub mod path;
|
pub mod path;
|
||||||
#[cfg(feature = "query")]
|
#[cfg(feature = "query")]
|
||||||
pub mod query;
|
pub mod query;
|
||||||
#[cfg(test)]
|
|
||||||
#[cfg(all(feature = "garde", feature = "validator"))]
|
|
||||||
pub mod test;
|
|
||||||
#[cfg(feature = "typed_header")]
|
#[cfg(feature = "typed_header")]
|
||||||
pub mod typed_header;
|
pub mod typed_header;
|
||||||
#[cfg(feature = "typed_multipart")]
|
#[cfg(feature = "typed_multipart")]
|
||||||
|
|||||||
103
src/msgpack.rs
103
src/msgpack.rs
@@ -12,42 +12,85 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//!#![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
|
//! mod validator_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::Json;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||||
|
//! use axum_valid::Valid;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use validator::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new()
|
||||||
|
//! .route("/msgpack", post(handler))
|
||||||
|
//! .route("/msgpackraw", post(raw_handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Valid(MsgPack(parameter)): Valid<MsgPack<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! use axum::routing::post;
|
//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid<MsgPackRaw<Parameter>>) {
|
||||||
//! use axum::Router;
|
//! assert!(parameter.validate().is_ok());
|
||||||
//! use axum_msgpack::{MsgPack, MsgPackRaw};
|
//! }
|
||||||
//! use axum_valid::Valid;
|
//! #[derive(Validate, Deserialize)]
|
||||||
//! use serde::Deserialize;
|
//! pub struct Parameter {
|
||||||
//! use validator::Validate;
|
//! #[validate(range(min = 5, max = 10))]
|
||||||
//!
|
//! pub v0: i32,
|
||||||
//! #[tokio::main]
|
//! #[validate(length(min = 1, max = 10))]
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! pub v1: String,
|
||||||
//! let router = Router::new()
|
//! }
|
||||||
//! .route("/msgpack", post(handler))
|
|
||||||
//! .route("/msgpackraw", post(raw_handler));
|
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
|
||||||
//! .serve(router.into_make_service())
|
|
||||||
//! .await?;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn handler(Valid(MsgPack(parameter)): Valid<MsgPack<Parameter>>) {
|
//! #[cfg(feature = "garde")]
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! mod garde_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||||
|
//! use axum_valid::Garde;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use garde::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new()
|
||||||
|
//! .route("/msgpack", post(handler))
|
||||||
|
//! .route("/msgpackraw", post(raw_handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(Garde(MsgPack(parameter)): Garde<MsgPack<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn raw_handler(Garde(MsgPackRaw(parameter)): Garde<MsgPackRaw<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid<MsgPackRaw<Parameter>>) {
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! # #[cfg(feature = "validator")]
|
||||||
//! }
|
//! # validator_example::launch()?;
|
||||||
//!
|
//! # #[cfg(feature = "garde")]
|
||||||
//! #[derive(Validate, Deserialize)]
|
//! # garde_example::launch()?;
|
||||||
//! pub struct Parameter {
|
//! # Ok(())
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! # }
|
||||||
//! pub v0: i32,
|
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
|
||||||
//! pub v1: String,
|
|
||||||
//! }
|
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
#[cfg(feature = "validator")]
|
#[cfg(feature = "validator")]
|
||||||
|
|||||||
88
src/path.rs
88
src/path.rs
@@ -8,35 +8,73 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! #![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
//!
|
//! mod validator_example {
|
||||||
//! use axum::extract::Path;
|
//! use axum::extract::Path;
|
||||||
//! use axum::routing::post;
|
//! use axum::routing::post;
|
||||||
//! use axum::Router;
|
//! use axum::Router;
|
||||||
//! use axum_valid::Valid;
|
//! use axum_valid::Valid;
|
||||||
//! use serde::Deserialize;
|
//! use serde::Deserialize;
|
||||||
//! use validator::Validate;
|
//! use validator::Validate;
|
||||||
//!
|
//! #[tokio::main]
|
||||||
//! #[tokio::main]
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
//! let router = Router::new().route("/path/:v0/:v1", post(handler));
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
//! .serve(router.into_make_service())
|
||||||
//! .serve(router.into_make_service())
|
//! .await?;
|
||||||
//! .await?;
|
//! Ok(())
|
||||||
//! Ok(())
|
//! }
|
||||||
|
//! async fn handler(Valid(Path(parameter)): Valid<Path<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! 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(Path(parameter)): Valid<Path<Parameter>>) {
|
//! #[cfg(feature = "garde")]
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! mod garde_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::extract::Path;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_valid::Garde;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use garde::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Garde(Path(parameter)): Garde<Path<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Validate, Deserialize)]
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
//! pub struct Parameter {
|
//! # #[cfg(feature = "validator")]
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! # validator_example::launch()?;
|
||||||
//! pub v0: i32,
|
//! # #[cfg(feature = "garde")]
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
//! # garde_example::launch()?;
|
||||||
//! pub v1: String,
|
//! # Ok(())
|
||||||
//! }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
|
|||||||
88
src/query.rs
88
src/query.rs
@@ -12,35 +12,73 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! #![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
//!
|
//! mod validator_example {
|
||||||
//! use axum::extract::Query;
|
//! use axum::extract::Query;
|
||||||
//! use axum::routing::post;
|
//! use axum::routing::post;
|
||||||
//! use axum::Router;
|
//! use axum::Router;
|
||||||
//! use axum_valid::Valid;
|
//! use axum_valid::Valid;
|
||||||
//! use serde::Deserialize;
|
//! use serde::Deserialize;
|
||||||
//! use validator::Validate;
|
//! use validator::Validate;
|
||||||
//!
|
//! #[tokio::main]
|
||||||
//! #[tokio::main]
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
//! let router = Router::new().route("/query", post(handler));
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
//! .serve(router.into_make_service())
|
||||||
//! .serve(router.into_make_service())
|
//! .await?;
|
||||||
//! .await?;
|
//! Ok(())
|
||||||
//! Ok(())
|
//! }
|
||||||
|
//! async fn handler(Valid(Query(parameter)): Valid<Query<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! 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(Query(parameter)): Valid<Query<Parameter>>) {
|
//! #[cfg(feature = "garde")]
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! mod garde_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum::extract::Query;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_valid::Garde;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use garde::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Garde(Query(parameter)): Garde<Query<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Validate, Deserialize)]
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
//! pub struct Parameter {
|
//! # #[cfg(feature = "validator")]
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! # validator_example::launch()?;
|
||||||
//! pub v0: i32,
|
//! # #[cfg(feature = "garde")]
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
//! # garde_example::launch()?;
|
||||||
//! pub v1: String,
|
//! # Ok(())
|
||||||
//! }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//! # Validator support
|
//! # Validator support
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
use crate::{HasValidate, VALIDATION_ERROR_STATUS};
|
use crate::{HasValidate, VALIDATION_ERROR_STATUS};
|
||||||
use axum::async_trait;
|
use axum::async_trait;
|
||||||
use axum::extract::{FromRef, FromRequest, FromRequestParts};
|
use axum::extract::{FromRef, FromRequest, FromRequestParts};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
#![cfg(feature = "validator")]
|
||||||
|
|
||||||
use crate::tests::{ValidTest, ValidTestParameter};
|
use crate::tests::{ValidTest, ValidTestParameter};
|
||||||
use crate::Garde;
|
|
||||||
use crate::{Arguments, HasValidate, HasValidateArgs, Valid, ValidEx, VALIDATION_ERROR_STATUS};
|
use crate::{Arguments, HasValidate, HasValidateArgs, Valid, ValidEx, VALIDATION_ERROR_STATUS};
|
||||||
use axum::extract::{FromRef, Path, Query};
|
use axum::extract::{FromRef, Path, Query};
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
@@ -83,21 +84,6 @@ impl Default for ParametersExValidationArgumentsInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, garde::Validate, Eq, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "extra_protobuf", derive(prost::Message))]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "typed_multipart",
|
|
||||||
derive(axum_typed_multipart::TryFromMultipart)
|
|
||||||
)]
|
|
||||||
pub struct ParametersGarde {
|
|
||||||
#[garde(range(min = 5, max = 10))]
|
|
||||||
#[cfg_attr(feature = "extra_protobuf", prost(int32, tag = "1"))]
|
|
||||||
v0: i32,
|
|
||||||
#[garde(length(min = 1, max = 10))]
|
|
||||||
#[cfg_attr(feature = "extra_protobuf", prost(string, tag = "2"))]
|
|
||||||
v1: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALID_PARAMETERS: Lazy<Parameters> = Lazy::new(|| Parameters {
|
static VALID_PARAMETERS: Lazy<Parameters> = Lazy::new(|| Parameters {
|
||||||
v0: 5,
|
v0: 5,
|
||||||
v1: String::from("0123456789"),
|
v1: String::from("0123456789"),
|
||||||
@@ -167,11 +153,6 @@ async fn test_main() -> anyhow::Result<()> {
|
|||||||
.route(route::FORM_EX, post(extract_form_ex))
|
.route(route::FORM_EX, post(extract_form_ex))
|
||||||
.route(route::JSON_EX, post(extract_json_ex));
|
.route(route::JSON_EX, post(extract_json_ex));
|
||||||
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
let router = router
|
|
||||||
.route(route::QUERY_GARDE, get(extract_query_garde))
|
|
||||||
.route(route::JSON_GARDE, post(extract_json_garde));
|
|
||||||
|
|
||||||
#[cfg(feature = "typed_header")]
|
#[cfg(feature = "typed_header")]
|
||||||
let router = router
|
let router = router
|
||||||
.route(
|
.route(
|
||||||
@@ -369,12 +350,6 @@ async fn test_main() -> anyhow::Result<()> {
|
|||||||
.execute::<Query<Parameters>>(Method::GET, route::QUERY_EX)
|
.execute::<Query<Parameters>>(Method::GET, route::QUERY_EX)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Garde
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
test_executor
|
|
||||||
.execute::<Query<Parameters>>(Method::GET, route::QUERY_GARDE)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Valid
|
// Valid
|
||||||
test_executor
|
test_executor
|
||||||
.execute::<Form<Parameters>>(Method::POST, route::FORM)
|
.execute::<Form<Parameters>>(Method::POST, route::FORM)
|
||||||
@@ -395,12 +370,6 @@ async fn test_main() -> anyhow::Result<()> {
|
|||||||
.execute::<Json<Parameters>>(Method::POST, route::JSON_EX)
|
.execute::<Json<Parameters>>(Method::POST, route::JSON_EX)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Garde
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
test_executor
|
|
||||||
.execute::<Json<Parameters>>(Method::POST, route::JSON_GARDE)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
#[cfg(feature = "typed_header")]
|
#[cfg(feature = "typed_header")]
|
||||||
{
|
{
|
||||||
use axum::TypedHeader;
|
use axum::TypedHeader;
|
||||||
@@ -703,14 +672,10 @@ mod route {
|
|||||||
pub const PATH_EX: &str = "/path_ex/:v0/:v1";
|
pub const PATH_EX: &str = "/path_ex/:v0/:v1";
|
||||||
pub const QUERY: &str = "/query";
|
pub const QUERY: &str = "/query";
|
||||||
pub const QUERY_EX: &str = "/query_ex";
|
pub const QUERY_EX: &str = "/query_ex";
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
pub const QUERY_GARDE: &str = "/query_garde";
|
|
||||||
pub const FORM: &str = "/form";
|
pub const FORM: &str = "/form";
|
||||||
pub const FORM_EX: &str = "/form_ex";
|
pub const FORM_EX: &str = "/form_ex";
|
||||||
pub const JSON: &str = "/json";
|
pub const JSON: &str = "/json";
|
||||||
pub const JSON_EX: &str = "/json_ex";
|
pub const JSON_EX: &str = "/json_ex";
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
pub const JSON_GARDE: &str = "/json_garde";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extract_path(Valid(Path(parameters)): Valid<Path<Parameters>>) -> StatusCode {
|
async fn extract_path(Valid(Path(parameters)): Valid<Path<Parameters>>) -> StatusCode {
|
||||||
@@ -733,13 +698,6 @@ async fn extract_query_ex(
|
|||||||
validate_again_ex(parameters, args.get())
|
validate_again_ex(parameters, args.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
async fn extract_query_garde(
|
|
||||||
Garde(Query(parameters)): Garde<Query<ParametersGarde>>,
|
|
||||||
) -> StatusCode {
|
|
||||||
validate_again_garde(parameters, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn extract_form(Valid(Form(parameters)): Valid<Form<Parameters>>) -> StatusCode {
|
async fn extract_form(Valid(Form(parameters)): Valid<Form<Parameters>>) -> StatusCode {
|
||||||
validate_again(parameters)
|
validate_again(parameters)
|
||||||
}
|
}
|
||||||
@@ -760,11 +718,6 @@ async fn extract_json_ex(
|
|||||||
validate_again_ex(parameters, args.get())
|
validate_again_ex(parameters, args.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
async fn extract_json_garde(Garde(Json(parameters)): Garde<Json<ParametersGarde>>) -> StatusCode {
|
|
||||||
validate_again_garde(parameters, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_again<V: Validate>(validate: V) -> StatusCode {
|
fn validate_again<V: Validate>(validate: V) -> StatusCode {
|
||||||
// The `Valid` extractor has validated the `parameters` once,
|
// The `Valid` extractor has validated the `parameters` once,
|
||||||
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
|
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
|
||||||
@@ -790,21 +743,6 @@ fn validate_again_ex<'v, V: ValidateArgs<'v>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "garde")]
|
|
||||||
fn validate_again_garde<V>(validate: V, context: V::Context) -> StatusCode
|
|
||||||
where
|
|
||||||
V: garde::Validate,
|
|
||||||
{
|
|
||||||
// The `Garde` extractor has validated the `parameters` once,
|
|
||||||
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
|
|
||||||
// Let's validate them again to check if the `Garde` extractor works well.
|
|
||||||
// If it works properly, this function will never return `500 INTERNAL SERVER ERROR`
|
|
||||||
match validate.validate(&context) {
|
|
||||||
Ok(_) => StatusCode::OK,
|
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "typed_header")]
|
#[cfg(feature = "typed_header")]
|
||||||
mod typed_header {
|
mod typed_header {
|
||||||
|
|
||||||
@@ -814,7 +752,7 @@ mod typed_header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use super::{validate_again, Parameters};
|
use super::{validate_again, Parameters};
|
||||||
use crate::test::{validate_again_ex, ParametersEx, ParametersExValidationArguments};
|
use super::{validate_again_ex, ParametersEx, ParametersExValidationArguments};
|
||||||
use crate::{Arguments, Valid, ValidEx};
|
use crate::{Arguments, Valid, ValidEx};
|
||||||
use axum::headers::{Error, Header, HeaderName, HeaderValue};
|
use axum::headers::{Error, Header, HeaderName, HeaderValue};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
@@ -919,7 +857,7 @@ mod typed_header {
|
|||||||
|
|
||||||
#[cfg(feature = "typed_multipart")]
|
#[cfg(feature = "typed_multipart")]
|
||||||
mod typed_multipart {
|
mod typed_multipart {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -975,7 +913,7 @@ mod typed_multipart {
|
|||||||
|
|
||||||
#[cfg(feature = "extra")]
|
#[cfg(feature = "extra")]
|
||||||
mod extra {
|
mod extra {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -1174,7 +1112,7 @@ mod extra {
|
|||||||
|
|
||||||
#[cfg(feature = "extra_typed_path")]
|
#[cfg(feature = "extra_typed_path")]
|
||||||
mod extra_typed_path {
|
mod extra_typed_path {
|
||||||
use crate::test::{validate_again, validate_again_ex};
|
use super::{validate_again, validate_again_ex};
|
||||||
use crate::{Arguments, HasValidate, HasValidateArgs, Valid, ValidEx};
|
use crate::{Arguments, HasValidate, HasValidateArgs, Valid, ValidEx};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum_extra::routing::TypedPath;
|
use axum_extra::routing::TypedPath;
|
||||||
@@ -1256,7 +1194,7 @@ mod extra_typed_path {
|
|||||||
|
|
||||||
#[cfg(feature = "extra_query")]
|
#[cfg(feature = "extra_query")]
|
||||||
mod extra_query {
|
mod extra_query {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -1287,7 +1225,7 @@ mod extra_query {
|
|||||||
|
|
||||||
#[cfg(feature = "extra_form")]
|
#[cfg(feature = "extra_form")]
|
||||||
mod extra_form {
|
mod extra_form {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -1318,7 +1256,7 @@ mod extra_form {
|
|||||||
|
|
||||||
#[cfg(feature = "extra_protobuf")]
|
#[cfg(feature = "extra_protobuf")]
|
||||||
mod extra_protobuf {
|
mod extra_protobuf {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -1349,7 +1287,7 @@ mod extra_protobuf {
|
|||||||
|
|
||||||
#[cfg(feature = "yaml")]
|
#[cfg(feature = "yaml")]
|
||||||
mod yaml {
|
mod yaml {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
@@ -1378,7 +1316,7 @@ mod yaml {
|
|||||||
|
|
||||||
#[cfg(feature = "msgpack")]
|
#[cfg(feature = "msgpack")]
|
||||||
mod msgpack {
|
mod msgpack {
|
||||||
use crate::test::{
|
use super::{
|
||||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||||
ParametersExValidationArguments,
|
ParametersExValidationArguments,
|
||||||
};
|
};
|
||||||
88
src/yaml.rs
88
src/yaml.rs
@@ -12,35 +12,73 @@
|
|||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! #![cfg(feature = "validator")]
|
//! #[cfg(feature = "validator")]
|
||||||
//!
|
//! mod validator_example {
|
||||||
//! use axum::routing::post;
|
//! use axum::routing::post;
|
||||||
//! use axum::Router;
|
//! use axum_yaml::Yaml;
|
||||||
//! use axum_valid::Valid;
|
//! use axum::Router;
|
||||||
//! use axum_yaml::Yaml;
|
//! use axum_valid::Valid;
|
||||||
//! use serde::Deserialize;
|
//! use serde::Deserialize;
|
||||||
//! use validator::Validate;
|
//! use validator::Validate;
|
||||||
//!
|
//! #[tokio::main]
|
||||||
//! #[tokio::main]
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
//! let router = Router::new().route("/yaml", post(handler));
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
//! .serve(router.into_make_service())
|
||||||
//! .serve(router.into_make_service())
|
//! .await?;
|
||||||
//! .await?;
|
//! Ok(())
|
||||||
//! Ok(())
|
//! }
|
||||||
|
//! async fn handler(Valid(Yaml(parameter)): Valid<Yaml<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate().is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[validate(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[validate(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! async fn handler(parameter: Valid<Yaml<Parameter>>) {
|
//! #[cfg(feature = "garde")]
|
||||||
//! assert!(parameter.validate().is_ok());
|
//! mod garde_example {
|
||||||
|
//! use axum::routing::post;
|
||||||
|
//! use axum_yaml::Yaml;
|
||||||
|
//! use axum::Router;
|
||||||
|
//! use axum_valid::Garde;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//! use garde::Validate;
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn launch() -> anyhow::Result<()> {
|
||||||
|
//! let router = Router::new().route("/json", post(handler));
|
||||||
|
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
|
||||||
|
//! .serve(router.into_make_service())
|
||||||
|
//! .await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! async fn handler(Garde(Yaml(parameter)): Garde<Yaml<Parameter>>) {
|
||||||
|
//! assert!(parameter.validate(&()).is_ok());
|
||||||
|
//! // Support automatic dereferencing
|
||||||
|
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
|
||||||
|
//! }
|
||||||
|
//! #[derive(Validate, Deserialize)]
|
||||||
|
//! pub struct Parameter {
|
||||||
|
//! #[garde(range(min = 5, max = 10))]
|
||||||
|
//! pub v0: i32,
|
||||||
|
//! #[garde(length(min = 1, max = 10))]
|
||||||
|
//! pub v1: String,
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Deserialize, Validate)]
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
//! struct Parameter {
|
//! # #[cfg(feature = "validator")]
|
||||||
//! #[validate(range(min = 5, max = 10))]
|
//! # validator_example::launch()?;
|
||||||
//! v0: i32,
|
//! # #[cfg(feature = "garde")]
|
||||||
//! #[validate(length(min = 1, max = 10))]
|
//! # garde_example::launch()?;
|
||||||
//! v1: String,
|
//! # Ok(())
|
||||||
//! }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::HasValidate;
|
use crate::HasValidate;
|
||||||
|
|||||||
Reference in New Issue
Block a user