murgamu 0.7.4

A NestJS-inspired web framework for Rust
Documentation
use super::MurFormField;
use super::MurMultipartConfig;
use super::MurMultipartUtils;
use super::MurUploadedFile;
use crate::core::error::MurError;
use crate::mur_http::request::MurRequestContext;
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct MurMultipart {
	pub fields: Vec<MurFormField>,
	pub text_fields: HashMap<String, Vec<String>>,
	pub file_fields: HashMap<String, Vec<MurUploadedFile>>,
	pub total_file_size: usize,
}

impl MurMultipart {
	pub fn empty() -> Self {
		Self {
			fields: Vec::new(),
			text_fields: HashMap::new(),
			file_fields: HashMap::new(),
			total_file_size: 0,
		}
	}

	pub async fn parse(ctx: &MurRequestContext) -> Result<Self, MurError> {
		Self::parse_with_config(ctx, &MurMultipartConfig::default()).await
	}

	pub async fn parse_with_config(
		ctx: &MurRequestContext,
		config: &MurMultipartConfig,
	) -> Result<Self, MurError> {
		let content_type = ctx
			.header("Content-Type")
			.ok_or_else(|| MurError::BadRequest("Missing Content-Type header".into()))?;
		let boundary = MurMultipartUtils::parse_boundary(content_type)?;
		let body = ctx
			.body
			.as_ref()
			.ok_or_else(|| MurError::BadRequest("Missing request body".into()))?;

		if body.len() > config.max_body_size {
			return Err(MurError::BadRequest(format!(
				"Request body exceeds maximum size of {} bytes",
				config.max_body_size
			)));
		}

		MurMultipartUtils::parse_multipart_body(body, &boundary, config)
	}

	pub fn fields(&self) -> &[MurFormField] {
		&self.fields
	}

	pub fn fields_count(&self) -> usize {
		self.fields.len()
	}

	pub fn text_fields_count(&self) -> usize {
		self.text_fields.values().map(|v| v.len()).sum()
	}

	pub fn files_count(&self) -> usize {
		self.file_fields.values().map(|v| v.len()).sum()
	}

	pub fn total_file_size(&self) -> usize {
		self.total_file_size
	}

	pub fn has_files(&self) -> bool {
		!self.file_fields.is_empty()
	}

	pub fn has(&self, name: &str) -> bool {
		self.text_fields.contains_key(name) || self.file_fields.contains_key(name)
	}

	pub fn text(&self, name: &str) -> Option<&str> {
		self.text_fields
			.get(name)
			.and_then(|v| v.first())
			.map(|s| s.as_str())
	}

	pub fn text_all(&self, name: &str) -> Vec<&str> {
		self.text_fields
			.get(name)
			.map(|v| v.iter().map(|s| s.as_str()).collect())
			.unwrap_or_default()
	}

	pub fn text_or<'a>(&'a self, name: &str, default: &'a str) -> &'a str {
		self.text(name).unwrap_or(default)
	}

	pub fn text_as<T: std::str::FromStr>(&self, name: &str) -> Option<T> {
		self.text(name).and_then(|s| s.parse().ok())
	}

	pub fn file(&self, name: &str) -> Option<&MurUploadedFile> {
		self.file_fields.get(name).and_then(|v| v.first())
	}

	pub fn files(&self, name: &str) -> Vec<&MurUploadedFile> {
		self.file_fields
			.get(name)
			.map(|v| v.iter().collect())
			.unwrap_or_default()
	}

	pub fn all_files(&self) -> Vec<&MurUploadedFile> {
		self.file_fields.values().flatten().collect()
	}

	pub fn take_file(&mut self, name: &str) -> Option<MurUploadedFile> {
		self.file_fields.get_mut(name).and_then(|v| {
			if v.is_empty() {
				None
			} else {
				Some(v.remove(0))
			}
		})
	}

	pub fn take_files(&mut self, name: &str) -> Vec<MurUploadedFile> {
		self.file_fields.remove(name).unwrap_or_default()
	}

	pub fn take_all_files(&mut self) -> Vec<MurUploadedFile> {
		let mut files = Vec::new();
		for (_, field_files) in self.file_fields.drain() {
			files.extend(field_files);
		}
		files
	}

	pub fn field_names(&self) -> Vec<&str> {
		let mut names: Vec<&str> = self
			.text_fields
			.keys()
			.chain(self.file_fields.keys())
			.map(|s| s.as_str())
			.collect();
		names.sort();
		names.dedup();
		names
	}

	pub fn to_text_map(&self) -> HashMap<String, String> {
		self.text_fields
			.iter()
			.filter_map(|(k, v)| v.first().map(|val| (k.clone(), val.clone())))
			.collect()
	}
}