use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest, HttpResponse, ResponseError};
use std::fmt;
use std::future::Future;
use std::pin::Pin;
#[derive(Debug)]
pub struct VldJsonError {
error: vld::error::VldError,
}
impl fmt::Display for VldJsonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Validation failed: {}", self.error)
}
}
impl ResponseError for VldJsonError {
fn error_response(&self) -> HttpResponse {
let body = vld_http_common::format_vld_error(&self.error);
HttpResponse::UnprocessableEntity()
.content_type("application/json")
.body(body.to_string())
}
}
pub struct VldJson<T>(pub T);
impl<T> std::ops::Deref for VldJson<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldJson<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldJson<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let json_fut = actix_web::web::Json::<serde_json::Value>::from_request(req, payload);
Box::pin(async move {
let json_value = json_fut.await.map_err(|e| VldJsonError {
error: vld::error::VldError::single(
vld::error::IssueCode::ParseError,
format!("JSON parse error: {}", e),
),
})?;
let parsed = T::vld_parse_value(&json_value).map_err(|error| VldJsonError { error })?;
Ok(VldJson(parsed))
})
}
}
pub struct VldQuery<T>(pub T);
impl<T> std::ops::Deref for VldQuery<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldQuery<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldQuery<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let query_string = req.query_string().to_owned();
Box::pin(async move {
let value = query_string_to_json(&query_string);
let parsed = T::vld_parse_value(&value).map_err(|error| VldJsonError { error })?;
Ok(VldQuery(parsed))
})
}
}
pub struct VldPath<T>(pub T);
impl<T> std::ops::Deref for VldPath<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldPath<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldPath<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let mut map = serde_json::Map::new();
if let Some(pattern) = req.match_pattern() {
for name in extract_path_param_names(&pattern) {
if let Some(value) = req.match_info().get(&name) {
map.insert(name, coerce_value(value));
}
}
}
let value = serde_json::Value::Object(map);
Box::pin(async move {
let parsed = T::vld_parse_value(&value).map_err(|error| VldJsonError { error })?;
Ok(VldPath(parsed))
})
}
}
pub struct VldForm<T>(pub T);
impl<T> std::ops::Deref for VldForm<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldForm<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldForm<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let bytes_fut = actix_web::web::Bytes::from_request(req, payload);
Box::pin(async move {
let body = bytes_fut.await.map_err(|e| VldJsonError {
error: vld::error::VldError::single(
vld::error::IssueCode::ParseError,
format!("Failed to read form body: {}", e),
),
})?;
let body_str = std::str::from_utf8(&body).map_err(|_| VldJsonError {
error: vld::error::VldError::single(
vld::error::IssueCode::ParseError,
"Form body is not valid UTF-8",
),
})?;
let value = query_string_to_json(body_str);
let parsed = T::vld_parse_value(&value).map_err(|error| VldJsonError { error })?;
Ok(VldForm(parsed))
})
}
}
pub struct VldHeaders<T>(pub T);
impl<T> std::ops::Deref for VldHeaders<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldHeaders<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldHeaders<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let value = headers_to_json(req.headers());
Box::pin(async move {
let parsed = T::vld_parse_value(&value).map_err(|error| VldJsonError { error })?;
Ok(VldHeaders(parsed))
})
}
}
pub struct VldCookie<T>(pub T);
impl<T> std::ops::Deref for VldCookie<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for VldCookie<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: vld::schema::VldParse> FromRequest for VldCookie<T> {
type Error = VldJsonError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let cookie_header = req
.headers()
.get(actix_web::http::header::COOKIE)
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_owned();
Box::pin(async move {
let value = cookies_to_json(&cookie_header);
let parsed = T::vld_parse_value(&value).map_err(|error| VldJsonError { error })?;
Ok(VldCookie(parsed))
})
}
}
use vld_http_common::{
coerce_value, cookies_to_json, extract_path_param_names, query_string_to_json,
};
fn headers_to_json(headers: &actix_web::http::header::HeaderMap) -> serde_json::Value {
let mut map = serde_json::Map::new();
for (name, value) in headers.iter() {
let key = name.as_str().replace('-', "_");
if let Ok(v) = value.to_str() {
map.insert(key, coerce_value(v));
}
}
serde_json::Value::Object(map)
}
pub mod prelude {
pub use crate::{VldCookie, VldForm, VldHeaders, VldJson, VldJsonError, VldPath, VldQuery};
pub use vld::prelude::*;
}