use std::cell::RefCell;
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::fmt;
use std::io;
use bytes::{Bytes, BytesMut};
use futures_util::StreamExt;
use futures_util::stream::BoxStream;
use primitives_rs::tsgen::{self, TsGen, TypeDef, TypePhase, TypeRef, TypeRegistry};
use serde::de::value::{MapDeserializer, SeqDeserializer, StringDeserializer};
use serde::de::{self, DeserializeOwned, Deserializer, IntoDeserializer, Visitor};
use serde::{Deserialize, forward_to_deserialize_any};
use tempfile::NamedTempFile;
use tokio::io::AsyncWriteExt;
use crate::media_type::FormMediaType;
#[doc(hidden)]
pub type FormBody = BoxStream<'static, Result<Bytes, Box<dyn StdError + Send + Sync>>>;
#[derive(Clone, Debug)]
pub struct FormInput<Fields>(pub Fields);
impl<Fields: TsGen> TsGen for FormInput<Fields> {
fn type_ref() -> TypeRef {
Fields::type_ref()
}
fn collect_type_defs(registry: &mut TypeRegistry) -> tsgen::Result<()> {
collect_form_type_defs::<Fields>(TypePhase::Deserialize, registry)
}
fn type_ref_for(phase: TypePhase) -> TypeRef {
Fields::type_ref_for(phase)
}
fn collect_type_defs_for(phase: TypePhase, registry: &mut TypeRegistry) -> tsgen::Result<()> {
collect_form_type_defs::<Fields>(phase, registry)
}
}
fn collect_form_type_defs<Fields: TsGen>(
phase: TypePhase,
registry: &mut TypeRegistry,
) -> tsgen::Result<()> {
let mut collected = TypeRegistry::default();
Fields::collect_type_defs_for(phase, &mut collected)?;
let defs = collected.into_defs();
check_form_fields(&Fields::type_ref_for(phase), &defs).map_err(tsgen::Error::new)?;
for def in defs {
registry.try_define(def)?;
}
Ok(())
}
fn resolve_form_type<'a>(mut type_ref: &'a TypeRef, defs: &'a [TypeDef]) -> &'a TypeRef {
for _ in 0..=defs.len() {
let TypeRef::Named { key, .. } = type_ref else {
break;
};
match defs.iter().find(|def| def.key() == key) {
Some(def) => type_ref = def.type_ref(),
None => break,
}
}
type_ref
}
fn check_form_fields(type_ref: &TypeRef, defs: &[TypeDef]) -> Result<(), String> {
let TypeRef::Object(fields) = resolve_form_type(type_ref, defs) else {
return Err(
"form input must be a struct with named fields, without flattened fields".to_owned(),
);
};
for field in fields {
check_form_field(field.type_ref(), true, defs).map_err(|problem| {
format!(
"the form field `{}` is {problem}, which form input cannot read",
field.name()
)
})?;
}
Ok(())
}
fn check_form_field(type_ref: &TypeRef, repeated: bool, defs: &[TypeDef]) -> Result<(), String> {
let described = match resolve_form_type(type_ref, defs) {
TypeRef::Bool
| TypeRef::String
| TypeRef::Number
| TypeRef::Integer
| TypeRef::WideInteger { .. }
| TypeRef::Blob
| TypeRef::StringLiteral(_) => return Ok(()),
TypeRef::Nullable(inner) => return check_form_field(inner, repeated, defs),
TypeRef::Array(inner) if repeated => return check_form_field(inner, false, defs),
TypeRef::Union(members)
if !members.is_empty()
&& members
.iter()
.all(|member| matches!(member, TypeRef::StringLiteral(_))) =>
{
return Ok(());
}
TypeRef::Array(_) => "a sequence inside a sequence",
TypeRef::Object(_) => "a nested struct",
TypeRef::Map(..) => "a map",
TypeRef::Tuple(_) => "a tuple",
TypeRef::Intersection(_) => "a flattened struct",
TypeRef::Union(_) => "an enum with data",
TypeRef::Unit | TypeRef::Null => "an empty value",
TypeRef::Unknown | TypeRef::Raw(_) => "a value of unknown shape",
TypeRef::FormData => "a form",
TypeRef::Named { .. } => "a type that the declarations do not define",
};
Err(described.to_owned())
}
#[derive(Debug)]
pub struct UploadedFile {
pub file_name: String,
pub content_type: Option<String>,
pub temp_file: NamedTempFile,
}
impl TsGen for UploadedFile {
fn type_ref() -> TypeRef {
TypeRef::Blob
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum FormDeserializeError {
MalformedMultipartBody,
MissingField {
field: &'static str,
},
MultipleValues,
UnexpectedFile,
UnexpectedText,
InvalidValue,
}
impl fmt::Display for FormDeserializeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MalformedMultipartBody => formatter.write_str("malformed multipart form body"),
Self::MissingField { field } => {
write!(formatter, "form input is missing field `{field}`")
}
Self::MultipleValues => {
formatter.write_str("a single-valued form field received several parts")
}
Self::UnexpectedFile => formatter.write_str("a text form field received a file"),
Self::UnexpectedText => formatter.write_str("a file form field received text"),
Self::InvalidValue => formatter.write_str("form input does not match its input type"),
}
}
}
impl std::error::Error for FormDeserializeError {}
impl de::Error for FormDeserializeError {
fn custom<M: fmt::Display>(_message: M) -> Self {
Self::InvalidValue
}
fn missing_field(field: &'static str) -> Self {
Self::MissingField { field }
}
}
pub(crate) enum FormDecodeError {
Invalid,
BodyRead(Box<dyn StdError + Send + Sync>),
Storage(io::Error),
}
impl From<FormDeserializeError> for FormDecodeError {
fn from(_error: FormDeserializeError) -> Self {
Self::Invalid
}
}
pub(crate) async fn decode_form_input<Fields: DeserializeOwned>(
media_type: FormMediaType,
body: FormBody,
) -> Result<Fields, FormDecodeError> {
let fields = match media_type {
FormMediaType::Multipart { boundary } => read_multipart_fields(boundary, body).await?,
FormMediaType::UrlEncoded => read_urlencoded_fields(&collect_body(body).await?),
};
Ok(Fields::deserialize(MapDeserializer::new(
fields
.into_iter()
.map(|(name, parts)| (name, FieldParts(parts))),
))?)
}
async fn collect_body(mut body: FormBody) -> Result<Bytes, FormDecodeError> {
let mut collected = BytesMut::new();
while let Some(chunk) = body.next().await {
collected.extend_from_slice(&chunk.map_err(FormDecodeError::BodyRead)?);
}
Ok(collected.freeze())
}
type Fields = BTreeMap<String, Vec<Part>>;
enum Part {
Text(String),
File(UploadedFile),
}
fn read_urlencoded_fields(body: &[u8]) -> Fields {
let mut fields = Fields::new();
for (name, value) in form_urlencoded::parse(body) {
fields
.entry(name.into_owned())
.or_default()
.push(Part::Text(value.into_owned()));
}
fields
}
fn from_multer(error: multer::Error) -> FormDecodeError {
match error {
multer::Error::StreamReadFailed(error) => FormDecodeError::BodyRead(error),
_ => FormDecodeError::Invalid,
}
}
async fn read_multipart_fields(
boundary: String,
body: FormBody,
) -> Result<Fields, FormDecodeError> {
let mut multipart = multer::Multipart::new(body, boundary);
let mut fields = Fields::new();
while let Some(mut field) = multipart.next_field().await.map_err(from_multer)? {
let name = field
.name()
.ok_or(FormDeserializeError::MalformedMultipartBody)?
.to_owned();
let part = match field.file_name() {
Some(file_name) => {
let file_name = file_name.to_owned();
let content_type = field.content_type().map(ToString::to_string);
let temp_file = store_file_part(&mut field).await?;
Part::File(UploadedFile {
file_name,
content_type,
temp_file,
})
}
None => Part::Text(field.text().await.map_err(from_multer)?),
};
fields.entry(name).or_default().push(part);
}
Ok(fields)
}
async fn store_file_part(field: &mut multer::Field<'_>) -> Result<NamedTempFile, FormDecodeError> {
let (temp_file, handle) = tokio::task::spawn_blocking(|| {
let temp_file = NamedTempFile::new()?;
let handle = temp_file.reopen()?;
Ok::<_, io::Error>((temp_file, handle))
})
.await
.map_err(io::Error::other)
.and_then(|created| created)
.map_err(FormDecodeError::Storage)?;
let mut handle = tokio::fs::File::from_std(handle);
while let Some(chunk) = field.chunk().await.map_err(from_multer)? {
handle
.write_all(&chunk)
.await
.map_err(FormDecodeError::Storage)?;
}
handle.flush().await.map_err(FormDecodeError::Storage)?;
Ok(temp_file)
}
thread_local! {
static PENDING_UPLOADED_FILE: RefCell<Option<UploadedFile>> = const { RefCell::new(None) };
}
const UPLOADED_FILE_TOKEN: &str = "$vorma::UploadedFile";
struct FieldParts(Vec<Part>);
impl FieldParts {
fn into_single(self) -> Result<Part, FormDeserializeError> {
let [part] =
<[Part; 1]>::try_from(self.0).map_err(|_| FormDeserializeError::MultipleValues)?;
Ok(part)
}
fn into_text(self) -> Result<String, FormDeserializeError> {
match self.into_single()? {
Part::Text(text) => Ok(text),
Part::File(_) => Err(FormDeserializeError::UnexpectedFile),
}
}
}
impl IntoDeserializer<'_, FormDeserializeError> for FieldParts {
type Deserializer = Self;
fn into_deserializer(self) -> Self {
self
}
}
macro_rules! deserialize_parsed_text {
($($method:ident => $visit:ident,)*) => {$(
fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, FormDeserializeError> {
let text = self.into_text()?;
visitor.$visit(text.parse().map_err(|_| FormDeserializeError::InvalidValue)?)
}
)*};
}
impl<'de> Deserializer<'de> for FieldParts {
type Error = FormDeserializeError;
fn deserialize_any<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
visitor.visit_string(self.into_text()?)
}
deserialize_parsed_text! {
deserialize_bool => visit_bool,
deserialize_i8 => visit_i8,
deserialize_i16 => visit_i16,
deserialize_i32 => visit_i32,
deserialize_i64 => visit_i64,
deserialize_i128 => visit_i128,
deserialize_u8 => visit_u8,
deserialize_u16 => visit_u16,
deserialize_u32 => visit_u32,
deserialize_u64 => visit_u64,
deserialize_u128 => visit_u128,
deserialize_f32 => visit_f32,
deserialize_f64 => visit_f64,
deserialize_char => visit_char,
}
fn deserialize_option<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
visitor.visit_some(self)
}
fn deserialize_seq<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
let els = self.0.into_iter().map(|part| Self(vec![part]));
visitor.visit_seq(SeqDeserializer::new(els))
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
if name != UPLOADED_FILE_TOKEN {
return visitor.visit_newtype_struct(self);
}
let Part::File(file) = self.into_single()? else {
return Err(FormDeserializeError::UnexpectedText);
};
PENDING_UPLOADED_FILE.with(|pending| *pending.borrow_mut() = Some(file));
visitor.visit_unit()
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
visitor.visit_enum(StringDeserializer::new(self.into_text()?))
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, FormDeserializeError> {
visitor.visit_unit()
}
forward_to_deserialize_any! {
str string bytes byte_buf unit unit_struct tuple tuple_struct map struct identifier
}
}
impl<'de> Deserialize<'de> for UploadedFile {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_newtype_struct(UPLOADED_FILE_TOKEN, UploadedFileVisitor)
}
}
struct UploadedFileVisitor;
impl<'de> Visitor<'de> for UploadedFileVisitor {
type Value = UploadedFile;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a file part of form input")
}
fn visit_unit<E: de::Error>(self) -> Result<UploadedFile, E> {
PENDING_UPLOADED_FILE
.with(|pending| pending.borrow_mut().take())
.ok_or_else(|| E::custom("an uploaded file deserializes only from form input"))
}
}