Skip to main content

rama_http_headers/common/
etag.rs

1use std::str::FromStr;
2
3use crate::util::EntityTag;
4
5/// `ETag` header, defined in [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3)
6///
7/// The `ETag` header field in a response provides the current entity-tag
8/// for the selected representation, as determined at the conclusion of
9/// handling the request.  An entity-tag is an opaque validator for
10/// differentiating between multiple representations of the same
11/// resource, regardless of whether those multiple representations are
12/// due to resource state changes over time, content negotiation
13/// resulting in multiple representations being valid at the same time,
14/// or both.  An entity-tag consists of an opaque quoted string, possibly
15/// prefixed by a weakness indicator.
16///
17/// # ABNF
18///
19/// ```text
20/// ETag       = entity-tag
21/// ```
22///
23/// # Example values
24///
25/// * `"xyzzy"`
26/// * `W/"xyzzy"`
27/// * `""`
28///
29/// # Examples
30///
31/// ```
32/// let etag = "\"xyzzy\"".parse::<rama_http_headers::ETag>().unwrap();
33/// ```
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct ETag(pub(super) EntityTag);
36
37derive_header! {
38    ETag(_),
39    name: ETAG
40}
41
42impl ETag {
43    #[cfg(test)]
44    pub(crate) fn from_static(src: &'static str) -> Self {
45        Self(EntityTag::from_static(src))
46    }
47
48    /// Returns `true` if the etag is [weak](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag#directives).
49    #[must_use]
50    pub fn is_weak(&self) -> bool {
51        self.0.is_weak()
52    }
53}
54
55rama_utils::macros::error::static_str_error! {
56    #[doc = "etag is not valid"]
57    pub struct InvalidETag;
58}
59
60impl FromStr for ETag {
61    type Err = InvalidETag;
62    fn from_str(src: &str) -> Result<Self, Self::Err> {
63        let val = src.parse().map_err(|_e| InvalidETag)?;
64
65        EntityTag::from_owned(val).map(ETag).ok_or(InvalidETag)
66    }
67}
68
69/*
70test_etag {
71    // From the RFC
72    test_header!(test1,
73        vec![b"\"xyzzy\""],
74        Some(ETag(EntityTag::new(false, "xyzzy".to_owned()))));
75    test_header!(test2,
76        vec![b"W/\"xyzzy\""],
77        Some(ETag(EntityTag::new(true, "xyzzy".to_owned()))));
78    test_header!(test3,
79        vec![b"\"\""],
80        Some(ETag(EntityTag::new(false, "".to_owned()))));
81    // Own tests
82    test_header!(test4,
83        vec![b"\"foobar\""],
84        Some(ETag(EntityTag::new(false, "foobar".to_owned()))));
85    test_header!(test5,
86        vec![b"\"\""],
87        Some(ETag(EntityTag::new(false, "".to_owned()))));
88    test_header!(test6,
89        vec![b"W/\"weak-etag\""],
90        Some(ETag(EntityTag::new(true, "weak-etag".to_owned()))));
91    test_header!(test7,
92        vec![b"W/\"\x65\x62\""],
93        Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned()))));
94    test_header!(test8,
95        vec![b"W/\"\""],
96        Some(ETag(EntityTag::new(true, "".to_owned()))));
97    test_header!(test9,
98        vec![b"no-dquotes"],
99        None::<ETag>);
100    test_header!(test10,
101        vec![b"w/\"the-first-w-is-case-sensitive\""],
102        None::<ETag>);
103    test_header!(test11,
104        vec![b""],
105        None::<ETag>);
106    test_header!(test12,
107        vec![b"\"unmatched-dquotes1"],
108        None::<ETag>);
109    test_header!(test13,
110        vec![b"unmatched-dquotes2\""],
111        None::<ETag>);
112    test_header!(test14,
113        vec![b"matched-\"dquotes\""],
114        None::<ETag>);
115    test_header!(test15,
116        vec![b"\""],
117        None::<ETag>);
118}
119*/
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn is_weak() {
127        assert!(!ETag::from_static("\"xyzzy\"").is_weak());
128        assert!(ETag::from_static("W/\"xyzzy\"").is_weak());
129    }
130}