http_types/content/
media_type_proposal.rs1use crate::ensure;
2use crate::headers::HeaderValue;
3use crate::Mime;
4
5use std::ops::{Deref, DerefMut};
6use std::{
7 cmp::{Ordering, PartialEq},
8 str::FromStr,
9};
10
11#[derive(Debug, Clone, PartialEq)]
13pub struct MediaTypeProposal {
14 pub(crate) media_type: Mime,
16
17 weight: Option<f32>,
21}
22
23impl MediaTypeProposal {
24 pub fn new(media_type: impl Into<Mime>, weight: Option<f32>) -> crate::Result<Self> {
26 if let Some(weight) = weight {
27 ensure!(
28 weight.is_sign_positive() && weight <= 1.0,
29 "MediaTypeProposal should have a weight between 0.0 and 1.0"
30 )
31 }
32
33 Ok(Self {
34 media_type: media_type.into(),
35 weight,
36 })
37 }
38
39 pub fn media_type(&self) -> &Mime {
41 &self.media_type
42 }
43
44 pub fn weight(&self) -> Option<f32> {
46 self.weight
47 }
48
49 pub(crate) fn from_str(s: &str) -> crate::Result<Self> {
55 let mut media_type = Mime::from_str(s)?;
56 let weight = media_type
57 .remove_param("q")
58 .map(|param| param.as_str().parse())
59 .transpose()?;
60 Self::new(media_type, weight)
61 }
62}
63
64impl From<Mime> for MediaTypeProposal {
65 fn from(media_type: Mime) -> Self {
66 Self {
67 media_type,
68 weight: None,
69 }
70 }
71}
72
73impl From<MediaTypeProposal> for Mime {
74 fn from(accept: MediaTypeProposal) -> Self {
75 accept.media_type
76 }
77}
78
79impl PartialEq<Mime> for MediaTypeProposal {
80 fn eq(&self, other: &Mime) -> bool {
81 self.media_type == *other
82 }
83}
84
85impl PartialEq<Mime> for &MediaTypeProposal {
86 fn eq(&self, other: &Mime) -> bool {
87 self.media_type == *other
88 }
89}
90
91impl Deref for MediaTypeProposal {
92 type Target = Mime;
93 fn deref(&self) -> &Self::Target {
94 &self.media_type
95 }
96}
97
98impl DerefMut for MediaTypeProposal {
99 fn deref_mut(&mut self) -> &mut Self::Target {
100 &mut self.media_type
101 }
102}
103
104impl PartialOrd for MediaTypeProposal {
112 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
113 match (self.weight, other.weight) {
114 (Some(left), Some(right)) => left.partial_cmp(&right),
115 (Some(_), None) => Some(Ordering::Greater),
116 (None, Some(_)) => Some(Ordering::Less),
117 (None, None) => None,
118 }
119 }
120}
121
122impl From<MediaTypeProposal> for HeaderValue {
123 fn from(entry: MediaTypeProposal) -> HeaderValue {
124 let s = match entry.weight {
125 Some(weight) => format!("{};q={:.3}", entry.media_type, weight),
126 None => entry.media_type.to_string(),
127 };
128 unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
129 }
130}
131
132#[cfg(test)]
133mod test {
134 use super::*;
135 use crate::mime;
136
137 #[test]
138 fn smoke() {
139 let _ = MediaTypeProposal::new(mime::JSON, Some(0.0)).unwrap();
140 let _ = MediaTypeProposal::new(mime::XML, Some(0.5)).unwrap();
141 let _ = MediaTypeProposal::new(mime::HTML, Some(1.0)).unwrap();
142 }
143
144 #[test]
145 fn error_code_500() {
146 let err = MediaTypeProposal::new(mime::JSON, Some(1.1)).unwrap_err();
147 assert_eq!(err.status(), 500);
148
149 let err = MediaTypeProposal::new(mime::XML, Some(-0.1)).unwrap_err();
150 assert_eq!(err.status(), 500);
151
152 let err = MediaTypeProposal::new(mime::HTML, Some(-0.0)).unwrap_err();
153 assert_eq!(err.status(), 500);
154 }
155}