update to 0.5.0

This commit is contained in:
gengteng
2023-08-04 18:50:02 +08:00
parent f3f0171e72
commit d95e04b4d4
19 changed files with 836 additions and 292 deletions

71
src/extra.rs Normal file
View File

@@ -0,0 +1,71 @@
//! # Implementation of the `HasValidate` trait for the extractor in `axum-extra`.
//!
#[cfg(feature = "extra_form")]
pub mod form;
#[cfg(feature = "extra_protobuf")]
pub mod protobuf;
#[cfg(feature = "extra_query")]
pub mod query;
use crate::HasValidate;
use axum_extra::extract::{Cached, WithRejection};
use validator::Validate;
impl<T: Validate> HasValidate for Cached<T> {
type Validate = T;
fn get_validate(&self) -> &Self::Validate {
&self.0
}
}
impl<T: Validate, R> HasValidate for WithRejection<T, R> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(test)]
mod tests {
use crate::tests::{Rejection, ValidTest};
use axum::http::StatusCode;
use axum_extra::extract::{Cached, WithRejection};
use reqwest::RequestBuilder;
impl<T: ValidTest> ValidTest for Cached<T> {
const ERROR_STATUS_CODE: StatusCode = T::ERROR_STATUS_CODE;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_valid_request(builder)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
// cached never fails
T::set_error_request(builder)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_invalid_request(builder)
}
}
impl<T: ValidTest, R: Rejection> ValidTest for WithRejection<T, R> {
// just use conflict to test
const ERROR_STATUS_CODE: StatusCode = R::STATUS_CODE;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_valid_request(builder)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
// cached never fails
T::set_error_request(builder)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
T::set_invalid_request(builder)
}
}
}

13
src/extra/form.rs Normal file
View File

@@ -0,0 +1,13 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor in `axum-extra`.
//!
use crate::HasValidate;
use axum_extra::extract::Form;
use validator::Validate;
impl<T: Validate> HasValidate for Form<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}

13
src/extra/protobuf.rs Normal file
View File

@@ -0,0 +1,13 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor.
//!
use crate::HasValidate;
use axum_extra::protobuf::Protobuf;
use validator::Validate;
impl<T: Validate> HasValidate for Protobuf<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}

13
src/extra/query.rs Normal file
View File

@@ -0,0 +1,13 @@
//! # Implementation of the `HasValidate` trait for the `Query` extractor in `axum-extra`.
//!
use crate::HasValidate;
use axum_extra::extract::Query;
use validator::Validate;
impl<T: Validate> HasValidate for Query<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}

View File

@@ -1,21 +1,37 @@
//! # Implementation of the `HasValidate` trait for the `Form` extractor.
//!
use crate::{HasValidate, ValidRejection};
use axum::extract::rejection::FormRejection;
use crate::HasValidate;
use axum::Form;
use validator::Validate;
impl<T: Validate> HasValidate for Form<T> {
type Validate = T;
type Rejection = FormRejection;
fn get_validate(&self) -> &T {
&self.0
}
}
impl From<FormRejection> for ValidRejection<FormRejection> {
fn from(value: FormRejection) -> Self {
Self::Inner(value)
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::http::StatusCode;
use axum::Form;
use reqwest::RequestBuilder;
impl<T: ValidTestParameter> ValidTest for Form<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder.form(T::valid())
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
builder.form(T::error())
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder.form(T::invalid())
}
}
}

View File

@@ -1,21 +1,37 @@
//! # Implementation of the `HasValidate` trait for the `Json` extractor.
//!
use crate::{HasValidate, ValidRejection};
use axum::extract::rejection::JsonRejection;
use crate::HasValidate;
use axum::Json;
use validator::Validate;
impl<T: Validate> HasValidate for Json<T> {
type Validate = T;
type Rejection = JsonRejection;
fn get_validate(&self) -> &T {
&self.0
}
}
impl From<JsonRejection> for ValidRejection<JsonRejection> {
fn from(value: JsonRejection) -> Self {
Self::Inner(value)
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::http::StatusCode;
use axum::Json;
use reqwest::RequestBuilder;
impl<T: ValidTestParameter> ValidTest for Json<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(T::error())
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder.json(T::invalid())
}
}
}

