requiem_http/header/shared/
entity.rs1use std::fmt::{self, Display, Write};
2use std::str::FromStr;
3
4use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
5
6fn check_slice_validity(slice: &str) -> bool {
11 slice
12 .bytes()
13 .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80'))
14}
15
16#[derive(Clone, Debug, Eq, PartialEq)]
48pub struct EntityTag {
49 pub weak: bool,
51 tag: String,
53}
54
55impl EntityTag {
56 pub fn new(weak: bool, tag: String) -> EntityTag {
60 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
61 EntityTag { weak, tag }
62 }
63
64 pub fn weak(tag: String) -> EntityTag {
68 EntityTag::new(true, tag)
69 }
70
71 pub fn strong(tag: String) -> EntityTag {
75 EntityTag::new(false, tag)
76 }
77
78 pub fn tag(&self) -> &str {
80 self.tag.as_ref()
81 }
82
83 pub fn set_tag(&mut self, tag: String) {
87 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
88 self.tag = tag
89 }
90
91 pub fn strong_eq(&self, other: &EntityTag) -> bool {
94 !self.weak && !other.weak && self.tag == other.tag
95 }
96
97 pub fn weak_eq(&self, other: &EntityTag) -> bool {
101 self.tag == other.tag
102 }
103
104 pub fn strong_ne(&self, other: &EntityTag) -> bool {
106 !self.strong_eq(other)
107 }
108
109 pub fn weak_ne(&self, other: &EntityTag) -> bool {
111 !self.weak_eq(other)
112 }
113}
114
115impl Display for EntityTag {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 if self.weak {
118 write!(f, "W/\"{}\"", self.tag)
119 } else {
120 write!(f, "\"{}\"", self.tag)
121 }
122 }
123}
124
125impl FromStr for EntityTag {
126 type Err = crate::error::ParseError;
127
128 fn from_str(s: &str) -> Result<EntityTag, crate::error::ParseError> {
129 let length: usize = s.len();
130 let slice = &s[..];
131 if !slice.ends_with('"') || slice.len() < 2 {
133 return Err(crate::error::ParseError::Header);
134 }
135 if slice.len() >= 2
137 && slice.starts_with('"')
138 && check_slice_validity(&slice[1..length - 1])
139 {
140 return Ok(EntityTag {
143 weak: false,
144 tag: slice[1..length - 1].to_owned(),
145 });
146 } else if slice.len() >= 4
147 && slice.starts_with("W/\"")
148 && check_slice_validity(&slice[3..length - 1])
149 {
150 return Ok(EntityTag {
151 weak: true,
152 tag: slice[3..length - 1].to_owned(),
153 });
154 }
155 Err(crate::error::ParseError::Header)
156 }
157}
158
159impl IntoHeaderValue for EntityTag {
160 type Error = InvalidHeaderValue;
161
162 fn try_into(self) -> Result<HeaderValue, Self::Error> {
163 let mut wrt = Writer::new();
164 write!(wrt, "{}", self).unwrap();
165 HeaderValue::from_maybe_shared(wrt.take())
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::EntityTag;
172
173 #[test]
174 fn test_etag_parse_success() {
175 assert_eq!(
177 "\"foobar\"".parse::<EntityTag>().unwrap(),
178 EntityTag::strong("foobar".to_owned())
179 );
180 assert_eq!(
181 "\"\"".parse::<EntityTag>().unwrap(),
182 EntityTag::strong("".to_owned())
183 );
184 assert_eq!(
185 "W/\"weaktag\"".parse::<EntityTag>().unwrap(),
186 EntityTag::weak("weaktag".to_owned())
187 );
188 assert_eq!(
189 "W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
190 EntityTag::weak("\x65\x62".to_owned())
191 );
192 assert_eq!(
193 "W/\"\"".parse::<EntityTag>().unwrap(),
194 EntityTag::weak("".to_owned())
195 );
196 }
197
198 #[test]
199 fn test_etag_parse_failures() {
200 assert!("no-dquotes".parse::<EntityTag>().is_err());
202 assert!("w/\"the-first-w-is-case-sensitive\""
203 .parse::<EntityTag>()
204 .is_err());
205 assert!("".parse::<EntityTag>().is_err());
206 assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
207 assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
208 assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
209 }
210
211 #[test]
212 fn test_etag_fmt() {
213 assert_eq!(
214 format!("{}", EntityTag::strong("foobar".to_owned())),
215 "\"foobar\""
216 );
217 assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
218 assert_eq!(
219 format!("{}", EntityTag::weak("weak-etag".to_owned())),
220 "W/\"weak-etag\""
221 );
222 assert_eq!(
223 format!("{}", EntityTag::weak("\u{0065}".to_owned())),
224 "W/\"\x65\""
225 );
226 assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
227 }
228
229 #[test]
230 fn test_cmp() {
231 let mut etag1 = EntityTag::weak("1".to_owned());
238 let mut etag2 = EntityTag::weak("1".to_owned());
239 assert!(!etag1.strong_eq(&etag2));
240 assert!(etag1.weak_eq(&etag2));
241 assert!(etag1.strong_ne(&etag2));
242 assert!(!etag1.weak_ne(&etag2));
243
244 etag1 = EntityTag::weak("1".to_owned());
245 etag2 = EntityTag::weak("2".to_owned());
246 assert!(!etag1.strong_eq(&etag2));
247 assert!(!etag1.weak_eq(&etag2));
248 assert!(etag1.strong_ne(&etag2));
249 assert!(etag1.weak_ne(&etag2));
250
251 etag1 = EntityTag::weak("1".to_owned());
252 etag2 = EntityTag::strong("1".to_owned());
253 assert!(!etag1.strong_eq(&etag2));
254 assert!(etag1.weak_eq(&etag2));
255 assert!(etag1.strong_ne(&etag2));
256 assert!(!etag1.weak_ne(&etag2));
257
258 etag1 = EntityTag::strong("1".to_owned());
259 etag2 = EntityTag::strong("1".to_owned());
260 assert!(etag1.strong_eq(&etag2));
261 assert!(etag1.weak_eq(&etag2));
262 assert!(!etag1.strong_ne(&etag2));
263 assert!(!etag1.weak_ne(&etag2));
264 }
265}