feat: add support for sonic
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "axum-valid"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
description = "Provides validation extractors for your Axum application, allowing you to validate data using validator, garde, validify or all of them."
|
||||
authors = ["GengTeng <me@gteng.org>"]
|
||||
license = "MIT"
|
||||
@@ -36,7 +36,7 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.axum-serde]
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.axum_typed_multipart]
|
||||
@@ -55,7 +55,6 @@ optional = true
|
||||
anyhow = "1.0.75"
|
||||
axum = { version = "0.7.1", features = ["macros"] }
|
||||
tokio = { version = "1.34.0", features = ["full"] }
|
||||
hyper = { version = "0.14.27", features = ["full"] }
|
||||
reqwest = { version = "0.11.23", features = ["json", "multipart"] }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
validator = { version = "0.16.1", features = ["derive"] }
|
||||
@@ -83,6 +82,7 @@ msgpack = ["dep:axum-serde", "axum-serde/msgpack"]
|
||||
yaml = ["dep:axum-serde", "axum-serde/yaml"]
|
||||
xml = ["dep:axum-serde", "axum-serde/xml"]
|
||||
toml = ["dep:axum-serde", "axum-serde/toml"]
|
||||
sonic = ["dep:axum-serde", "axum-serde/sonic"]
|
||||
typed_multipart = ["dep:axum_typed_multipart"]
|
||||
into_json = ["json", "dep:serde", "garde?/serde"]
|
||||
422 = []
|
||||
@@ -92,7 +92,7 @@ extra_query = ["extra", "axum-extra/query"]
|
||||
extra_form = ["extra", "axum-extra/form"]
|
||||
extra_protobuf = ["extra", "axum-extra/protobuf"]
|
||||
all_extra_types = ["extra", "typed_header", "extra_typed_path", "extra_query", "extra_form", "extra_protobuf"]
|
||||
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "toml", "all_extra_types", "typed_multipart"]
|
||||
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "toml", "sonic", "all_extra_types", "typed_multipart"]
|
||||
full_validator = ["validator", "all_types", "422", "into_json"]
|
||||
full_garde = ["garde", "all_types", "422", "into_json"]
|
||||
full_validify = ["validify", "all_types", "422", "into_json"]
|
||||
|
||||
@@ -7,9 +7,8 @@ use axum::http::StatusCode;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Form, Json, Router};
|
||||
use garde::Validate;
|
||||
use hyper::Method;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Url;
|
||||
use reqwest::{Method, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::type_name;
|
||||
use std::net::SocketAddr;
|
||||
@@ -146,6 +145,9 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "toml")]
|
||||
let router = router.route(toml::route::TOML, post(toml::extract_toml));
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
let router = router.route(sonic::route::SONIC, post(sonic::extract_sonic));
|
||||
|
||||
let router = router.with_state(MyState::default());
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
@@ -410,6 +412,14 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
{
|
||||
use axum_serde::Sonic;
|
||||
test_executor
|
||||
.execute::<Sonic<ParametersGarde>>(Method::POST, sonic::route::SONIC)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -528,7 +538,6 @@ fn validate_again<V: Validate>(validate: V, context: V::Context) -> StatusCode {
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
mod typed_header {
|
||||
|
||||
pub(crate) mod route {
|
||||
pub const TYPED_HEADER: &str = "/typed_header";
|
||||
}
|
||||
@@ -646,6 +655,7 @@ mod extra {
|
||||
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;
|
||||
|
||||
@@ -903,6 +913,7 @@ mod msgpack {
|
||||
) -> StatusCode {
|
||||
validate_again(parameters, ())
|
||||
}
|
||||
|
||||
pub async fn extract_msgpack_raw(
|
||||
Garde(MsgPackRaw(parameters)): Garde<MsgPackRaw<ParametersGarde>>,
|
||||
) -> StatusCode {
|
||||
@@ -941,3 +952,19 @@ mod toml {
|
||||
validate_again(parameters, ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
mod sonic {
|
||||
use super::{validate_again, ParametersGarde};
|
||||
use crate::Garde;
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Sonic;
|
||||
|
||||
pub mod route {
|
||||
pub const SONIC: &str = "/sonic";
|
||||
}
|
||||
|
||||
pub async fn extract_sonic(Garde(Sonic(parameters)): Garde<Sonic<ParametersGarde>>) -> StatusCode {
|
||||
validate_again(parameters, ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ pub mod toml;
|
||||
pub mod typed_multipart;
|
||||
#[cfg(feature = "xml")]
|
||||
pub mod xml;
|
||||
mod sonic;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
|
||||
160
src/sonic.rs
Normal file
160
src/sonic.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
//! # Support for `Sonic<T>`
|
||||
//!
|
||||
//! ## Feature
|
||||
//!
|
||||
//! Enable the `sonic` feature to use `Valid<Sonic<T>>`.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
|
||||
//! 2. In your handler function, use `Valid<Sonic<T>>` as some parameter's type.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! #[cfg(feature = "validator")]
|
||||
//! mod validator_example {
|
||||
//! use axum::routing::post;
|
||||
//! use axum_serde::Sonic;
|
||||
//! use axum::Router;
|
||||
//! use axum_valid::Valid;
|
||||
//! use serde::Deserialize;
|
||||
//! use validator::Validate;
|
||||
//!
|
||||
//! pub fn router() -> Router {
|
||||
//! Router::new().route("/sonic", post(handler))
|
||||
//! }
|
||||
//!
|
||||
//! async fn handler(Valid(Sonic(parameter)): Valid<Sonic<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,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[cfg(feature = "garde")]
|
||||
//! mod garde_example {
|
||||
//! use axum::routing::post;
|
||||
//! use axum_serde::Sonic;
|
||||
//! use axum::Router;
|
||||
//! use axum_valid::Garde;
|
||||
//! use serde::Deserialize;
|
||||
//! use garde::Validate;
|
||||
//!
|
||||
//! pub fn router() -> Router {
|
||||
//! Router::new().route("/sonic", post(handler))
|
||||
//! }
|
||||
//!
|
||||
//! async fn handler(Garde(Sonic(parameter)): Garde<Sonic<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,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> anyhow::Result<()> {
|
||||
//! # use std::net::SocketAddr;
|
||||
//! # use axum::Router;
|
||||
//! # use tokio::net::TcpListener;
|
||||
//! # let router = Router::new();
|
||||
//! # #[cfg(feature = "validator")]
|
||||
//! # let router = router.nest("/validator", validator_example::router());
|
||||
//! # #[cfg(feature = "garde")]
|
||||
//! # let router = router.nest("/garde", garde_example::router());
|
||||
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
//! # axum::serve(listener, router.into_make_service())
|
||||
//! # .await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::HasValidate;
|
||||
#[cfg(feature = "validator")]
|
||||
use crate::HasValidateArgs;
|
||||
use axum_serde::Sonic;
|
||||
#[cfg(feature = "validator")]
|
||||
use validator::ValidateArgs;
|
||||
|
||||
impl<T> HasValidate for Sonic<T> {
|
||||
type Validate = T;
|
||||
fn get_validate(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validator")]
|
||||
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Sonic<T> {
|
||||
type ValidateArgs = T;
|
||||
fn get_validate_args(&self) -> &Self::ValidateArgs {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Sonic<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T> crate::PayloadExtractor for Sonic<T> {
|
||||
type Payload = T;
|
||||
|
||||
fn get_payload(self) -> Self::Payload {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Validify + validify::ValidifyPayload> crate::HasValidify for Sonic<T> {
|
||||
type Validify = T;
|
||||
type PayloadExtractor = Sonic<T::Payload>;
|
||||
fn from_validify(v: Self::Validify) -> Self {
|
||||
Sonic(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Sonic;
|
||||
use reqwest::RequestBuilder;
|
||||
use serde::Serialize;
|
||||
|
||||
impl<T: ValidTestParameter + Serialize> ValidTest for Sonic<T> {
|
||||
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
|
||||
|
||||
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.json(T::valid())
|
||||
}
|
||||
|
||||
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.json(&serde_json::json!({ "a" : 1}))
|
||||
}
|
||||
|
||||
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.json(T::invalid())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ use axum::extract::{FromRef, Path, Query};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Form, Json, Router};
|
||||
use hyper::Method;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Url;
|
||||
use reqwest::{Method, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::type_name;
|
||||
use std::net::SocketAddr;
|
||||
@@ -281,6 +280,11 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.route(toml::route::TOML, post(toml::extract_toml))
|
||||
.route(toml::route::TOML_EX, post(toml::extract_toml_ex));
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
let router = router
|
||||
.route(sonic::route::SONIC, post(sonic::extract_sonic))
|
||||
.route(sonic::route::SONIC_EX, post(sonic::extract_sonic_ex));
|
||||
|
||||
let router = router.with_state(state);
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
@@ -616,6 +620,17 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
{
|
||||
use axum_serde::Sonic;
|
||||
test_executor
|
||||
.execute::<Sonic<Parameters>>(Method::POST, sonic::route::SONIC)
|
||||
.await?;
|
||||
test_executor
|
||||
.execute::<Sonic<Parameters>>(Method::POST, sonic::route::SONIC_EX)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -776,7 +791,6 @@ fn validate_again_ex<'v, V: ValidateArgs<'v>>(
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
mod typed_header {
|
||||
|
||||
pub(crate) mod route {
|
||||
pub const TYPED_HEADER: &str = "/typed_header";
|
||||
pub const TYPED_HEADER_EX: &str = "/typed_header_ex";
|
||||
@@ -965,6 +979,7 @@ mod extra {
|
||||
pub const WITH_REJECTION_VALID: &str = "/with_rejection_valid";
|
||||
pub const WITH_REJECTION_VALID_EX: &str = "/with_rejection_valid_ex";
|
||||
}
|
||||
|
||||
pub const PARAMETERS_HEADER: &str = "parameters-header";
|
||||
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
|
||||
|
||||
@@ -1447,3 +1462,32 @@ mod toml {
|
||||
validate_again_ex(parameters, args.get())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
mod sonic {
|
||||
use super::{
|
||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||
ParametersExValidationArguments,
|
||||
};
|
||||
use crate::{Arguments, Valid, ValidEx};
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Sonic;
|
||||
|
||||
pub mod route {
|
||||
pub const SONIC: &str = "/sonic";
|
||||
pub const SONIC_EX: &str = "/sonic_ex";
|
||||
}
|
||||
|
||||
pub async fn extract_sonic(Valid(Sonic(parameters)): Valid<Sonic<Parameters>>) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
pub async fn extract_sonic_ex(
|
||||
ValidEx(Sonic(parameters), args): ValidEx<
|
||||
Sonic<ParametersEx>,
|
||||
ParametersExValidationArguments,
|
||||
>,
|
||||
) -> StatusCode {
|
||||
validate_again_ex(parameters, args.get())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ use axum::extract::{Path, Query};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Form, Json, Router};
|
||||
use hyper::Method;
|
||||
use once_cell::sync::Lazy;
|
||||
#[cfg(feature = "extra_protobuf")]
|
||||
use prost::Message;
|
||||
use reqwest::Url;
|
||||
use reqwest::{Method, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::type_name;
|
||||
use std::net::SocketAddr;
|
||||
@@ -368,6 +367,22 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
post(toml::extract_toml_validified_by_ref),
|
||||
);
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
let router = router
|
||||
.route(sonic::route::SONIC, post(sonic::extract_sonic))
|
||||
.route(
|
||||
sonic::route::SONIC_MODIFIED,
|
||||
post(sonic::extract_sonic_modified),
|
||||
)
|
||||
.route(
|
||||
sonic::route::SONIC_VALIDIFIED,
|
||||
post(sonic::extract_sonic_validified),
|
||||
)
|
||||
.route(
|
||||
sonic::route::SONIC_VALIDIFIED_BY_REF,
|
||||
post(sonic::extract_sonic_validified_by_ref),
|
||||
);
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
let server_addr = listener.local_addr()?;
|
||||
let server = axum::serve(listener, router.into_make_service());
|
||||
@@ -1020,6 +1035,31 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
{
|
||||
use axum_serde::Sonic;
|
||||
|
||||
// Validated
|
||||
test_executor
|
||||
.execute::<Sonic<ParametersValidify>>(Method::POST, sonic::route::SONIC)
|
||||
.await?;
|
||||
// Modified
|
||||
test_executor
|
||||
.execute_modified::<Sonic<ParametersValidify>>(Method::POST, sonic::route::SONIC_MODIFIED)
|
||||
.await?;
|
||||
// Validified
|
||||
test_executor
|
||||
.execute_validified::<Sonic<ParametersValidify>>(
|
||||
Method::POST,
|
||||
sonic::route::SONIC_VALIDIFIED,
|
||||
)
|
||||
.await?;
|
||||
// ValidifiedByRef
|
||||
test_executor
|
||||
.execute::<Sonic<ParametersValidify>>(Method::POST, sonic::route::SONIC_VALIDIFIED_BY_REF)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1309,7 +1349,6 @@ fn check_validified<D: IsModified + Validate>(data: &D) -> StatusCode {
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
mod typed_header {
|
||||
|
||||
pub(crate) mod route {
|
||||
pub const TYPED_HEADER: &str = "/typed_header";
|
||||
pub const TYPED_HEADER_MODIFIED: &str = "/typed_header_modified";
|
||||
@@ -1490,6 +1529,7 @@ mod extra {
|
||||
pub const WITH_REJECTION_VALIDIFY_VALIDIFIED_BY_REF: &str =
|
||||
"/with_rejection_validify_validified_by_ref";
|
||||
}
|
||||
|
||||
pub const PARAMETERS_HEADER: &str = "parameters-header";
|
||||
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
|
||||
|
||||
@@ -1969,6 +2009,7 @@ mod msgpack {
|
||||
) -> StatusCode {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_msgpack_raw(
|
||||
Validated(MsgPackRaw(parameters)): Validated<MsgPackRaw<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
@@ -2071,3 +2112,42 @@ mod toml {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sonic")]
|
||||
mod sonic {
|
||||
use super::{check_modified, check_validated, check_validified, ParametersValidify};
|
||||
use crate::{Modified, Validated, Validified, ValidifiedByRef};
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Sonic;
|
||||
|
||||
pub mod route {
|
||||
pub const SONIC: &str = "/sonic";
|
||||
pub const SONIC_MODIFIED: &str = "/sonic_modified";
|
||||
pub const SONIC_VALIDIFIED: &str = "/sonic_validified";
|
||||
pub const SONIC_VALIDIFIED_BY_REF: &str = "/sonic_validified_by_ref";
|
||||
}
|
||||
|
||||
pub async fn extract_sonic(
|
||||
Validated(Sonic(parameters)): Validated<Sonic<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validated(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_sonic_modified(
|
||||
Modified(Sonic(parameters)): Modified<Sonic<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_modified(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_sonic_validified(
|
||||
Validified(Sonic(parameters)): Validified<Sonic<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_sonic_validified_by_ref(
|
||||
ValidifiedByRef(Sonic(parameters)): ValidifiedByRef<Sonic<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user