lettre/message/header/
content_type.rs1use std::{
2 error::Error as StdError,
3 fmt::{self, Display},
4 str::FromStr,
5};
6
7use mime::Mime;
8
9use super::{Header, HeaderName, HeaderValue};
10use crate::BoxError;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ContentType(Mime);
22
23impl ContentType {
24 pub const TEXT_PLAIN: ContentType = Self::from_mime(mime::TEXT_PLAIN_UTF_8);
28
29 pub const TEXT_HTML: ContentType = Self::from_mime(mime::TEXT_HTML_UTF_8);
33
34 pub fn parse(s: &str) -> Result<ContentType, ContentTypeErr> {
36 Ok(Self::from_mime(s.parse().map_err(ContentTypeErr)?))
37 }
38
39 pub(crate) const fn from_mime(mime: Mime) -> Self {
40 Self(mime)
41 }
42
43 pub(crate) fn as_ref(&self) -> &Mime {
44 &self.0
45 }
46}
47
48impl Header for ContentType {
49 fn name() -> HeaderName {
50 HeaderName::new_from_ascii_str("Content-Type")
51 }
52
53 fn parse(s: &str) -> Result<Self, BoxError> {
54 Ok(Self(s.parse()?))
55 }
56
57 fn display(&self) -> HeaderValue {
58 HeaderValue::new(Self::name(), self.0.to_string())
59 }
60}
61
62impl FromStr for ContentType {
63 type Err = ContentTypeErr;
64
65 fn from_str(s: &str) -> Result<Self, Self::Err> {
66 Self::parse(s)
67 }
68}
69
70#[cfg(feature = "mime03")]
71#[cfg_attr(docsrs, doc(cfg(feature = "mime03")))]
72impl From<Mime> for ContentType {
73 fn from(mime: Mime) -> Self {
74 Self::from_mime(mime)
75 }
76}
77
78#[derive(Debug)]
80pub struct ContentTypeErr(mime::FromStrError);
81
82impl StdError for ContentTypeErr {
83 fn source(&self) -> Option<&(dyn StdError + 'static)> {
84 Some(&self.0)
85 }
86}
87
88impl Display for ContentTypeErr {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 Display::fmt(&self.0, f)
91 }
92}
93
94#[cfg(feature = "serde")]
96mod serde {
97 use std::fmt;
98
99 use serde::{
100 de::{self, Deserialize, Deserializer, Visitor},
101 ser::{Serialize, Serializer},
102 };
103
104 use super::ContentType;
105
106 impl Serialize for ContentType {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
110 {
111 serializer.serialize_newtype_struct("ContentType", &format!("{}", &self.0))
112 }
113 }
114
115 impl<'de> Deserialize<'de> for ContentType {
116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117 where
118 D: Deserializer<'de>,
119 {
120 struct ContentTypeVisitor;
121
122 impl Visitor<'_> for ContentTypeVisitor {
123 type Value = ContentType;
124
125 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter.write_str("a ContentType string like `text/plain`")
129 }
130
131 fn visit_str<E>(self, mime: &str) -> Result<ContentType, E>
132 where
133 E: de::Error,
134 {
135 match ContentType::parse(mime) {
136 Ok(content_type) => Ok(content_type),
137 Err(_) => Err(E::custom(format!(
138 "Couldn't parse the following MIME-Type: {mime}"
139 ))),
140 }
141 }
142 }
143
144 deserializer.deserialize_str(ContentTypeVisitor)
145 }
146 }
147}
148
149#[cfg(test)]
150mod test {
151 use pretty_assertions::assert_eq;
152
153 use super::ContentType;
154 use crate::message::header::{HeaderName, HeaderValue, Headers};
155
156 #[test]
157 fn format_content_type() {
158 let mut headers = Headers::new();
159
160 headers.set(ContentType::TEXT_PLAIN);
161
162 assert_eq!(
163 headers.to_string(),
164 "Content-Type: text/plain; charset=utf-8\r\n"
165 );
166
167 headers.set(ContentType::TEXT_HTML);
168
169 assert_eq!(
170 headers.to_string(),
171 "Content-Type: text/html; charset=utf-8\r\n"
172 );
173 }
174
175 #[test]
176 fn parse_content_type() {
177 let mut headers = Headers::new();
178
179 headers.insert_raw(HeaderValue::new(
180 HeaderName::new_from_ascii_str("Content-Type"),
181 "text/plain; charset=utf-8".to_owned(),
182 ));
183
184 assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_PLAIN));
185
186 headers.insert_raw(HeaderValue::new(
187 HeaderName::new_from_ascii_str("Content-Type"),
188 "text/html; charset=utf-8".to_owned(),
189 ));
190
191 assert_eq!(headers.get::<ContentType>(), Some(ContentType::TEXT_HTML));
192 }
193}