From 0d0caf09da2604eb0a506599fcc76ec1231c52da Mon Sep 17 00:00:00 2001 From: Thomas Oettli Date: Sat, 10 Oct 2026 22:53:14 +0200 Subject: [PATCH] take ContentType in set_content, hide mediatype --- README.md | 4 ++-- src/lib.rs | 57 ++++++++++++++++++++++++++-------------------------- src/tests.rs | 4 ++-- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 9fa9645..cabd816 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ assert!(out.starts_with(b"Subject: Hello")); Building a multipart message from scratch: ```rust -use cow_mail::{CowMail, HeaderMap, Mail, MediaType, names}; +use cow_mail::{ContentType, CowMail, HeaderMap, Mail}; let mut mail = CowMail::default(); mail.add_header("Subject", "Report"); @@ -49,7 +49,7 @@ mail.set_body("plain text version", "

html version

").unwrap(); let mut attachment = CowMail::new(); attachment.add_header("Content-Disposition", "attachment; filename=\"report.csv\""); -attachment.set_content("a,b\n1,2\n", Some(MediaType::new(names::TEXT, names::CSV))); +attachment.set_content("a,b\n1,2\n", Some(ContentType::new("text", "csv"))); mail.add_attachment(attachment); assert!(mail.is_multipart()); diff --git a/src/lib.rs b/src/lib.rs index 18c1392..c8ef7ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,7 @@ use mailparse::{ ParsedContentType, ParsedMail, body::Body, parse_content_disposition, parse_content_type, }; -/// Re-exported from [`mediatype`]. [`MediaType`] is the content type accepted by -/// [`CowMail::set_content`] and [`CowMail::set_content_raw`], and [`names`] / [`values`] hold the -/// typed constants to build one with, so depending on `mediatype` separately is neither necessary -/// nor advisable. -pub use mediatype::{MediaType, names, values}; -use mediatype::{MediaTypeBuf, Name, ReadParams, WriteParams, names::*, values::UTF_8}; +use mediatype::{MediaTypeBuf, Name, names::*}; use rand::{RngExt, distr::Alphanumeric, rng}; use std::borrow::Cow; use std::fmt::Display; @@ -35,7 +30,7 @@ use std::fmt::Display; /// A failure reported by the parser the crate uses internally. /// /// The concrete error type is deliberately not part of this crate's API, so that the parser -/// can be changed or upgraded without breaking callers. [`Display`](std::fmt::Display) gives +/// can be changed or upgraded without breaking callers. [`Display`] gives /// the message, and [`source`](std::error::Error::source) the underlying error, for anyone who /// wants to walk the chain down to e.g. the base64 decoder that rejected a body. pub struct ParseError { @@ -140,13 +135,12 @@ impl BodyType { Self::HTML => Self::PLAIN, } } -} -impl From for Name<'_> { - fn from(btype: BodyType) -> Self { - match btype { - BodyType::PLAIN => PLAIN, - BodyType::HTML => HTML, + /// The subtype a part of this body type declares. + fn name(self) -> Name<'static> { + match self { + Self::PLAIN => PLAIN, + Self::HTML => HTML, } } } @@ -167,7 +161,7 @@ pub enum MessageType { /// A `Content-Type`: the mimetype plus its parameters. /// /// The type both sides of the crate speak: [`Mail::content_type`] hands one back for a part, and -/// [`CowMail::set_content`] takes one for the part it writes. [`Display`](std::fmt::Display) +/// [`CowMail::set_content`] takes one for the part it writes. [`Display`] /// renders it as it belongs in the header, which is how it ends up serialized. /// /// Build one with [`new`](Self::new), take a ready-made one from [`mime`], or parse a header value @@ -381,10 +375,11 @@ fn is_token(value: &str) -> bool { /// # Examples /// /// ``` -/// use cow_mail::mime; +/// use cow_mail::{CowMail, Mail, mime}; /// -/// assert_eq!(mime::TEXT_HTML.mimetype(), "text/html"); -/// assert_eq!(mime::MULTIPART_MIXED.subty(), "mixed"); +/// let mut part = CowMail::default(); +/// part.set_content("

hi

