@@ -12,22 +12,41 @@ use std::option;
1212/// # Specifications
1313///
1414/// - [RFC 7232 HTTP/1.1: Conditional Requests](https://tools.ietf.org/html/rfc7232#section-2.3)
15- #[ derive( Debug ) ]
16- pub enum Etag {
17- /// An Etag using strong validation.
15+ ///
16+ /// # Examples
17+ ///
18+ /// ```
19+ /// # fn main() -> http_types::Result<()> {
20+ /// #
21+ /// use http_types::Response;
22+ /// use http_types::cache::ETag;
23+ ///
24+ /// let etag = ETag::new("0xcafebeef".to_string());
25+ ///
26+ /// let mut res = Response::new(200);
27+ /// etag.apply(&mut res);
28+ ///
29+ /// let etag = ETag::from_headers(res)?.unwrap();
30+ /// assert_eq!(etag, ETag::Strong(String::from("0xcafebeef")));
31+ /// #
32+ /// # Ok(()) }
33+ /// ```
34+ #[ derive( Debug , Clone , Eq , PartialEq ) ]
35+ pub enum ETag {
36+ /// An ETag using strong validation.
1837 Strong ( String ) ,
1938 /// An ETag using weak validation.
2039 Weak ( String ) ,
2140}
2241
23- impl Etag {
24- /// Create a new Etag that uses strong validation.
42+ impl ETag {
43+ /// Create a new ETag that uses strong validation.
2544 pub fn new ( s : String ) -> Self {
2645 debug_assert ! ( !s. contains( '\\' ) , "ETags ought to avoid backslash chars" ) ;
2746 Self :: Strong ( s)
2847 }
2948
30- /// Create a new Etag that uses weak validation.
49+ /// Create a new ETag that uses weak validation.
3150 pub fn new_weak ( s : String ) -> Self {
3251 debug_assert ! ( !s. contains( '\\' ) , "ETags ought to avoid backslash chars" ) ;
3352 Self :: Weak ( s)
@@ -44,13 +63,15 @@ impl Etag {
4463 } ;
4564
4665 // If a header is returned we can assume at least one exists.
47- let mut s = headers. iter ( ) . last ( ) . unwrap ( ) . as_str ( ) ;
66+ let s = headers. iter ( ) . last ( ) . unwrap ( ) . as_str ( ) ;
4867
49- let weak = if s. starts_with ( "/W" ) {
50- s = & s[ 2 ..] ;
51- true
52- } else {
53- false
68+ let mut weak = false ;
69+ let s = match s. strip_prefix ( "W/" ) {
70+ Some ( s) => {
71+ weak = true ;
72+ s
73+ }
74+ None => s,
5475 } ;
5576
5677 let s = match s. strip_prefix ( '"' ) . and_then ( |s| s. strip_suffix ( '"' ) ) {
@@ -86,9 +107,19 @@ impl Etag {
86107 // SAFETY: the internal string is validated to be ASCII.
87108 unsafe { HeaderValue :: from_bytes_unchecked ( s. into ( ) ) }
88109 }
110+
111+ /// Returns `true` if the ETag is a `Strong` value.
112+ pub fn is_strong ( & self ) -> bool {
113+ matches ! ( self , Self :: Strong ( _) )
114+ }
115+
116+ /// Returns `true` if the ETag is a `Weak` value.
117+ pub fn is_weak ( & self ) -> bool {
118+ matches ! ( self , Self :: Weak ( _) )
119+ }
89120}
90121
91- impl ToHeaderValues for Etag {
122+ impl ToHeaderValues for ETag {
92123 type Iter = option:: IntoIter < HeaderValue > ;
93124 fn to_header_values ( & self ) -> crate :: Result < Self :: Iter > {
94125 // A HeaderValue will always convert into itself.
@@ -99,38 +130,54 @@ impl ToHeaderValues for Etag {
99130#[ cfg( test) ]
100131mod test {
101132 use super :: * ;
102- use crate :: headers:: { Headers , CACHE_CONTROL } ;
133+ use crate :: headers:: Headers ;
103134
104135 #[ test]
105136 fn smoke ( ) -> crate :: Result < ( ) > {
106- let mut etag = Etag :: new ( "0xcafebeef" ) ;
137+ let etag = ETag :: new ( "0xcafebeef" . to_string ( ) ) ;
107138
108139 let mut headers = Headers :: new ( ) ;
109- entries . apply ( & mut headers) ;
140+ etag . apply ( & mut headers) ;
110141
111- let entries = Etag :: from_headers ( headers) ?. unwrap ( ) ;
112- let mut entries = entries. iter ( ) ;
113- assert_eq ! ( entries. next( ) . unwrap( ) , & CacheDirective :: Immutable ) ;
114- assert_eq ! ( entries. next( ) . unwrap( ) , & CacheDirective :: NoStore ) ;
142+ let etag = ETag :: from_headers ( headers) ?. unwrap ( ) ;
143+ assert_eq ! ( etag, ETag :: Strong ( String :: from( "0xcafebeef" ) ) ) ;
115144 Ok ( ( ) )
116145 }
117146
118147 #[ test]
119- fn ignore_unkonwn_directives ( ) -> crate :: Result < ( ) > {
148+ fn smoke_weak ( ) -> crate :: Result < ( ) > {
149+ let etag = ETag :: new_weak ( "0xcafebeef" . to_string ( ) ) ;
150+
120151 let mut headers = Headers :: new ( ) ;
121- headers . insert ( CACHE_CONTROL , "barrel_roll" ) ;
122- let entries = Etag :: from_headers ( headers ) ? . unwrap ( ) ;
123- let mut entries = entries . iter ( ) ;
124- assert ! ( entries . next ( ) . is_none ( ) ) ;
152+ etag . apply ( & mut headers ) ;
153+
154+ let etag = ETag :: from_headers ( headers ) ? . unwrap ( ) ;
155+ assert_eq ! ( etag , ETag :: Weak ( String :: from ( "0xcafebeef" ) ) ) ;
125156 Ok ( ( ) )
126157 }
127158
128159 #[ test]
129160 fn bad_request_on_parse_error ( ) -> crate :: Result < ( ) > {
130161 let mut headers = Headers :: new ( ) ;
131- headers. insert ( CACHE_CONTROL , "min-fresh=0.9" ) ; // floats are not supported
132- let err = Etag :: from_headers ( headers) . unwrap_err ( ) ;
162+ headers. insert ( ETAG , "<nori ate the tag. yum.>" ) ;
163+ let err = ETag :: from_headers ( headers) . unwrap_err ( ) ;
133164 assert_eq ! ( err. status( ) , 400 ) ;
134165 Ok ( ( ) )
135166 }
167+
168+ #[ test]
169+ fn validate_quotes ( ) -> crate :: Result < ( ) > {
170+ assert_entry_err ( r#""hello"# , "Invalid ETag header" ) ;
171+ assert_entry_err ( r#"hello""# , "Invalid ETag header" ) ;
172+ assert_entry_err ( r#"/O"valid content""# , "Invalid ETag header" ) ;
173+ assert_entry_err ( r#"/Wvalid content""# , "Invalid ETag header" ) ;
174+ Ok ( ( ) )
175+ }
176+
177+ fn assert_entry_err ( s : & str , msg : & str ) {
178+ let mut headers = Headers :: new ( ) ;
179+ headers. insert ( ETAG , s) ;
180+ let err = ETag :: from_headers ( headers) . unwrap_err ( ) ;
181+ assert_eq ! ( format!( "{}" , err) , msg) ;
182+ }
136183}
0 commit comments