add examples and support for typed_path

This commit is contained in:
gengteng
2023-09-12 13:02:09 +08:00
parent 6f3915bb87
commit e7dba8fd0b
17 changed files with 904 additions and 39 deletions

View File

@@ -1,11 +1,12 @@
[package] [package]
name = "axum-valid" name = "axum-valid"
version = "0.6.0" version = "0.7.0"
description = "Provide validator extractor for your axum application." description = "Provide validator extractor for your axum application."
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"
repository = "https://github.com/gengteng/axum-valid" repository = "https://github.com/gengteng/axum-valid"
documentation = "https://docs.rs/axum-valid"
keywords = [ keywords = [
"axum", "axum",
"validator", "validator",
@@ -19,6 +20,9 @@ categories = [
] ]
edition = "2021" edition = "2021"
[package.metadata.docs.rs]
features = ["all_types"]
[dependencies] [dependencies]
axum = { version = "0.6.18", default-features = false } axum = { version = "0.6.18", default-features = false }
validator = "0.16.0" validator = "0.16.0"
@@ -70,9 +74,10 @@ yaml = ["axum-yaml"]
into_json = ["json"] into_json = ["json"]
422 = [] 422 = []
extra = ["axum-extra"] extra = ["axum-extra"]
extra_typed_path = ["axum-extra/typed-routing"]
extra_query = ["axum-extra/query"] extra_query = ["axum-extra/query"]
extra_form = ["axum-extra/form"] extra_form = ["axum-extra/form"]
extra_protobuf = ["axum-extra/protobuf"] extra_protobuf = ["axum-extra/protobuf"]
all_extra_types = ["extra", "extra_query", "extra_form", "extra_protobuf"] all_extra_types = ["extra", "extra_typed_path", "extra_query", "extra_form", "extra_protobuf"]
all_types = ["json", "form", "query", "typed_header", "typed_multipart", "msgpack", "yaml", "all_extra_types"] all_types = ["json", "form", "query", "typed_header", "typed_multipart", "msgpack", "yaml", "all_extra_types"]
full = ["all_types", "422", "into_json"] full = ["all_types", "422", "into_json"]

View File

@@ -10,13 +10,13 @@ This crate provides a `Valid` type that can be used in combination with `Json`,
Additional extractors like `TypedHeader`, `MsgPack`, `Yaml` etc. are supported through optional features. The full list of supported extractors is in the Features section below. Additional extractors like `TypedHeader`, `MsgPack`, `Yaml` etc. are supported through optional features. The full list of supported extractors is in the Features section below.
## Usage ## Basic usage
```shell ```shell
cargo add axum-valid cargo add axum-valid
``` ```
```rust ```rust,no_run
use validator::Validate; use validator::Validate;
use serde::Deserialize; use serde::Deserialize;
use axum_valid::Valid; use axum_valid::Valid;
@@ -31,44 +31,58 @@ pub struct Pager {
pub page_no: usize, pub page_no: usize,
} }
pub async fn get_page_by_query( pub async fn pager_from_query(
Valid(Query(pager)): Valid<Query<Pager>>, Valid(Query(pager)): Valid<Query<Pager>>,
) { ) {
assert!((1..=50).contains(&pager.page_size)); assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no)); assert!((1..).contains(&pager.page_no));
} }
pub async fn get_page_by_json( pub async fn pager_from_json(
Valid(Json(pager)): Valid<Json<Pager>>, Valid(Json(pager)): Valid<Json<Pager>>,
) { ) {
assert!((1..=50).contains(&pager.page_size)); assert!((1..=50).contains(&pager.page_size));
assert!((1..).contains(&pager.page_no)); 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?;
Ok(())
}
``` ```
When validation errors occur, the extractor will automatically return 400 with validation errors as the HTTP message body. When validation errors occur, the extractor will automatically return 400 with validation errors as the HTTP message body.
To see how each extractor can be used with `Valid`, please refer to the example in the [documentation](#modules) of the corresponding module.
## Features ## Features
| Feature | Description | Default | Tests | | Feature | Description | Default | Example | Tests |
|-----------------|------------------------------------------------------------------------------------------------------|---------|-------| |------------------|------------------------------------------------------------------------------------------------------|---------|---------|-------|
| default | Enables support for `Path`, `Query`, `Json` and `Form` | ✅ | ✅ | | default | Enables support for `Path`, `Query`, `Json` and `Form` | ✅ | ✅ | ✅ |
| json | Enables support for `Json` | ✅ | ✅ | | json | Enables support for `Json` | ✅ | ✅ | ✅ |
| query | Enables support for `Query` | ✅ | ✅ | | query | Enables support for `Query` | ✅ | ✅ | ✅ |
| form | Enables support for `Form` | ✅ | ✅ | | form | Enables support for `Form` | ✅ | ✅ | ✅ |
| typed_header | Enables support for `TypedHeader` | ❌ | ✅ | | typed_header | Enables support for `TypedHeader` | ❌ | ✅ | ✅ |
| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | ❌ | ✅ | | typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | ❌ | ✅ | ✅ |
| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | ❌ | ✅ | | msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | ❌ | ✅ | ✅ |
| yaml | Enables support for `Yaml` from `axum-yaml` | ❌ | ✅ | | yaml | Enables support for `Yaml` from `axum-yaml` | ❌ | ✅ | ✅ |
| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | ❌ | ✅ | | extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | ❌ | ✅ | ✅ |
| extra_query | Enables support for `Query` from `axum-extra` | ❌ | ✅ | | extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | ❌ | ✅ | ✅ |
| extra_form | Enables support for `Form` from `axum-extra` | ❌ | ✅ | | extra_query | Enables support for `Query` from `axum-extra` | ❌ | ✅ | ✅ |
| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | ❌ | ✅ | | extra_form | Enables support for `Form` from `axum-extra` | ❌ | ✅ | ✅ |
| all_extra_types | Enables support for all extractors above from `axum-extra` | ❌ | ✅ | | extra_protobuf | Enables support for `Protobuf` from `axum-extra` | ❌ | ✅ | ✅ |
| all_types | Enables support for all extractors above | ❌ | ✅ | | all_extra_types | Enables support for all extractors above from `axum-extra` | ❌ | ✅ | ✅ |
| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | ❌ | ✅ | | all_types | Enables support for all extractors above | ❌ | ✅ | ✅ |
| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | | ✅ | | 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | ❌ | | ✅ |
| full | Enables all features | | ✅ | | into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | ❌ | | ✅ |
| full | Enables all features | ❌ | ✅ | ✅ |
## License ## License
@@ -78,4 +92,8 @@ This project is licensed under the MIT License.
* [axum](https://crates.io/crates/axum) * [axum](https://crates.io/crates/axum)
* [validator](https://crates.io/crates/validator) * [validator](https://crates.io/crates/validator)
* [serde](https://crates.io/crates/serde) * [serde](https://crates.io/crates/serde)
* [axum-extra](https://crates.io/crates/axum-extra)
* [axum-yaml](https://crates.io/crates/axum-yaml)
* [axum-msgpack](https://crates.io/crates/axum-msgpack)
* [axum_typed_multipart](https://crates.io/crates/axum_typed_multipart)

View File

@@ -1,5 +1,220 @@
//! # Implementation of the `HasValidate` trait for the extractor in `axum-extra`. //! # Support for `Cached<T>` and `WithRejection<T, R>`
//! //!
//! ## Feature
//!
//! Enable the `extra` feature to use `Valid<Cached<T>>`, `Valid<WithRejection<T, R>>` and `WithRejection<Valid<T>, R>`.
//!
//! ## `Valid<Cached<T>>`
//!
//! ### Usage
//!
//! 0. Implement your own extractor `T`.
//! 1. Implement `Clone` and `Validate` for your extractor type `T`.
//! 2. In your handler function, use `Valid<Cached<T>>` as some parameter's type.
//!
//! ### Example
//!
//! ```no_run
//! use axum::extract::FromRequestParts;
//! use axum::http::request::Parts;
//! use axum::response::{IntoResponse, Response};
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::extract::Cached;
//! use axum_valid::Valid;
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/cached", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//! async fn handler(Valid(Cached(parameter)): Valid<Cached<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//! #[derive(Validate, Clone)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//!
//! pub struct ParameterRejection;
//!
//! impl IntoResponse for ParameterRejection {
//! fn into_response(self) -> Response {
//! todo!()
//! }
//! }
//!
//! #[axum::async_trait]
//! impl<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! }
//! ```
//!
//! ## `Valid<WithRejection<T, R>>`
//!
//! ### Usage
//!
//! 0. Implement your own extractor `T` and rejection type `R`.
//! 1. Implement `Validate` for your extractor type `T`.
//! 2. In your handler function, use `Valid<WithRejection<T, R>>` as some parameter's type.
//!
//! ### Example
//!
//! ```no_run
//! use axum::extract::FromRequestParts;
//! use axum::http::request::Parts;
//! use axum::http::StatusCode;
//! use axum::response::{IntoResponse, Response};
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::extract::WithRejection;
//! use axum_valid::Valid;
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/valid_with_rejection", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//! async fn handler(
//! Valid(WithRejection(parameter, _)): Valid<
//! WithRejection<Parameter, ValidWithRejectionRejection>,
//! >,
//! ) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(Validate)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//!
//! pub struct ValidWithRejectionRejection;
//!
//! impl IntoResponse for ValidWithRejectionRejection {
//! fn into_response(self) -> Response {
//! StatusCode::BAD_REQUEST.into_response()
//! }
//! }
//!
//! #[axum::async_trait]
//! impl<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ValidWithRejectionRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! }
//! ```
//!
//! ## `WithRejection<Valid<T>, R>`
//!
//! ### Usage
//!
//! 0. Implement your own extractor `T` and rejection type `R`.
//! 1. Implement `Validate` and `HasValidate` for your extractor type `T`.
//! 2. Implement `From<ValidRejection<T::Rejection>>` for `R`.
//! 3. In your handler function, use `WithRejection<Valid<T>, R>` as some parameter's type.
//!
//! ### Example
//!
//! ```no_run
//! use axum::extract::FromRequestParts;
//! use axum::http::request::Parts;
//! use axum::response::{IntoResponse, Response};
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::extract::WithRejection;
//! use axum_valid::{HasValidate, Valid, ValidRejection};
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/with_rejection_valid", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//! async fn handler(
//! WithRejection(Valid(parameter), _): WithRejection<
//! Valid<Parameter>,
//! WithRejectionValidRejection,
//! >,
//! ) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(Validate)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//!
//! impl HasValidate for Parameter {
//! type Validate = Self;
//!
//! fn get_validate(&self) -> &Self::Validate {
//! self
//! }
//! }
//!
//! pub struct ParameterRejection;
//!
//! impl IntoResponse for ParameterRejection {
//! fn into_response(self) -> Response {
//! todo!()
//! }
//! }
//!
//! #[axum::async_trait]
//! impl<S> FromRequestParts<S> for Parameter
//! where
//! S: Send + Sync,
//! {
//! type Rejection = ParameterRejection;
//!
//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
//! todo!()
//! }
//! }
//!
//! pub struct WithRejectionValidRejection;
//!
//! impl From<ValidRejection<ParameterRejection>> for WithRejectionValidRejection {
//! fn from(_inner: ValidRejection<ParameterRejection>) -> Self {
//! todo!()
//! }
//! }
//!
//! impl IntoResponse for WithRejectionValidRejection {
//! fn into_response(self) -> Response {
//! todo!()
//! }
//! }
//! ```
#[cfg(feature = "extra_form")] #[cfg(feature = "extra_form")]
pub mod form; pub mod form;
@@ -7,6 +222,8 @@ pub mod form;
pub mod protobuf; pub mod protobuf;
#[cfg(feature = "extra_query")] #[cfg(feature = "extra_query")]
pub mod query; pub mod query;
#[cfg(feature = "extra_typed_path")]
pub mod typed_path;
use crate::HasValidate; use crate::HasValidate;
use axum_extra::extract::{Cached, WithRejection}; use axum_extra::extract::{Cached, WithRejection};

View File

@@ -1,5 +1,42 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor in `axum-extra`. //! # Support for `Form<T>` from `axum-extra`
//! //!
//! ## Feature
//!
//! Enable the `extra_form` feature to use `Valid<Form<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Form<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::extract::Form;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/extra_form", 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());
//! }
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum_extra::extract::Form; use axum_extra::extract::Form;

View File

@@ -1,5 +1,44 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor. //! # Support for `Protobuf<T>` from `axum-extra`
//! //!
//! ## Feature
//!
//! Enable the `extra_protobuf` feature to use `Valid<Protobuf<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `prost::Message` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Protobuf<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::protobuf::Protobuf;
//! use axum_valid::Valid;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/protobuf", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//! async fn handler(Valid(Protobuf(parameter)): Valid<Protobuf<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//! #[derive(Validate, prost::Message)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! #[prost(int32, tag = "1")]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! #[prost(string, tag = "2")]
//! pub v1: String,
//! }
//! ```
use crate::HasValidate; use crate::HasValidate;
use axum_extra::protobuf::Protobuf; use axum_extra::protobuf::Protobuf;

View File

@@ -1,5 +1,45 @@
//! # Implementation of the `HasValidate` trait for the `Query` extractor in `axum-extra`. //! # Support for `Query<T>` from `axum-extra`
//! //!
//! ## Feature
//!
//! Enable the `extra_query` feature to use `Valid<Query<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Query<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_extra::extract::Query;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/extra_query", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(Valid(Query(parameter)): Valid<Query<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum_extra::extract::Query; use axum_extra::extract::Query;

64
src/extra/typed_path.rs Normal file
View File

@@ -0,0 +1,64 @@
//! # Support for `T: TypedPath` from `axum-extra`
//!
//! ## Feature
//!
//! Enable the `extra_typed_path` feature to use `Valid<T: TypedPath>`.
//!
//! ## Usage
//!
//! 1. Implement `TypedPath`, `Deserialize`, `Validate` and `HasValidate` for your data type `T`.
//! 2. In your handler function, use `Valid<T>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::Router;
//! use axum_extra::routing::{RouterExt, TypedPath};
//! use axum_valid::{HasValidate, Valid};
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().typed_get(handler);
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(parameter: Valid<Parameter>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(TypedPath, Deserialize, Validate)]
//! #[typed_path("/extra_typed_path/:v0/:v1")]
//! struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! v1: String,
//! }
//!
//! impl HasValidate for Parameter {
//! type Validate = Self;
//!
//! fn get_validate(&self) -> &Self::Validate {
//! self
//! }
//! }
//! ```
use crate::Valid;
use axum_extra::routing::TypedPath;
use std::fmt::{Display, Formatter};
impl<T: Display> Display for Valid<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<T: TypedPath + Display> TypedPath for Valid<T> {
const PATH: &'static str = T::PATH;
}

View File

@@ -1,5 +1,42 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor. //! # Support for `Form<T>`
//! //!
//! ## Feature
//!
//! Enable the `form` feature (enabled by default) to use `Valid<Form<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Form<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Form;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/form", 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());
//! }
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum::Form; use axum::Form;

View File

@@ -1,5 +1,42 @@
//! # Implementation of the `HasValidate` trait for the `Json` extractor. //! # Support for `Json<T>`
//! //!
//! ## Feature
//!
//! Enable the `json` feature (enabled by default) to use `Valid<Json<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Json<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Json;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//! #[tokio::main]
//! async fn main() -> 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());
//! }
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum::Json; use axum::Json;

View File

@@ -116,6 +116,9 @@ impl<E: IntoResponse> IntoResponse for ValidRejection<E> {
} }
/// Trait for types that can provide a reference that can be validated for correctness. /// Trait for types that can provide a reference that can be validated for correctness.
///
/// Extractor types `T` that implement this trait can be used with `Valid`.
///
pub trait HasValidate { pub trait HasValidate {
/// Inner type that can be validated for correctness /// Inner type that can be validated for correctness
type Validate: Validate; type Validate: Validate;

View File

@@ -1,5 +1,51 @@
//! # Implementation of the `HasValidate` trait for the `MsgPack` extractor. //! # Support for `MsgPack<T>` and `MsgPackRaw<T>` from `axum-msgpack`
//! //!
//! ## Feature
//!
//! Enable the `msgpack` feature to use `Valid<MsgPack<T>>` and `Valid<MsgPackRaw<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<MsgPack<T>>` or `Valid<MsgPackRaw<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_msgpack::{MsgPack, MsgPackRaw};
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> 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());
//! }
//!
//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid<MsgPackRaw<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum_msgpack::{MsgPack, MsgPackRaw}; use axum_msgpack::{MsgPack, MsgPackRaw};

View File

@@ -1,5 +1,41 @@
//! # Implementation of the `HasValidate` trait for the `Path` extractor. //! # Support for `Path<T>`
//! //!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Path<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::extract::Path;
//! use axum::routing::post;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/path/:v0/:v1", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(Valid(Path(parameter)): Valid<Path<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum::extract::Path; use axum::extract::Path;

View File

@@ -1,5 +1,45 @@
//! # Implementation of the `HasValidate` trait for the `Query` extractor. //! # Support for `Query<T>`
//! //!
//! ## Feature
//!
//! Enable the `query` feature (enabled by default) to use `Valid<Query<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Query<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::extract::Query;
//! use axum::routing::post;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/query", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(Valid(Query(parameter)): Valid<Query<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[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 crate::HasValidate; use crate::HasValidate;
use axum::extract::Query; use axum::extract::Query;

View File

@@ -96,6 +96,12 @@ async fn test_main() -> anyhow::Result<()> {
post(extra::extract_with_rejection_valid), 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")] #[cfg(feature = "extra_query")]
let router = router.route( let router = router.route(
extra_query::route::EXTRA_QUERY, extra_query::route::EXTRA_QUERY,
@@ -246,6 +252,61 @@ async fn test_main() -> anyhow::Result<()> {
.await?; .await?;
} }
#[cfg(feature = "extra_typed_path")]
{
{
let extra_typed_path_type_name = "T: TypedPath";
let valid_extra_typed_path_response = test_executor
.client()
.get(format!(
"{}/extra_typed_path/{}/{}",
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
))
.send()
.await?;
assert_eq!(
valid_extra_typed_path_response.status(),
StatusCode::OK,
"Valid '{}' test failed.",
extra_typed_path_type_name
);
let error_extra_typed_path_response = test_executor
.client()
.get(format!("{}/extra_typed_path/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!(
"{}/extra_typed_path/{}/{}",
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);
}
}
#[cfg(feature = "extra_query")] #[cfg(feature = "extra_query")]
{ {
use axum_extra::extract::Query; use axum_extra::extract::Query;
@@ -411,7 +472,7 @@ fn validate_again<V: Validate>(validate: V) -> StatusCode {
mod typed_header { mod typed_header {
pub(crate) mod route { pub(crate) mod route {
pub const TYPED_HEADER: &str = "/typedHeader"; pub const TYPED_HEADER: &str = "/typed_header";
} }
use super::{validate_again, Parameters}; use super::{validate_again, Parameters};
@@ -665,6 +726,41 @@ mod extra {
} }
} }
#[cfg(feature = "extra_typed_path")]
mod extra_typed_path {
use crate::test::validate_again;
use crate::{HasValidate, Valid};
use axum::http::StatusCode;
use axum_extra::routing::TypedPath;
use serde::Deserialize;
use validator::Validate;
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 {
#[validate(range(min = 5, max = 10))]
v0: i32,
#[validate(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(Valid(param): Valid<TypedPathParam>) -> StatusCode {
validate_again(param)
}
}
#[cfg(feature = "extra_query")] #[cfg(feature = "extra_query")]
mod extra_query { mod extra_query {
use crate::test::{validate_again, Parameters}; use crate::test::{validate_again, Parameters};

View File

@@ -1,5 +1,65 @@
//! # Implementation of the `HasValidate` trait for the `TypedHeader` extractor. //! # Support for `TypedHeader<T>`
//! //!
//! ## Feature
//!
//! Enable the `typed_header` feature to use `Valid<TypedHeader<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Header` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<TypedHeader<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::headers::{Error, Header, HeaderValue};
//! use axum::http::HeaderName;
//! use axum::routing::post;
//! use axum::{Router, TypedHeader};
//! use axum_valid::Valid;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/typed_header", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(Valid(TypedHeader(parameter)): Valid<TypedHeader<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(Validate)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//!
//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header");
//!
//! impl Header for Parameter {
//! fn name() -> &'static HeaderName {
//! &HEADER_NAME
//! }
//!
//! fn decode<'i, I>(_values: &mut I) -> Result<Self, Error>
//! where
//! Self: Sized,
//! I: Iterator<Item = &'i HeaderValue>,
//! {
//! todo!()
//! }
//!
//! fn encode<E: Extend<HeaderValue>>(&self, _values: &mut E) {
//! todo!()
//! }
//! }
//! ```
use crate::HasValidate; use crate::HasValidate;
use axum::TypedHeader; use axum::TypedHeader;

View File

@@ -1,5 +1,54 @@
//! # Implementation of the `HasValidate` trait for the `TypedMultipart` extractor. //! # Support for `TypedMultipart<T>` and `BaseMultipart<T, R>` from `axum_typed_multipart`
//! //!
//! ## Feature
//!
//! Enable the `typed_multipart` feature to use `Valid<TypedMultipart<T>>` and `Valid<BaseMultipart<T, R>>`.
//!
//! ## Usage
//!
//! 1. Implement `TryFromMultipart` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<TypedMultipart<T>>` or `Valid<BaseMultipart<T, E>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_typed_multipart::{BaseMultipart, TryFromMultipart, TypedMultipart, TypedMultipartError};
//! use axum_valid::Valid;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new()
//! .route("/typed_multipart", post(handler))
//! .route("/base_multipart", post(base_handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(Valid(TypedMultipart(parameter)): Valid<TypedMultipart<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! async fn base_handler(
//! Valid(BaseMultipart {
//! data: parameter, ..
//! }): Valid<BaseMultipart<Parameter, TypedMultipartError>>,
//! ) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(TryFromMultipart, Validate)]
//! struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! v1: String,
//! }
//! ```
use crate::HasValidate; use crate::HasValidate;
use axum_typed_multipart::{BaseMultipart, TypedMultipart}; use axum_typed_multipart::{BaseMultipart, TypedMultipart};

View File

@@ -1,5 +1,46 @@
//! # Implementation of the `HasValidate` trait for the `Yaml` extractor. //! # Support for `Yaml<T>` from `axum-yaml`
//! //!
//! ## Feature
//!
//! Enable the `yaml` feature to use `Valid<Yaml<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Yaml<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! use axum::routing::post;
//! use axum::Router;
//! use axum_valid::Valid;
//! use axum_yaml::Yaml;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let router = Router::new().route("/yaml", post(handler));
//! axum::Server::bind(&([0u8, 0, 0, 0], 8080).into())
//! .serve(router.into_make_service())
//! .await?;
//! Ok(())
//! }
//!
//! async fn handler(parameter: Valid<Yaml<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! #[derive(Deserialize, Validate)]
//! struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! v1: String,
//! }
//! ```
use crate::HasValidate; use crate::HasValidate;
use axum_yaml::Yaml; use axum_yaml::Yaml;