From 3adc8d733d9ab403070bd371b8725ebbe0a4ecba Mon Sep 17 00:00:00 2001 From: LT Date: Wed, 29 Sep 2021 00:12:23 +0800 Subject: [PATCH] Add validator example (#352) --- examples/validator/Cargo.toml | 16 ++++++ examples/validator/src/main.rs | 100 +++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 examples/validator/Cargo.toml create mode 100644 examples/validator/src/main.rs diff --git a/examples/validator/Cargo.toml b/examples/validator/Cargo.toml new file mode 100644 index 00000000..4f989f5c --- /dev/null +++ b/examples/validator/Cargo.toml @@ -0,0 +1,16 @@ +[package] +edition = "2018" +name = "example-validator" +publish = false +version = "0.1.0" + +[dependencies] +async-trait = "0.1" +axum = { path = "../.." } +http-body = "0.4.3" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.29" +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = "0.2" +validator = { version = "0.14.0", features = ["derive"] } diff --git a/examples/validator/src/main.rs b/examples/validator/src/main.rs new file mode 100644 index 00000000..e49e6dbd --- /dev/null +++ b/examples/validator/src/main.rs @@ -0,0 +1,100 @@ +//! Run with +//! +//! ```not_rust +//! cargo run -p example-validator +//! +//! curl '127.0.0.1:3000?name=' +//! -> Input validation error: [name: Can not be empty] +//! +//! curl '127.0.0.1:3000?name=LT' +//! ->

Hello, LT!

+//! ``` + +use async_trait::async_trait; +use axum::{ + body::{Bytes, Full}, + extract::{Form, FromRequest, RequestParts}, + handler::get, + http::{Response, StatusCode}, + response::{Html, IntoResponse}, + BoxError, Router, +}; +use serde::{de::DeserializeOwned, Deserialize}; +use std::{convert::Infallible, net::SocketAddr}; +use thiserror::Error; +use validator::Validate; + +#[tokio::main] +async fn main() { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_validator=debug") + } + tracing_subscriber::fmt::init(); + + // build our application with a route + let app = Router::new().route("/", get(handler)); + + // run it + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +#[derive(Debug, Deserialize, Validate)] +pub struct NameInput { + #[validate(length(min = 1, message = "Can not be empty"))] + pub name: String, +} + +async fn handler(ValidatedForm(input): ValidatedForm) -> Html { + Html(format!("

Hello, {}!

", input.name)) +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct ValidatedForm(pub T); + +#[async_trait] +impl FromRequest for ValidatedForm +where + T: DeserializeOwned + Validate, + B: http_body::Body + Send, + B::Data: Send, + B::Error: Into, +{ + type Rejection = ServerError; + + async fn from_request(req: &mut RequestParts) -> Result { + let Form(value) = Form::::from_request(req).await?; + value.validate()?; + Ok(ValidatedForm(value)) + } +} + +#[derive(Debug, Error)] +pub enum ServerError { + #[error(transparent)] + ValidationError(#[from] validator::ValidationErrors), + + #[error(transparent)] + AxumFormRejection(#[from] axum::extract::rejection::FormRejection), +} + +impl IntoResponse for ServerError { + type Body = Full; + type BodyError = Infallible; + + fn into_response(self) -> Response { + match self { + ServerError::ValidationError(_) => { + let message = format!("Input validation error: [{}]", self).replace("\n", ", "); + (StatusCode::BAD_REQUEST, message) + } + ServerError::AxumFormRejection(_) => (StatusCode::BAD_REQUEST, self.to_string()), + } + .into_response() + } +}