View File

@@ -1,19 +1,30 @@
#![doc = include_str!("../README.md")]
#![deny(unsafe_code, missing_docs, clippy::unwrap_used)]
#[cfg(feature = "extra")]
pub mod extra;
#[cfg(feature = "form")]
pub mod form;
#[cfg(feature = "json")]
pub mod json;
#[cfg(feature = "msgpack")]
pub mod msgpack;
pub mod path;
#[cfg(feature = "query")]
pub mod query;
#[cfg(test)]
pub mod test;
#[cfg(feature = "typed_header")]
pub mod typed_header;
#[cfg(feature = "yaml")]
pub mod yaml;
use axum::async_trait;
use axum::extract::{FromRequest, FromRequestParts};
use axum::http::request::Parts;
use axum::http::{Request, StatusCode};
use axum::response::{IntoResponse, Response};
use std::ops::{Deref, DerefMut};
use validator::{Validate, ValidationErrors};
/// Http status code returned when there are validation errors.
@@ -25,7 +36,21 @@ pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::BAD_REQUEST;
/// Valid entity extractor
#[derive(Debug, Clone, Copy, Default)]
pub struct Valid<T>(pub T);
pub struct Valid<E>(pub E);
impl<E> Deref for Valid<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<E> DerefMut for Valid<E> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// If the valid extractor fails it'll use this "rejection" type.
/// This rejection type can be converted into a response.
@@ -64,44 +89,84 @@ impl<E: IntoResponse> IntoResponse for ValidRejection<E> {
pub trait HasValidate {
/// Inner type that can be validated for correctness
type Validate: Validate;
/// If the inner extractor fails it'll use this "rejection" type.
/// A rejection is a kind of error that can be converted into a response.
type Rejection: IntoResponse;
/// get the inner type
/// Get the inner value
fn get_validate(&self) -> &Self::Validate;
}
#[async_trait]
impl<S, B, T> FromRequest<S, B> for Valid<T>
impl<S, B, E> FromRequest<S, B> for Valid<E>
where
S: Send + Sync + 'static,
B: Send + Sync + 'static,
T: HasValidate + FromRequest<S, B>,
T::Validate: Validate,
ValidRejection<<T as HasValidate>::Rejection>: From<<T as FromRequest<S, B>>::Rejection>,
E: HasValidate + FromRequest<S, B>,
E::Validate: Validate,
{
type Rejection = ValidRejection<<T as HasValidate>::Rejection>;
type Rejection = ValidRejection<<E as FromRequest<S, B>>::Rejection>;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let inner = T::from_request(req, state).await?;
let inner = E::from_request(req, state)
.await
.map_err(ValidRejection::Inner)?;
inner.get_validate().validate()?;
Ok(Valid(inner))
}
}
#[async_trait]
impl<S, T> FromRequestParts<S> for Valid<T>
impl<S, E> FromRequestParts<S> for Valid<E>
where
S: Send + Sync + 'static,
T: HasValidate + FromRequestParts<S>,
T::Validate: Validate,
ValidRejection<<T as HasValidate>::Rejection>: From<<T as FromRequestParts<S>>::Rejection>,
E: HasValidate + FromRequestParts<S>,
E::Validate: Validate,
{
type Rejection = ValidRejection<<T as HasValidate>::Rejection>;
type Rejection = ValidRejection<<E as FromRequestParts<S>>::Rejection>;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let inner = T::from_request_parts(parts, state).await?;
let inner = E::from_request_parts(parts, state)
.await
.map_err(ValidRejection::Inner)?;
inner.get_validate().validate()?;
Ok(Valid(inner))
}
}
#[cfg(test)]
pub mod tests {
use reqwest::{RequestBuilder, StatusCode};
use serde::Serialize;
/// # Valid test parameter
pub trait ValidTestParameter: Serialize + 'static {
/// Create a valid parameter
fn valid() -> &'static Self;
/// Create an error serializable array
fn error() -> &'static [(&'static str, &'static str)];
/// Create a invalid parameter
fn invalid() -> &'static Self;
}
/// # Valid Tests
///
/// This trait defines three test cases to check
/// if an extractor combined with the Valid type works properly.
///
/// 1. For a valid request, the server should return `200 OK`.
/// 2. For an invalid request according to the extractor, the server should return the error HTTP status code defined by the extractor itself.
/// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code.
///
pub trait ValidTest {
/// Http status code when inner extractor failed
const ERROR_STATUS_CODE: StatusCode;
/// Build a valid request, the server should return `200 OK`.
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder;
/// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE`
fn set_error_request(builder: RequestBuilder) -> RequestBuilder;
/// Build an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder;
}
#[cfg(feature = "extra")]
pub trait Rejection {
const STATUS_CODE: StatusCode;
}
}

