diff --git a/Cargo.toml b/Cargo.toml index 9b26cfc..ef1c355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "axum-valid" -version = "0.6.0" +version = "0.7.0" description = "Provide validator extractor for your axum application." authors = ["GengTeng "] license = "MIT" homepage = "https://github.com/gengteng/axum-valid" repository = "https://github.com/gengteng/axum-valid" +documentation = "https://docs.rs/axum-valid" keywords = [ "axum", "validator", @@ -19,6 +20,9 @@ categories = [ ] edition = "2021" +[package.metadata.docs.rs] +features = ["all_types"] + [dependencies] axum = { version = "0.6.18", default-features = false } validator = "0.16.0" @@ -70,9 +74,10 @@ yaml = ["axum-yaml"] into_json = ["json"] 422 = [] extra = ["axum-extra"] +extra_typed_path = ["axum-extra/typed-routing"] extra_query = ["axum-extra/query"] extra_form = ["axum-extra/form"] 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"] full = ["all_types", "422", "into_json"] diff --git a/README.md b/README.md index 3bfe3ff..b30e165 100644 --- a/README.md +++ b/README.md @@ -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. -## Usage +## Basic usage ```shell cargo add axum-valid ``` -```rust +```rust,no_run use validator::Validate; use serde::Deserialize; use axum_valid::Valid; @@ -31,44 +31,58 @@ pub struct Pager { pub page_no: usize, } -pub async fn get_page_by_query( +pub async fn pager_from_query( Valid(Query(pager)): Valid>, ) { assert!((1..=50).contains(&pager.page_size)); assert!((1..).contains(&pager.page_no)); } -pub async fn get_page_by_json( +pub async fn pager_from_json( Valid(Json(pager)): Valid>, ) { 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?; + Ok(()) +} ``` 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 -| Feature | Description | Default | Tests | -|-----------------|------------------------------------------------------------------------------------------------------|---------|-------| -| default | Enables support for `Path`, `Query`, `Json` and `Form` | ✅ | ✅ | -| json | Enables support for `Json` | ✅ | ✅ | -| query | Enables support for `Query` | ✅ | ✅ | -| form | Enables support for `Form` | ✅ | ✅ | -| typed_header | Enables support for `TypedHeader` | ❌ | ✅ | -| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | ❌ | ✅ | -| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | ❌ | ✅ | -| yaml | Enables support for `Yaml` from `axum-yaml` | ❌ | ✅ | -| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | ❌ | ✅ | -| extra_query | Enables support for `Query` from `axum-extra` | ❌ | ✅ | -| extra_form | Enables support for `Form` from `axum-extra` | ❌ | ✅ | -| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | ❌ | ✅ | -| all_extra_types | Enables support for all extractors above from `axum-extra` | ❌ | ✅ | -| all_types | Enables support for all extractors above | ❌ | ✅ | -| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | ❌ | ✅ | -| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | ❌ | ✅ | -| full | Enables all features | ❌ | ✅ | +| Feature | Description | Default | Example | Tests | +|------------------|------------------------------------------------------------------------------------------------------|---------|---------|-------| +| default | Enables support for `Path`, `Query`, `Json` and `Form` | ✅ | ✅ | ✅ | +| json | Enables support for `Json` | ✅ | ✅ | ✅ | +| query | Enables support for `Query` | ✅ | ✅ | ✅ | +| form | Enables support for `Form` | ✅ | ✅ | ✅ | +| typed_header | Enables support for `TypedHeader` | ❌ | ✅ | ✅ | +| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | ❌ | ✅ | ✅ | +| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | ❌ | ✅ | ✅ | +| yaml | Enables support for `Yaml` from `axum-yaml` | ❌ | ✅ | ✅ | +| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | ❌ | ✅ | ✅ | +| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | ❌ | ✅ | ✅ | +| extra_query | Enables support for `Query` from `axum-extra` | ❌ | ✅ | ✅ | +| extra_form | Enables support for `Form` from `axum-extra` | ❌ | ✅ | ✅ | +| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | ❌ | ✅ | ✅ | +| all_extra_types | Enables support for all extractors above from `axum-extra` | ❌ | ✅ | ✅ | +| all_types | Enables support for all extractors above | ❌ | ✅ | ✅ | +| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | ❌ | ✅ | ✅ | +| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | ❌ | ✅ | ✅ | +| full | Enables all features | ❌ | ✅ | ✅ | ## License @@ -78,4 +92,8 @@ This project is licensed under the MIT License. * [axum](https://crates.io/crates/axum) * [validator](https://crates.io/crates/validator) -* [serde](https://crates.io/crates/serde) \ No newline at end of file +* [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) \ No newline at end of file diff --git a/src/extra.rs b/src/extra.rs index def5d59..2bd85ab 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -1,5 +1,220 @@ -//! # Implementation of the `HasValidate` trait for the extractor in `axum-extra`. +//! # Support for `Cached` and `WithRejection` //! +//! ## Feature +//! +//! Enable the `extra` feature to use `Valid>`, `Valid>` and `WithRejection, R>`. +//! +//! ## `Valid>` +//! +//! ### Usage +//! +//! 0. Implement your own extractor `T`. +//! 1. Implement `Clone` and `Validate` for your extractor type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! ``` +//! +//! ## `Valid>` +//! +//! ### 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>` 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, +//! >, +//! ) { +//! 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 FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ValidWithRejectionRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! ``` +//! +//! ## `WithRejection, 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>` for `R`. +//! 3. In your handler function, use `WithRejection, 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, +//! 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 FromRequestParts for Parameter +//! where +//! S: Send + Sync, +//! { +//! type Rejection = ParameterRejection; +//! +//! async fn from_request_parts(_parts: &mut Parts, _: &S) -> Result { +//! todo!() +//! } +//! } +//! +//! pub struct WithRejectionValidRejection; +//! +//! impl From> for WithRejectionValidRejection { +//! fn from(_inner: ValidRejection) -> Self { +//! todo!() +//! } +//! } +//! +//! impl IntoResponse for WithRejectionValidRejection { +//! fn into_response(self) -> Response { +//! todo!() +//! } +//! } +//! ``` #[cfg(feature = "extra_form")] pub mod form; @@ -7,6 +222,8 @@ pub mod form; pub mod protobuf; #[cfg(feature = "extra_query")] pub mod query; +#[cfg(feature = "extra_typed_path")] +pub mod typed_path; use crate::HasValidate; use axum_extra::extract::{Cached, WithRejection}; diff --git a/src/extra/form.rs b/src/extra/form.rs index a542dd7..cc22471 100644 --- a/src/extra/form.rs +++ b/src/extra/form.rs @@ -1,5 +1,42 @@ -//! # Implementation of the `HasValidate` trait for the `Form` extractor in `axum-extra`. +//! # Support for `Form` from `axum-extra` //! +//! ## Feature +//! +//! Enable the `extra_form` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum_extra::extract::Form; diff --git a/src/extra/protobuf.rs b/src/extra/protobuf.rs index d04ce3b..5ad13c2 100644 --- a/src/extra/protobuf.rs +++ b/src/extra/protobuf.rs @@ -1,5 +1,44 @@ -//! # Implementation of the `HasValidate` trait for the `Form` extractor. +//! # Support for `Protobuf` from `axum-extra` //! +//! ## Feature +//! +//! Enable the `extra_protobuf` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `prost::Message` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum_extra::protobuf::Protobuf; diff --git a/src/extra/query.rs b/src/extra/query.rs index 4e7a371..205a861 100644 --- a/src/extra/query.rs +++ b/src/extra/query.rs @@ -1,5 +1,45 @@ -//! # Implementation of the `HasValidate` trait for the `Query` extractor in `axum-extra`. +//! # Support for `Query` from `axum-extra` //! +//! ## Feature +//! +//! Enable the `extra_query` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum_extra::extract::Query; diff --git a/src/extra/typed_path.rs b/src/extra/typed_path.rs new file mode 100644 index 0000000..90601b6 --- /dev/null +++ b/src/extra/typed_path.rs @@ -0,0 +1,64 @@ +//! # Support for `T: TypedPath` from `axum-extra` +//! +//! ## Feature +//! +//! Enable the `extra_typed_path` feature to use `Valid`. +//! +//! ## Usage +//! +//! 1. Implement `TypedPath`, `Deserialize`, `Validate` and `HasValidate` for your data type `T`. +//! 2. In your handler function, use `Valid` 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) { +//! 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 Display for Valid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl TypedPath for Valid { + const PATH: &'static str = T::PATH; +} diff --git a/src/form.rs b/src/form.rs index 774cc13..33d56ba 100644 --- a/src/form.rs +++ b/src/form.rs @@ -1,5 +1,42 @@ -//! # Implementation of the `HasValidate` trait for the `Form` extractor. +//! # Support for `Form` //! +//! ## Feature +//! +//! Enable the `form` feature (enabled by default) to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum::Form; diff --git a/src/json.rs b/src/json.rs index 118e914..a61552d 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,5 +1,42 @@ -//! # Implementation of the `HasValidate` trait for the `Json` extractor. +//! # Support for `Json` //! +//! ## Feature +//! +//! Enable the `json` feature (enabled by default) to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum::Json; diff --git a/src/lib.rs b/src/lib.rs index c493b0c..709f0c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,9 @@ impl IntoResponse for ValidRejection { } /// 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 { /// Inner type that can be validated for correctness type Validate: Validate; diff --git a/src/msgpack.rs b/src/msgpack.rs index bf0ea46..24f5f0f 100644 --- a/src/msgpack.rs +++ b/src/msgpack.rs @@ -1,5 +1,51 @@ -//! # Implementation of the `HasValidate` trait for the `MsgPack` extractor. +//! # Support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` //! +//! ## Feature +//! +//! Enable the `msgpack` feature to use `Valid>` and `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` or `Valid>` 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>) { +//! assert!(parameter.validate().is_ok()); +//! } +//! +//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid>) { +//! 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 axum_msgpack::{MsgPack, MsgPackRaw}; diff --git a/src/path.rs b/src/path.rs index bea9f1b..36f9758 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,5 +1,41 @@ -//! # Implementation of the `HasValidate` trait for the `Path` extractor. +//! # Support for `Path` //! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum::extract::Path; diff --git a/src/query.rs b/src/query.rs index 99bb6fc..863acc9 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,5 +1,45 @@ -//! # Implementation of the `HasValidate` trait for the `Query` extractor. +//! # Support for `Query` //! +//! ## Feature +//! +//! Enable the `query` feature (enabled by default) to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum::extract::Query; diff --git a/src/test.rs b/src/test.rs index a2854aa..81ac9bd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -96,6 +96,12 @@ async fn test_main() -> anyhow::Result<()> { 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, @@ -246,6 +252,61 @@ async fn test_main() -> anyhow::Result<()> { .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")] { use axum_extra::extract::Query; @@ -411,7 +472,7 @@ fn validate_again(validate: V) -> StatusCode { mod typed_header { pub(crate) mod route { - pub const TYPED_HEADER: &str = "/typedHeader"; + pub const TYPED_HEADER: &str = "/typed_header"; } 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) -> StatusCode { + validate_again(param) + } +} + #[cfg(feature = "extra_query")] mod extra_query { use crate::test::{validate_again, Parameters}; diff --git a/src/typed_header.rs b/src/typed_header.rs index 2c2e064..346d059 100644 --- a/src/typed_header.rs +++ b/src/typed_header.rs @@ -1,5 +1,65 @@ -//! # Implementation of the `HasValidate` trait for the `TypedHeader` extractor. +//! # Support for `TypedHeader` //! +//! ## Feature +//! +//! Enable the `typed_header` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Header` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 +//! where +//! Self: Sized, +//! I: Iterator, +//! { +//! todo!() +//! } +//! +//! fn encode>(&self, _values: &mut E) { +//! todo!() +//! } +//! } +//! ``` use crate::HasValidate; use axum::TypedHeader; diff --git a/src/typed_multipart.rs b/src/typed_multipart.rs index 9675412..1183009 100644 --- a/src/typed_multipart.rs +++ b/src/typed_multipart.rs @@ -1,5 +1,54 @@ -//! # Implementation of the `HasValidate` trait for the `TypedMultipart` extractor. +//! # Support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` //! +//! ## Feature +//! +//! Enable the `typed_multipart` feature to use `Valid>` and `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `TryFromMultipart` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` or `Valid` 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>) { +//! assert!(parameter.validate().is_ok()); +//! } +//! +//! async fn base_handler( +//! Valid(BaseMultipart { +//! data: parameter, .. +//! }): Valid>, +//! ) { +//! 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 axum_typed_multipart::{BaseMultipart, TypedMultipart}; diff --git a/src/yaml.rs b/src/yaml.rs index cea4049..eaa40b8 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -1,5 +1,46 @@ -//! # Implementation of the `HasValidate` trait for the `Yaml` extractor. +//! # Support for `Yaml` from `axum-yaml` //! +//! ## Feature +//! +//! Enable the `yaml` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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 axum_yaml::Yaml;