1use super::{error::*, media_type::*, name::*, params::*, parse::*, value::*};
2use std::{
3 borrow::Cow,
4 collections::BTreeMap,
5 fmt,
6 hash::{Hash, Hasher},
7 str::FromStr,
8};
9
10#[derive(Debug, Clone)]
23pub struct MediaTypeBuf {
24 data: Box<str>,
25 indices: Indices,
26}
27
28impl MediaTypeBuf {
29 #[must_use]
31 pub fn new(ty: Name, subty: Name) -> Self {
32 Self::from_string(format!("{}/{}", ty, subty)).expect("`ty` and `subty` should be valid")
33 }
34
35 #[must_use]
37 pub fn from_parts(
38 ty: Name,
39 subty: Name,
40 suffix: Option<Name>,
41 params: &[(Name, Value)],
42 ) -> Self {
43 use std::fmt::Write;
44 let mut s = String::new();
45 write!(s, "{}/{}", ty, subty).expect("`ty` and `subty` should be valid");
46 if let Some(suffix) = suffix {
47 write!(s, "+{}", suffix).unwrap();
48 }
49 for (name, value) in params {
50 write!(s, "; {}={}", name, value).unwrap();
51 }
52 Self::from_string(s).expect("all values should be valid")
53 }
54
55 pub fn from_string(mut s: String) -> Result<Self, MediaTypeError> {
67 let (indices, len) = Indices::parse(&s)?;
68 s.truncate(len);
69 Ok(Self {
70 data: s.into(),
71 indices,
72 })
73 }
74
75 #[must_use]
77 pub fn ty(&self) -> Name {
78 Name::new_unchecked(&self.data[self.indices.ty()])
79 }
80
81 #[must_use]
83 pub fn subty(&self) -> Name {
84 Name::new_unchecked(&self.data[self.indices.subty()])
85 }
86
87 #[must_use]
89 pub fn suffix(&self) -> Option<Name> {
90 self.indices
91 .suffix()
92 .map(|range| Name::new_unchecked(&self.data[range]))
93 }
94
95 #[must_use]
106 pub fn essence(&self) -> MediaType<'_> {
107 MediaType::from_parts(self.ty(), self.subty(), self.suffix(), &[])
108 }
109
110 #[must_use]
112 pub const fn as_str(&self) -> &str {
113 &self.data
114 }
115
116 #[must_use]
129 pub fn canonicalize(&self) -> Self {
130 use std::fmt::Write;
131 let mut s = String::with_capacity(self.data.len());
132 write!(
133 s,
134 "{}/{}",
135 self.ty().as_str().to_ascii_lowercase(),
136 self.subty().as_str().to_ascii_lowercase()
137 )
138 .unwrap();
139 if let Some(suffix) = self.suffix() {
140 write!(s, "+{}", suffix.as_str().to_ascii_lowercase())
141 .expect("`write` should not fail on a `String`");
142 }
143 for (name, value) in self.params() {
144 write!(s, "; {}={}", name.as_str().to_ascii_lowercase(), value)
145 .expect("`write` should not fail on a `String`");
146 }
147 s.shrink_to_fit();
148 Self::from_string(s).expect("all values should be valid")
149 }
150
151 #[must_use]
153 pub fn to_ref(&self) -> MediaType {
154 let params = self.params().collect::<Vec<_>>();
155 let params = if params.is_empty() {
156 Cow::Borrowed([].as_slice())
157 } else {
158 Cow::Owned(params)
159 };
160 MediaType::from_parts_unchecked(self.ty(), self.subty(), self.suffix(), params)
161 }
162}
163
164impl ReadParams for MediaTypeBuf {
165 fn params(&self) -> Params {
166 Params::from_indices(&self.data, &self.indices)
167 }
168
169 fn get_param(&self, name: Name) -> Option<Value> {
170 self.indices
171 .params()
172 .iter()
173 .rev()
174 .find(|&&[start, end, _, _]| {
175 name == Name::new_unchecked(&self.data[start..end])
176 })
177 .map(|&[_, _, start, end]| {
178 Value::new_unchecked(&self.data[start..end])
179 })
180 }
181}
182
183impl FromStr for MediaTypeBuf {
184 type Err = MediaTypeError;
185
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 let (indices, len) = Indices::parse(s)?;
188 Ok(Self {
189 data: s[..len].into(),
190 indices,
191 })
192 }
193}
194
195impl From<MediaType<'_>> for MediaTypeBuf {
196 fn from(t: MediaType) -> Self {
197 Self::from_string(t.to_string()).expect("`t` should be valid")
198 }
199}
200
201impl From<&MediaType<'_>> for MediaTypeBuf {
202 fn from(t: &MediaType) -> Self {
203 Self::from_string(t.to_string()).expect("`t` should be valid")
204 }
205}
206
207impl AsRef<str> for MediaTypeBuf {
208 fn as_ref(&self) -> &str {
209 &self.data
210 }
211}
212
213impl PartialEq for MediaTypeBuf {
214 fn eq(&self, other: &Self) -> bool {
215 self.ty() == other.ty()
216 && self.subty() == other.subty()
217 && self.suffix() == other.suffix()
218 && self.params().collect::<BTreeMap<_, _>>()
219 == other.params().collect::<BTreeMap<_, _>>()
220 }
221}
222
223impl Eq for MediaTypeBuf {}
224
225impl PartialEq<MediaType<'_>> for MediaTypeBuf {
226 fn eq(&self, other: &MediaType) -> bool {
227 self.ty() == other.ty
228 && self.subty() == other.subty
229 && self.suffix() == other.suffix
230 && self.params().collect::<BTreeMap<_, _>>()
231 == other.params().collect::<BTreeMap<_, _>>()
232 }
233}
234
235impl PartialEq<&MediaType<'_>> for MediaTypeBuf {
236 fn eq(&self, other: &&MediaType) -> bool {
237 self == *other
238 }
239}
240
241impl PartialEq<MediaType<'_>> for &MediaTypeBuf {
242 fn eq(&self, other: &MediaType) -> bool {
243 *self == other
244 }
245}
246
247impl fmt::Display for MediaTypeBuf {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 write!(f, "{}/{}", self.ty(), self.subty())?;
250 if let Some(suffix) = self.suffix() {
251 write!(f, "+{}", suffix)?;
252 }
253 for (name, value) in self.params() {
254 write!(f, "; {}={}", name, value)?;
255 }
256 Ok(())
257 }
258}
259
260impl Hash for MediaTypeBuf {
261 fn hash<H: Hasher>(&self, state: &mut H) {
262 self.ty().hash(state);
263 self.subty().hash(state);
264 self.suffix().hash(state);
265 self.params()
266 .collect::<BTreeMap<_, _>>()
267 .into_iter()
268 .collect::<Vec<_>>()
269 .hash(state);
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::{media_type, names::*, values::*};
277 use std::collections::hash_map::DefaultHasher;
278
279 fn calculate_hash<T: Hash>(t: &T) -> u64 {
280 let mut s = DefaultHasher::new();
281 t.hash(&mut s);
282 s.finish()
283 }
284
285 #[test]
286 fn from_parts() {
287 assert_eq!(
288 MediaTypeBuf::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)]).to_string(),
289 "image/svg+xml; charset=UTF-8"
290 );
291 }
292
293 #[test]
294 fn get_param() {
295 assert_eq!(
296 MediaTypeBuf::from_str("image/svg+xml")
297 .unwrap()
298 .get_param(CHARSET),
299 None
300 );
301 assert_eq!(
302 MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8")
303 .unwrap()
304 .get_param(CHARSET),
305 Some(UTF_8)
306 );
307 assert_eq!(
308 MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8; HELLO=WORLD; HELLO=world")
309 .unwrap()
310 .get_param(Name::new("hello").unwrap()),
311 Some(Value::new("world").unwrap())
312 );
313 }
314
315 #[test]
316 fn essence() {
317 assert_eq!(
318 MediaTypeBuf::from_str("image/svg+xml")
319 .unwrap()
320 .essence()
321 .to_string(),
322 "image/svg+xml"
323 );
324 assert_eq!(
325 MediaTypeBuf::from_str("image/svg+xml; ")
326 .unwrap()
327 .essence()
328 .to_string(),
329 "image/svg+xml"
330 );
331 assert_eq!(
332 MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8")
333 .unwrap()
334 .essence()
335 .to_string(),
336 "image/svg+xml"
337 );
338 assert_eq!(
339 MediaTypeBuf::from_str("image/svg+xml ; charset=UTF-8")
340 .unwrap()
341 .essence()
342 .to_string(),
343 "image/svg+xml"
344 );
345 }
346
347 #[test]
348 fn canonicalize() {
349 assert_eq!(
350 MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8; ")
351 .unwrap()
352 .canonicalize()
353 .to_string(),
354 "image/svg+xml; charset=UTF-8"
355 );
356 }
357
358 #[test]
359 fn cmp() {
360 assert_eq!(
361 MediaTypeBuf::from_str("text/plain").unwrap(),
362 MediaTypeBuf::from_str("TEXT/PLAIN").unwrap()
363 );
364 assert_eq!(
365 MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(),
366 MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap()
367 );
368 assert_eq!(
369 MediaTypeBuf::from_str("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap(),
370 MediaTypeBuf::from_str("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap()
371 );
372 assert_eq!(
373 MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(),
374 MediaType::from_parts(
375 IMAGE,
376 SVG,
377 Some(XML),
378 &[(CHARSET, US_ASCII), (CHARSET, UTF_8)]
379 ),
380 );
381 assert_eq!(
382 &MediaTypeBuf::from_str("image/svg+xml").unwrap(),
383 media_type!(IMAGE / SVG + XML)
384 );
385 }
386
387 #[test]
388 fn hash() {
389 assert_eq!(
390 calculate_hash(&MediaTypeBuf::from_str("text/plain").unwrap()),
391 calculate_hash(&MediaTypeBuf::from_str("TEXT/PLAIN").unwrap())
392 );
393 assert_eq!(
394 calculate_hash(&MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap()),
395 calculate_hash(&MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap())
396 );
397 assert_eq!(
398 calculate_hash(
399 &MediaTypeBuf::from_str("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap()
400 ),
401 calculate_hash(
402 &MediaTypeBuf::from_str("IMAGE/SVG+XML; HELLO=WORLD; CHARSET=UTF-8").unwrap()
403 )
404 );
405 assert_eq!(
406 calculate_hash(&MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap()),
407 calculate_hash(
408 &MediaTypeBuf::from_str("image/svg+xml; charset=US-ASCII; charset=UTF-8").unwrap()
409 ),
410 );
411 }
412}