cogo_http/header/shared/
entity.rs1use std::str::FromStr;
2use std::fmt::{self, Display};
3
4fn check_slice_validity(slice: &str) -> bool {
9 slice.bytes().all(|c|
10 c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80' && c <= b'\xff'))
11}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct EntityTag {
44 pub weak: bool,
46 tag: String
48}
49
50impl EntityTag {
51 pub fn new(weak: bool, tag: String) -> EntityTag {
55 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
56 EntityTag { weak: weak, tag: tag }
57 }
58
59 pub fn weak(tag: String) -> EntityTag {
63 EntityTag::new(true, tag)
64 }
65
66 pub fn strong(tag: String) -> EntityTag {
70 EntityTag::new(false, tag)
71 }
72
73 pub fn tag(&self) -> &str {
75 self.tag.as_ref()
76 }
77
78 pub fn set_tag(&mut self, tag: String) {
82 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
83 self.tag = tag
84 }
85
86 pub fn strong_eq(&self, other: &EntityTag) -> bool {
89 !self.weak && !other.weak && self.tag == other.tag
90 }
91
92 pub fn weak_eq(&self, other: &EntityTag) -> bool {
96 self.tag == other.tag
97 }
98
99 pub fn strong_ne(&self, other: &EntityTag) -> bool {
101 !self.strong_eq(other)
102 }
103
104 pub fn weak_ne(&self, other: &EntityTag) -> bool {
106 !self.weak_eq(other)
107 }
108}
109
110impl Display for EntityTag {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 if self.weak {
113 write!(f, "W/\"{}\"", self.tag)
114 } else {
115 write!(f, "\"{}\"", self.tag)
116 }
117 }
118}
119
120impl FromStr for EntityTag {
121 type Err = crate::Error;
122 fn from_str(s: &str) -> crate::Result<EntityTag> {
123 let length: usize = s.len();
124 let slice = &s[..];
125 if !slice.ends_with('"') {
127 return Err(crate::Error::Header);
128 }
129 if slice.starts_with('"') && check_slice_validity(&slice[1..length-1]) {
131 return Ok(EntityTag { weak: false, tag: slice[1..length-1].to_owned() });
134 } else if slice.starts_with("W/\"") && check_slice_validity(&slice[3..length-1]) {
135 return Ok(EntityTag { weak: true, tag: slice[3..length-1].to_owned() });
136 }
137 Err(crate::Error::Header)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::EntityTag;
144
145 #[test]
146 fn test_etag_parse_success() {
147 assert_eq!("\"foobar\"".parse::<EntityTag>().unwrap(),
149 EntityTag::strong("foobar".to_owned()));
150 assert_eq!("\"\"".parse::<EntityTag>().unwrap(),
151 EntityTag::strong("".to_owned()));
152 assert_eq!("W/\"weaktag\"".parse::<EntityTag>().unwrap(),
153 EntityTag::weak("weaktag".to_owned()));
154 assert_eq!("W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
155 EntityTag::weak("\x65\x62".to_owned()));
156 assert_eq!("W/\"\"".parse::<EntityTag>().unwrap(), EntityTag::weak("".to_owned()));
157 }
158
159 #[test]
160 fn test_etag_parse_failures() {
161 assert!("no-dquotes".parse::<EntityTag>().is_err());
163 assert!("w/\"the-first-w-is-case-sensitive\"".parse::<EntityTag>().is_err());
164 assert!("".parse::<EntityTag>().is_err());
165 assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
166 assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
167 assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
168 }
169
170 #[test]
171 fn test_etag_fmt() {
172 assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\"");
173 assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
174 assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"");
175 assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\"");
176 assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
177 }
178
179 #[test]
180 fn test_cmp() {
181 let mut etag1 = EntityTag::weak("1".to_owned());
188 let mut etag2 = EntityTag::weak("1".to_owned());
189 assert!(!etag1.strong_eq(&etag2));
190 assert!(etag1.weak_eq(&etag2));
191 assert!(etag1.strong_ne(&etag2));
192 assert!(!etag1.weak_ne(&etag2));
193
194 etag1 = EntityTag::weak("1".to_owned());
195 etag2 = EntityTag::weak("2".to_owned());
196 assert!(!etag1.strong_eq(&etag2));
197 assert!(!etag1.weak_eq(&etag2));
198 assert!(etag1.strong_ne(&etag2));
199 assert!(etag1.weak_ne(&etag2));
200
201 etag1 = EntityTag::weak("1".to_owned());
202 etag2 = EntityTag::strong("1".to_owned());
203 assert!(!etag1.strong_eq(&etag2));
204 assert!(etag1.weak_eq(&etag2));
205 assert!(etag1.strong_ne(&etag2));
206 assert!(!etag1.weak_ne(&etag2));
207
208 etag1 = EntityTag::strong("1".to_owned());
209 etag2 = EntityTag::strong("1".to_owned());
210 assert!(etag1.strong_eq(&etag2));
211 assert!(etag1.weak_eq(&etag2));
212 assert!(!etag1.strong_ne(&etag2));
213 assert!(!etag1.weak_ne(&etag2));
214 }
215}