refactor doc tests

This commit is contained in:
gengteng
2023-10-09 11:37:27 +08:00
parent 928578a840
commit 6cf325b19d
14 changed files with 1510 additions and 332 deletions

285
README.md
View File

@@ -7,11 +7,15 @@
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/gengteng/axum-valid/.github/workflows/main.yml?branch=main)](https://github.com/gengteng/axum-valid/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/gengteng/axum-valid/badge.svg?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
@@ -20,43 +24,101 @@ cargo add axum-valid
```
```rust,no_run
use validator::Validate;
use serde::Deserialize;
use axum_valid::Valid;
use axum::extract::Query;
use axum::{Json, Router};
use axum::routing::{get, post};
#[derive(Debug, Validate, Deserialize)]
pub struct Pager {
#[validate(range(min = 1, max = 50))]
pub page_size: usize,
#[validate(range(min = 1))]
pub page_no: usize,
#[cfg(feature = "validator")]
mod validator_example {
use validator::Validate;
use serde::Deserialize;
use axum_valid::Valid;
use axum::extract::Query;
use axum::{Json, Router};
use axum::routing::{get, post};
#[derive(Debug, Validate, Deserialize)]
pub struct Pager {
#[validate(range(min = 1, max = 50))]
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(
Valid(Query(pager)): Valid<Query<Pager>>,
) {
assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no));
#[cfg(feature = "garde")]
mod garde_example {
use garde::Validate;
use serde::Deserialize;
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(
Valid(Json(pager)): Valid<Json<Pager>>,
) {
assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no));
}
#[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?;
fn main() -> anyhow::Result<()> {
#[cfg(feature = "validator")]
validator_example::launch()?;
#[cfg(feature = "garde")]
garde_example::launch()?;
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:
```rust,no_run
use axum::routing::post;
use axum::{Form, Router};
use axum_valid::{Arguments, ValidEx};
use serde::Deserialize;
use std::ops::{RangeFrom, RangeInclusive};
use validator::{Validate, ValidateArgs, ValidationError};
// NOTE: When some fields use custom validation functions with arguments,
// `#[derive(Validate)]` will implement `ValidateArgs` instead of `Validate` for the type.
// The validation arguments will be a tuple of all the field validation args.
// In this example it is (&RangeInclusive<usize>, &RangeFrom<usize>).
// For more detailed information and understanding of `ValidateArgs` and their argument types,
// please refer to the `validator` crate documentation.
#[derive(Debug, Validate, Deserialize)]
pub struct Pager {
#[validate(custom(function = "validate_page_size", arg = "&'v_a RangeInclusive<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(())
.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(())
.ok_or_else(|| ValidationError::new("page_no is out of range"))
}
// NOTE: Clone is required
#[derive(Debug, Clone)]
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.
impl<'a> Arguments<'a> for PagerValidArgs {
type T = Pager;
// 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)
#[cfg(feature = "validator")]
mod validator_example {
use axum::routing::post;
use axum::{Form, Router};
use axum_valid::{Arguments, ValidEx};
use serde::Deserialize;
use std::ops::{RangeFrom, RangeInclusive};
use validator::{Validate, ValidateArgs, ValidationError};
// NOTE: When some fields use custom validation functions with arguments,
// `#[derive(Validate)]` will implement `ValidateArgs` instead of `Validate` for the type.
// The validation arguments will be a tuple of all the field validation args.
// In this example it is (&RangeInclusive<usize>, &RangeFrom<usize>).
// For more detailed information and understanding of `ValidateArgs` and their argument types,
// please refer to the `validator` crate documentation.
#[derive(Debug, Validate, Deserialize)]
pub struct Pager {
#[validate(custom(function = "validate_page_size", arg = "&'v_a RangeInclusive<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(())
.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(())
.ok_or_else(|| ValidationError::new("page_no is out of range"))
}
// NOTE: Clone is required
#[derive(Debug, Clone)]
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.
impl<'a> Arguments<'a> for PagerValidArgs {
type T = Pager;
// 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>) {
assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no));
#[cfg(feature = "garde")]
mod garde_example {
#[tokio::main]
pub async fn launch() -> anyhow::Result<()> {
Ok(())
}
}
#[tokio::main]
async fn main() -> 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?;
fn main() -> anyhow::Result<()> {
#[cfg(feature = "validator")]
validator_example::launch()?;
#[cfg(feature = "garde")]
garde_example::launch()?;
Ok(())
}
```