use std::str;
use derive_more::{Deref, DerefMut, Display};
use ntex::http::StatusCode;
use ntex::web::{DefaultError, HttpRequest, WebResponseError};
use serde::de::DeserializeOwned;
use crate::form::{FieldReader, Limits, bytes::Bytes};
use crate::{Field, MultipartError};
#[derive(Debug, Deref, DerefMut)]
pub struct Text<T: DeserializeOwned>(pub T);
impl<T: DeserializeOwned> Text<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> FieldReader for Text<T>
where
T: DeserializeOwned + 'static,
{
async fn read_field(
req: &HttpRequest,
field: Field,
limits: &mut Limits,
) -> Result<Self, MultipartError> {
let config = req.app_state::<TextConfig>().unwrap_or(&DEFAULT_CONFIG);
if config.validate_content_type {
let valid = if let Some(mime) = field.content_type() {
mime.subtype() == mime::PLAIN || mime.suffix() == Some(mime::PLAIN)
} else {
true
};
if !valid {
return Err(MultipartError::Field {
name: field.form_field_name,
source: TextError::ContentType.into(),
});
}
}
let form_field_name = field.form_field_name.clone();
let bytes = Bytes::read_field(req, field, limits).await?;
let text = str::from_utf8(&bytes.data).map_err(|err| MultipartError::Field {
name: form_field_name.clone(),
source: TextError::Utf8Error(err).into(),
})?;
Ok(Text(serde_plain::from_str(text).map_err(|err| MultipartError::Field {
name: form_field_name,
source: TextError::Deserialize(err).into(),
})?))
}
}
#[derive(Debug, Display)]
#[non_exhaustive]
pub enum TextError {
#[display("UTF-8 decoding error: {}", _0)]
Utf8Error(str::Utf8Error),
#[display("Plain text deserialize error: {}", _0)]
Deserialize(serde_plain::Error),
#[display("Content type error")]
ContentType,
}
impl WebResponseError<DefaultError> for TextError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}
#[derive(Clone)]
pub struct TextConfig {
validate_content_type: bool,
}
impl TextConfig {
pub fn validate_content_type(mut self, validate_content_type: bool) -> Self {
self.validate_content_type = validate_content_type;
self
}
}
const DEFAULT_CONFIG: TextConfig = TextConfig { validate_content_type: true };
impl Default for TextConfig {
fn default() -> Self {
DEFAULT_CONFIG
}
}