use crate::{domain::Command, i18n};
use axum::{
body::Bytes,
extract::{FromRequest, Json, Request},
http::{StatusCode, header::ACCEPT_LANGUAGE},
response::{IntoResponse, Response},
};
use rkyv::{
Archive, Deserialize,
bytecheck::CheckBytes,
de::Pool,
rancor::{Error, Strategy},
util::AlignedVec,
validation::{Validator, archive::ArchiveValidator, shared::SharedValidator},
};
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use uuid::Uuid;
use validator::Validate;
#[derive(serde::Deserialize)]
pub struct UniKey {
pub agg_id: Uuid,
pub com_id: Uuid,
}
pub struct JsonFormat;
pub struct RkyvFormat;
pub struct UniCommand<T, F>(pub T, pub String, pub PhantomData<F>);
impl<T, S> FromRequest<S> for UniCommand<T, RkyvFormat>
where
T: Command + Validate,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, Error>>,
<T as Archive>::Archived:
for<'m> CheckBytes<Strategy<Validator<ArchiveValidator<'m>, SharedValidator>, Error>>,
Bytes: FromRequest<S>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
let lang = extract_language(&req);
let bytes = Bytes::from_request(req, state)
.await
.map_err(|e| e.into_response())?;
let required_align = std::mem::align_of::<T::Archived>();
let com = match bytes.as_ptr().align_offset(required_align) {
0 => rkyv::from_bytes::<T, Error>(&bytes)
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()).into_response())?,
_ => {
let mut aligned = AlignedVec::<16>::with_capacity(bytes.len());
aligned.extend_from_slice(&bytes);
rkyv::from_bytes::<T, Error>(&aligned)
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()).into_response())?
}
};
com.validate().map_err(|e| {
let mut result = String::new();
let err = match i18n::validation(&mut result, &e, &lang) {
Ok(()) => result,
Err(_) => e.to_string(),
};
(StatusCode::BAD_REQUEST, err).into_response()
})?;
Ok(UniCommand(com, lang, PhantomData))
}
}
impl<T, S> FromRequest<S> for UniCommand<T, JsonFormat>
where
T: Command + Validate + DeserializeOwned,
Bytes: FromRequest<S>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
let lang = extract_language(&req);
let bytes = Bytes::from_request(req, state)
.await
.map_err(|e| e.into_response())?;
let Json(com) = Json::<T>::from_bytes(&bytes).map_err(|e| e.into_response())?;
com.validate().map_err(|e| {
let mut result = String::new();
let err = match i18n::validation(&mut result, &e, &lang) {
Ok(()) => result,
Err(_) => e.to_string(),
};
(StatusCode::BAD_REQUEST, err).into_response()
})?;
Ok(UniCommand(com, lang, PhantomData))
}
}
fn extract_language(req: &Request) -> String {
req.headers()
.get(ACCEPT_LANGUAGE)
.and_then(|v| v.to_str().ok())
.and_then(|ls| ls.split(',').next())
.and_then(|l| l.split(';').next())
.and_then(|t| t.split('-').next())
.unwrap_or("zh")
.to_string()
}