", Some(mime::TEXT_HTML)); +/// assert_eq!(part.content_type().mimetype(), "text/html"); /// ``` pub mod mime { use super::ContentType; @@ -808,10 +803,10 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { /// # Examples /// /// ``` - /// use cow_mail::{CowMail, HeaderMap, Mail, MediaType, names}; + /// use cow_mail::{CowMail, HeaderMap, Mail, mime}; /// /// let mut part = CowMail::default(); - /// part.set_content("

hi

", Some(MediaType::new(names::TEXT, names::HTML))); + /// part.set_content("

hi

", Some(mime::TEXT_HTML)); /// /// assert_eq!(part.content_type().mimetype(), "text/html"); /// assert_eq!(part.get_content().unwrap(), "

hi

"); @@ -821,12 +816,12 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { /// assert_eq!(part.find_value("Content-Type").unwrap(), "text/plain; charset=UTF-8"); /// assert_eq!(part.get_content().unwrap(), "Grüße"); /// ``` - pub fn set_content(&mut self, content: T, ctype: Option) { + pub fn set_content(&mut self, content: T, ctype: Option) { let content = content.to_string(); - let mut ctype = ctype.unwrap_or(MediaType::new(TEXT, PLAIN)); - if !content.is_ascii() && ctype.get_param(CHARSET).is_none() { - ctype.set_param(CHARSET, UTF_8); + let mut ctype = ctype.unwrap_or_default(); + if !content.is_ascii() && ctype.get_param("charset").is_none() { + ctype.set_param("charset", "UTF-8"); } self.set_content_raw(content, Some(ctype)); @@ -838,7 +833,11 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { /// left off entirely when `None`. Binary content always ends up base64-encoded; pass a /// [`BodyContent::Text`] (or a `&str`/`String`, which convert into one) to allow the more /// compact encodings. - pub fn set_content_raw>(&mut self, content: B, ctype: Option) { + pub fn set_content_raw>( + &mut self, + content: B, + ctype: Option, + ) { self.clear_content(); if let Some(ctype) = ctype { @@ -904,7 +903,7 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { let subtype = info.subty(); let is_body = match bodytype { - Some(bodytype) => subtype == Name::from(bodytype), + Some(bodytype) => subtype == bodytype.name(), None => subtype == PLAIN || subtype == HTML, }; @@ -1135,7 +1134,7 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { /// # Examples /// /// ``` - /// use cow_mail::{CowMail, HeaderMap, Mail, MediaType, names}; + /// use cow_mail::{ContentType, CowMail, HeaderMap, Mail}; /// /// let mut mail = CowMail::default(); /// mail.set_text_body("see attached").unwrap(); @@ -1143,7 +1142,7 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { /// let mut attachment = CowMail::new(); /// attachment.set_content_raw( /// &b"\x89PNG\r\n\x1a\n"[..], - /// Some(MediaType::new(names::IMAGE, names::PNG)), + /// Some(ContentType::new("image", "png")), /// ); /// mail.add_attachment(attachment); /// @@ -1175,10 +1174,10 @@ impl<'a, H: Header<'a> + 'a> CowMail<'a, H> { current } - fn ctype_for_body(body_type: BodyType) -> Option> { + fn ctype_for_body(body_type: BodyType) -> Option { match body_type { BodyType::PLAIN => None, - BodyType::HTML => Some(MediaType::new(TEXT, HTML)), + BodyType::HTML => Some(mime::TEXT_HTML), } } diff --git a/src/tests.rs b/src/tests.rs index c056d47..57466fb 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ //use std::io::{Read, Write}; - use crate::{BodyType, CowHeader, CowMail, Header, HeaderMap, Mail, MediaType, names::HTML, names::TEXT}; + use crate::{BodyType, CowHeader, CowMail, Header, HeaderMap, Mail, mime}; fn serialize<'a>(mail: &'a CowMail<'a, CowHeader<'a>>) -> Vec { mail.serialize().iter().flat_map(|p| p.iter()).copied().collect() @@ -99,7 +99,7 @@ let mut html = CowMail::new(); html.set_content( "This is a new test message ☺".to_string(), - Some(MediaType::new(TEXT, HTML)), + Some(mime::TEXT_HTML), ); mail.add_alternative(html);