20
src/msgpack.rs Normal file
View File

@@ -0,0 +1,20 @@
//! # Implementation of the `HasValidate` trait for the `MsgPack` extractor.
//!
use crate::HasValidate;
use axum_msgpack::{MsgPack, MsgPackRaw};
use validator::Validate;
impl<T: Validate> HasValidate for MsgPack<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
impl<T: Validate> HasValidate for MsgPackRaw<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}

View File

@@ -1,21 +1,13 @@
//! # Implementation of the `HasValidate` trait for the `Path` extractor.
//!
use crate::{HasValidate, ValidRejection};
use axum::extract::rejection::PathRejection;
use crate::HasValidate;
use axum::extract::Path;
use validator::Validate;
impl<T: Validate> HasValidate for Path<T> {
type Validate = T;
type Rejection = PathRejection;
fn get_validate(&self) -> &T {
&self.0
}
}
impl From<PathRejection> for ValidRejection<PathRejection> {
fn from(value: PathRejection) -> Self {
Self::Inner(value)
}
}

View File

@@ -1,21 +1,37 @@
//! # Implementation of the `HasValidate` trait for the `Query` extractor.
//!
use crate::{HasValidate, ValidRejection};
use axum::extract::rejection::QueryRejection;
use crate::HasValidate;
use axum::extract::Query;
use validator::Validate;
impl<T: Validate> HasValidate for Query<T> {
type Validate = T;
type Rejection = QueryRejection;
fn get_validate(&self) -> &T {
&self.0
}
}
impl From<QueryRejection> for ValidRejection<QueryRejection> {
fn from(value: QueryRejection) -> Self {
Self::Inner(value)
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::extract::Query;
use axum::http::StatusCode;
use reqwest::RequestBuilder;
impl<T: ValidTestParameter> ValidTest for Query<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::BAD_REQUEST;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder.query(&T::valid())
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
builder.query(T::error())
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder.query(&T::invalid())
}
}
}

430
src/test.rs Normal file
View File

