use std::fmt::{self, Display, Formatter};
use std::str::from_utf8;
use rocket::data::ToByteUnit;
use rocket::form::{self, name::NameView, DataField, FromFormField, ValueField};
#[derive(serde::Deserialize, serde::Serialize, rocket::FromFormField, Debug, PartialEq, Eq)]
pub enum SortDirection {
#[serde(rename = "asc")]
Asc,
#[serde(rename = "desc")]
Desc,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SortSpec<T> {
pub field: T,
pub direction: SortDirection,
}
impl<T: Display> Display for SortSpec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.direction == SortDirection::Desc {
write!(f, "-")?;
}
write!(f, "{}", self.field)
}
}
impl<'v, T: FromFormField<'v>> SortSpec<T> {
fn from_str(mut data: &'v str, name: &NameView<'v>) -> form::Result<'v, SortSpec<T>> {
let mut direction = SortDirection::Asc;
if data.starts_with('-') {
data = data.trim_start_matches('-');
direction = SortDirection::Desc;
} else if data.starts_with('+') {
data = data.trim_start_matches('+');
}
data = data.trim_start_matches(' ');
let field = FromFormField::from_value(ValueField {
name: *name,
value: data,
})?;
Ok(SortSpec { field, direction })
}
}
#[rocket::async_trait]
impl<'v, T: FromFormField<'v>> FromFormField<'v> for SortSpec<T> {
fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
SortSpec::from_str(field.value, &field.name)
}
async fn from_data(field: DataField<'v, '_>) -> form::Result<'v, Self> {
let limit = 256.kibibytes();
let bytes = field.data.open(limit).into_bytes().await?;
if !bytes.is_complete() {
return Err((None, Some(limit)).into());
}
let bytes = bytes.into_inner();
let bytes = rocket::request::local_cache!(field.request, bytes);
let data = from_utf8(bytes)?;
SortSpec::from_str(data, &field.name)
}
fn default() -> Option<Self> {
None
}
}