Skip to main content

rama_http_headers/common/
if_none_match.rs

1use rama_core::telemetry::tracing;
2use rama_http_types::HeaderValue;
3use rama_utils::collections::NonEmptyVec;
4
5use super::ETag;
6use crate::util::{EntityTagRange, TryFromValues as _};
7
8/// `If-None-Match` header, defined in
9/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2)
10///
11/// The `If-None-Match` header field makes the request method conditional
12/// on a recipient cache or origin server either not having any current
13/// representation of the target resource, when the field-value is "*",
14/// or having a selected representation with an entity-tag that does not
15/// match any of those listed in the field-value.
16///
17/// A recipient MUST use the weak comparison function when comparing
18/// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags
19/// can be used for cache validation even if there have been changes to
20/// the representation data.
21///
22/// # ABNF
23///
24/// ```text
25/// If-None-Match = "*" / 1#entity-tag
26/// ```
27///
28/// # Example values
29///
30/// * `"xyzzy"`
31/// * `W/"xyzzy"`
32/// * `"xyzzy", "r2d2xxxx", "c3piozzzz"`
33/// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"`
34/// * `*`
35///
36/// # Examples
37///
38/// ```
39/// use rama_http_headers::IfNoneMatch;
40///
41/// let if_none_match = IfNoneMatch::any();
42/// ```
43#[derive(Clone, Debug, PartialEq)]
44pub struct IfNoneMatch(EntityTagRange);
45
46impl crate::TypedHeader for IfNoneMatch {
47    fn name() -> &'static ::rama_http_types::header::HeaderName {
48        &::rama_http_types::header::IF_NONE_MATCH
49    }
50}
51
52impl crate::HeaderDecode for IfNoneMatch {
53    fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
54    where
55        I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
56    {
57        EntityTagRange::try_from_values(values).map(Self)
58    }
59}
60
61impl crate::HeaderEncode for IfNoneMatch {
62    fn encode<E: Extend<::rama_http_types::HeaderValue>>(&self, values: &mut E) {
63        match HeaderValue::try_from(&self.0) {
64            Ok(value) => values.extend(::std::iter::once(value)),
65            Err(err) => {
66                tracing::debug!(
67                    "failed to encode if-none-match entity-tag-range as header value: {err}"
68                );
69            }
70        }
71    }
72}
73
74impl IfNoneMatch {
75    /// Create a new `If-None-Match: *` header.
76    pub fn any() -> Self {
77        Self(EntityTagRange::Any)
78    }
79
80    /// Checks whether the ETag passes this precondition.
81    pub fn precondition_passes(&self, etag: &ETag) -> bool {
82        !self.0.matches_weak(&etag.0)
83    }
84}
85
86impl From<ETag> for IfNoneMatch {
87    fn from(etag: ETag) -> Self {
88        Self(EntityTagRange::Tags(NonEmptyVec::new(etag.0)))
89    }
90}
91
92/*
93test_if_none_match {
94    test_header!(test1, vec![b"\"xyzzy\""]);
95    test_header!(test2, vec![b"W/\"xyzzy\""]);
96    test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]);
97    test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]);
98    test_header!(test5, vec![b"*"]);
99}
100*/
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn precondition_fails() {
108        let foo = ETag::from_static("\"foo\"");
109        let weak_foo = ETag::from_static("W/\"foo\"");
110
111        let if_none = IfNoneMatch::from(foo.clone());
112
113        assert!(!if_none.precondition_passes(&foo));
114        assert!(!if_none.precondition_passes(&weak_foo));
115    }
116
117    #[test]
118    fn precondition_passes() {
119        let if_none = IfNoneMatch::from(ETag::from_static("\"foo\""));
120
121        let bar = ETag::from_static("\"bar\"");
122        let weak_bar = ETag::from_static("W/\"bar\"");
123
124        assert!(if_none.precondition_passes(&bar));
125        assert!(if_none.precondition_passes(&weak_bar));
126    }
127
128    #[test]
129    fn precondition_any() {
130        let foo = ETag::from_static("\"foo\"");
131
132        let if_none = IfNoneMatch::any();
133
134        assert!(!if_none.precondition_passes(&foo));
135    }
136}