@@ -0,0 +1,430 @@
use crate::tests::{ValidTest, ValidTestParameter};
use crate::{Valid, VALIDATION_ERROR_STATUS};
use axum::extract::{Path, Query};
use axum::routing::{get, post};
use axum::{Form, Json, Router};
use hyper::Method;
use reqwest::{StatusCode, Url};
use serde::{Deserialize, Serialize};
use std::any::type_name;
use std::borrow::Cow;
use std::net::SocketAddr;
use validator::Validate;
#[derive(Debug, Clone, Deserialize, Serialize, Validate, Eq, PartialEq)]
pub struct Parameters {
#[validate(range(min = 5, max = 10))]
v0: i32,
#[validate(length(min = 1, max = 10))]
v1: Cow<'static, str>,
}
static VALID_PARAMETERS: Parameters = Parameters {
v0: 5,
v1: Cow::Borrowed("0123456789"),
};
static INVALID_PARAMETERS: Parameters = Parameters {
v0: 6,
v1: Cow::Borrowed("01234567890"),
};
impl ValidTestParameter for Parameters {
fn valid() -> &'static Self {
&VALID_PARAMETERS
}
fn error() -> &'static [(&'static str, &'static str)] {
&[("not_v0_or_v1", "value")]
}
fn invalid() -> &'static Self {
&INVALID_PARAMETERS
}
}
#[tokio::test]
async fn test_main() -> anyhow::Result<()> {
let router = Router::new()
.route(route::PATH, get(extract_path))
.route(route::QUERY, get(extract_query))
.route(route::FORM, post(extract_form))
.route(route::JSON, post(extract_json));
#[cfg(feature = "typed_header")]
let router = router.route(
typed_header::route::TYPED_HEADER,
post(typed_header::extract_typed_header),
);
#[cfg(feature = "extra")]
let router = router
.route(route::extra::CACHED, post(extra::extract_cached))
.route(
route::extra::WITH_REJECTION,
post(extra::extract_with_rejection),
);
let server = axum::Server::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16)))
.serve(router.into_make_service());
let server_addr = server.local_addr();
println!("Axum server address: {}.", server_addr);
let (server_guard, close) = tokio::sync::oneshot::channel::<()>();
let server_handle = tokio::spawn(server.with_graceful_shutdown(async move {
let _ = close.await;
}));
let server_url = format!("http://{}", server_addr);
let test_executor = TestExecutor::from(Url::parse(&format!("http://{}", server_addr))?);
// Valid<Path<...>>
let valid_path_response = test_executor
.client()
.get(format!(
"{}/path/{}/{}",
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
))
.send()
.await?;
assert_eq!(valid_path_response.status(), StatusCode::OK);
let invalid_path_response = test_executor
.client()
.get(format!("{}/path/not_i32/path", server_url))
.send()
.await?;
assert_eq!(invalid_path_response.status(), StatusCode::BAD_REQUEST);
let invalid_path_response = test_executor
.client()
.get(format!(
"{}/path/{}/{}",
server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1
))
.send()
.await?;
assert_eq!(invalid_path_response.status(), VALIDATION_ERROR_STATUS);
#[cfg(feature = "into_json")]
check_json(invalid_path_response).await;
println!("Valid<Path<...>> works.");
test_executor
.execute::<Query<Parameters>>(Method::GET, route::QUERY)
.await?;
test_executor
.execute::<Form<Parameters>>(Method::POST, route::FORM)
.await?;
test_executor
.execute::<Json<Parameters>>(Method::POST, route::JSON)
.await?;
#[cfg(feature = "typed_header")]
{
use axum::TypedHeader;
test_executor
.execute::<TypedHeader<Parameters>>(Method::POST, typed_header::route::TYPED_HEADER)
.await?;
}
#[cfg(feature = "extra")]
{
use axum_extra::extract::{Cached, WithRejection};
use extra::TestRejection;
test_executor
.execute::<Cached<Parameters>>(Method::POST, route::extra::CACHED)
.await?;
test_executor
.execute::<WithRejection<Parameters, TestRejection>>(
Method::POST,
route::extra::WITH_REJECTION,
)
.await?;
}
drop(server_guard);
server_handle.await??;
Ok(())
}
#[derive(Debug, Clone)]
pub struct TestExecutor {
client: reqwest::Client,
server_url: Url,
}
impl From<Url> for TestExecutor {
fn from(server_url: Url) -> Self {
Self {
client: Default::default(),
server_url,
}
}
}
impl TestExecutor {
/// Execute all tests
pub async fn execute<T: ValidTest>(&self, method: Method, route: &str) -> anyhow::Result<()> {
let url = {
let mut url_builder = self.server_url.clone();
url_builder.set_path(route);
url_builder
};
let valid_builder = self.client.request(method.clone(), url.clone());
let valid_response = T::set_valid_request(valid_builder).send().await?;
assert_eq!(valid_response.status(), StatusCode::OK);
let error_builder = self.client.request(method.clone(), url.clone());
let error_response = T::set_error_request(error_builder).send().await?;
assert_eq!(error_response.status(), T::ERROR_STATUS_CODE);
let invalid_builder = self.client.request(method, url);
let invalid_response = T::set_invalid_request(invalid_builder).send().await?;
assert_eq!(invalid_response.status(), VALIDATION_ERROR_STATUS);
#[cfg(feature = "into_json")]
check_json(invalid_response).await;
println!("{} works.", type_name::<T>());
Ok(())
}
pub fn client(&self) -> &reqwest::Client {
&self.client
}
}
/// Check if the response is a json response
#[cfg(feature = "into_json")]
pub async fn check_json(response: reqwest::Response) {
assert_eq!(
response.headers()[axum::http::header::CONTENT_TYPE],
axum::http::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref())
);
assert!(response.json::<serde_json::Value>().await.is_ok());
}
mod route {
pub const PATH: &str = "/path/:v0/:v1";
pub const QUERY: &str = "/query";
pub const FORM: &str = "/form";
pub const JSON: &str = "/json";
#[cfg(feature = "extra")]
pub mod extra {
pub const CACHED: &str = "/cached";
pub const WITH_REJECTION: &str = "/with_rejection";
}
}
async fn extract_path(Valid(Path(parameters)): Valid<Path<Parameters>>) -> StatusCode {
validate_again(parameters)
}
async fn extract_query(Valid(Query(parameters)): Valid<Query<Parameters>>) -> StatusCode {
validate_again(parameters)
}
async fn extract_form(Valid(Form(parameters)): Valid<Form<Parameters>>) -> StatusCode {
validate_again(parameters)
}
async fn extract_json(Valid(Json(parameters)): Valid<Json<Parameters>>) -> StatusCode {
validate_again(parameters)
}
fn validate_again(parameters: Parameters) -> StatusCode {
// The `Valid` extractor has validated the `parameters` once,
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
// Let's validate them again to check if the `Valid` extractor works well.
// If it works properly, this function will never return `500 INTERNAL SERVER ERROR`
match parameters.validate() {
Ok(_) => StatusCode::OK,
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[cfg(feature = "typed_header")]
mod typed_header {
pub(crate) mod route {
pub const TYPED_HEADER: &str = "/typedHeader";
}
use super::{validate_again, Parameters};
use crate::Valid;
use axum::headers::{Error, Header, HeaderName, HeaderValue};
use axum::http::StatusCode;
use axum::TypedHeader;
use std::borrow::Cow;
pub static AXUM_VALID_PARAMETERS: HeaderName = HeaderName::from_static("axum-valid-parameters");
pub(super) async fn extract_typed_header(
Valid(TypedHeader(parameters)): Valid<TypedHeader<Parameters>>,
) -> StatusCode {
validate_again(parameters)
}
impl Header for Parameters {
fn name() -> &'static HeaderName {
&AXUM_VALID_PARAMETERS
}
fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
where
Self: Sized,
I: Iterator<Item = &'i HeaderValue>,
{
let value = values.next().ok_or_else(Error::invalid)?;
let src = std::str::from_utf8(value.as_bytes()).map_err(|_| Error::invalid())?;
let split = src.split(',').collect::<Vec<_>>();
match split.as_slice() {
[v0, v1] => Ok(Parameters {
v0: v0.parse().map_err(|_| Error::invalid())?,
v1: Cow::Owned(v1.to_string()),
}),
_ => Err(Error::invalid()),
}
}
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
let v0 = self.v0.to_string();
let mut vec = Vec::with_capacity(v0.len() + 1 + self.v1.len());
vec.extend_from_slice(v0.as_bytes());
vec.push(b',');
vec.extend_from_slice(self.v1.as_bytes());
let value = HeaderValue::from_bytes(&vec).expect("Failed to build header");
values.extend(::std::iter::once(value));
}
}
#[test]
fn parameter_is_header() -> anyhow::Result<()> {
let parameter = Parameters {
v0: 123456,
v1: Cow::Owned("111111".to_string()),
};
let mut vec = Vec::new();
parameter.encode(&mut vec);
let mut iter = vec.iter();
assert_eq!(parameter, Parameters::decode(&mut iter)?);
Ok(())
}
}
#[cfg(feature = "extra")]
mod extra {
use crate::test::{validate_again, Parameters};
use crate::tests::{Rejection, ValidTest, ValidTestParameter};
use crate::Valid;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum_extra::extract::{Cached, WithRejection};
use reqwest::RequestBuilder;
pub const PARAMETERS_HEADER: &str = "parameters-header";
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
// 1.2. Define you own `Rejection` type and implement `IntoResponse` for it.
pub enum ParametersRejection {
Null,
InvalidJson(serde_json::error::Error),
}
impl IntoResponse for ParametersRejection {
fn into_response(self) -> Response {
match self {
ParametersRejection::Null => {
(CACHED_REJECTION_STATUS, "My-Data header is missing").into_response()
}
ParametersRejection::InvalidJson(e) => (
CACHED_REJECTION_STATUS,
format!("My-Data is not valid json string: {e}"),
)
.into_response(),
}
}
}
// 1.3. Implement your extractor (`FromRequestParts` or `FromRequest`)
#[axum::async_trait]
impl<S> FromRequestParts<S> for Parameters
where
S: Send + Sync,
{
type Rejection = ParametersRejection;
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
let Some(value) = parts.headers.get(PARAMETERS_HEADER) else {
return Err(ParametersRejection::Null);
};
serde_json::from_slice(value.as_bytes()).map_err(ParametersRejection::InvalidJson)
}
}
impl ValidTest for Parameters {
const ERROR_STATUS_CODE: StatusCode = CACHED_REJECTION_STATUS;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder.header(
PARAMETERS_HEADER,
serde_json::to_string(Parameters::valid()).expect("Failed to serialize parameters"),
)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
builder.header(
PARAMETERS_HEADER,
serde_json::to_string(Parameters::error()).expect("Failed to serialize parameters"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder.header(
PARAMETERS_HEADER,
serde_json::to_string(Parameters::invalid())
.expect("Failed to serialize parameters"),
)
}
}
pub struct TestRejection {
_inner: ParametersRejection,
}
impl Rejection for TestRejection {
const STATUS_CODE: StatusCode = StatusCode::CONFLICT;
}
impl IntoResponse for TestRejection {
fn into_response(self) -> Response {
Self::STATUS_CODE.into_response()
}
}
// satisfy the `WithRejection`'s extractor trait bound
// R: From<E::Rejection> + IntoResponse
impl From<ParametersRejection> for TestRejection {
fn from(_inner: ParametersRejection) -> Self {
Self { _inner }
}
}
pub async fn extract_cached(
Valid(Cached(parameters)): Valid<Cached<Parameters>>,
) -> StatusCode {
validate_again(parameters)
}
pub async fn extract_with_rejection(
Valid(WithRejection(parameters, _)): Valid<WithRejection<Parameters, TestRejection>>,
) -> StatusCode {
validate_again(parameters)
}
}

43
src/typed_header.rs Normal file
View File

@@ -0,0 +1,43 @@
//! # Implementation of the `HasValidate` trait for the `TypedHeader` extractor.
//!
use crate::HasValidate;
use axum::TypedHeader;
use validator::Validate;
impl<T: Validate> HasValidate for TypedHeader<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::headers::{Header, HeaderMapExt};
use axum::http::StatusCode;
use axum::TypedHeader;
use reqwest::header::HeaderMap;
use reqwest::RequestBuilder;
impl<T: ValidTestParameter + Header + Clone> ValidTest for TypedHeader<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::BAD_REQUEST;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
let mut headers = HeaderMap::default();
headers.typed_insert(T::valid().clone());
builder.headers(headers)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
builder
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
let mut headers = HeaderMap::default();
headers.typed_insert(T::invalid().clone());
builder.headers(headers)
}
}
}

13
src/yaml.rs Normal file
View File

@@ -0,0 +1,13 @@
//! # Implementation of the `HasValidate` trait for the `Yaml` extractor.
//!
use crate::HasValidate;
use axum_yaml::Yaml;
use validator::Validate;
impl<T: Validate> HasValidate for Yaml<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}