use std::convert::Infallible;
use std::fmt;
use std::marker::PhantomData;
use rama::extensions::{Extension, ExtensionsRef};
use rama::http::body::util::BodyExt;
use rama::http::header;
use rama::http::{Body, Request, Response, StatusCode};
use rama::{Layer, Service};
use vld::schema::VldParse;
#[derive(Clone)]
pub struct Validated<T>(pub T);
impl<T: fmt::Debug> fmt::Debug for Validated<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Validated").field(&self.0).finish()
}
}
impl<T> std::ops::Deref for Validated<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: fmt::Debug + Send + Sync + 'static> Extension for Validated<T> {}
#[derive(Clone)]
pub struct ValidateJsonLayer<T> {
_marker: PhantomData<fn() -> T>,
}
impl<T> ValidateJsonLayer<T> {
#[must_use]
pub fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<T> Default for ValidateJsonLayer<T> {
fn default() -> Self {
Self::new()
}
}
impl<S, T> Layer<S> for ValidateJsonLayer<T> {
type Service = ValidateJsonService<S, T>;
fn layer(&self, inner: S) -> Self::Service {
ValidateJsonService {
inner,
_marker: PhantomData,
}
}
}
#[derive(Clone)]
pub struct ValidateJsonService<S, T> {
inner: S,
_marker: PhantomData<fn() -> T>,
}
impl<S, T> Service<Request> for ValidateJsonService<S, T>
where
S: Service<Request, Output = Response, Error = Infallible>,
T: VldParse + Clone + fmt::Debug + Send + Sync + 'static,
{
type Output = Response;
type Error = Infallible;
async fn serve(&self, req: Request) -> Result<Self::Output, Self::Error> {
let is_json = req
.headers()
.get(header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.map(|ct| {
let ct = ct.to_ascii_lowercase();
ct.starts_with("application/json") || ct.contains("+json")
})
.unwrap_or(false);
let (parts, body) = req.into_parts();
let bytes = match body.collect().await {
Ok(collected) => collected.to_bytes(),
Err(_) => {
let error_body = vld_http_common::format_generic_error("Failed to read body");
return Ok(json_response(StatusCode::BAD_REQUEST, &error_body));
}
};
if !is_json {
let new_req = Request::from_parts(parts, Body::from(bytes));
return self.inner.serve(new_req).await;
}
let json_value: serde_json::Value = match serde_json::from_slice(&bytes) {
Ok(v) => v,
Err(e) => {
let error_body = vld_http_common::format_json_parse_error(&e.to_string());
return Ok(json_response(StatusCode::BAD_REQUEST, &error_body));
}
};
match T::vld_parse_value(&json_value) {
Ok(validated) => {
let new_req = Request::from_parts(parts, Body::from(bytes));
new_req.extensions().insert(Validated(validated));
self.inner.serve(new_req).await
}
Err(vld_err) => {
let error_body = vld_http_common::format_vld_error(&vld_err);
Ok(json_response(StatusCode::UNPROCESSABLE_ENTITY, &error_body))
}
}
}
}
fn json_response(status: StatusCode, body: &serde_json::Value) -> Response {
use rama::http::service::web::response::IntoResponse;
(
status,
[(header::CONTENT_TYPE, "application/json")],
serde_json::to_string(body).unwrap_or_else(|_| "{}".into()),
)
.into_response()
}
#[must_use]
pub fn validated<T: Clone + fmt::Debug + Send + Sync + 'static>(req: &Request) -> T {
req.extensions()
.get_ref::<Validated<T>>()
.expect(
"vld-rama: Validated<T> not found in request extensions. \
Make sure ValidateJsonLayer is applied.",
)
.0
.clone()
}
#[must_use]
pub fn try_validated<T: Clone + fmt::Debug + Send + Sync + 'static>(req: &Request) -> Option<T> {
req.extensions()
.get_ref::<Validated<T>>()
.map(|v| v.0.clone())
}