rama_http_headers/common/
if_match.rs1use rama_core::telemetry::tracing;
2use rama_http_types::HeaderValue;
3use rama_utils::collections::NonEmptyVec;
4
5use super::ETag;
6use crate::util::{EntityTagRange, TryFromValues};
7
8#[derive(Clone, Debug, PartialEq)]
42pub struct IfMatch(EntityTagRange);
43
44impl crate::TypedHeader for IfMatch {
45 fn name() -> &'static ::rama_http_types::header::HeaderName {
46 &::rama_http_types::header::IF_MATCH
47 }
48}
49
50impl crate::HeaderDecode for IfMatch {
51 fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
52 where
53 I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
54 {
55 EntityTagRange::try_from_values(values).map(Self)
56 }
57}
58
59impl crate::HeaderEncode for IfMatch {
60 fn encode<E: Extend<::rama_http_types::HeaderValue>>(&self, values: &mut E) {
61 match HeaderValue::try_from(&self.0) {
62 Ok(value) => values.extend(::std::iter::once(value)),
63 Err(err) => {
64 tracing::debug!(
65 "failed to encode if-match entity-tag-range as header value: {err}"
66 );
67 }
68 }
69 }
70}
71
72impl IfMatch {
73 pub fn any() -> Self {
75 Self(EntityTagRange::Any)
76 }
77
78 pub fn is_any(&self) -> bool {
80 match self.0 {
81 EntityTagRange::Any => true,
82 EntityTagRange::Tags(..) => false,
83 }
84 }
85
86 pub fn precondition_passes(&self, etag: &ETag) -> bool {
88 self.0.matches_strong(&etag.0)
89 }
90}
91
92impl From<ETag> for IfMatch {
93 fn from(etag: ETag) -> Self {
94 Self(EntityTagRange::Tags(NonEmptyVec::new(etag.0)))
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn is_any() {
104 assert!(IfMatch::any().is_any());
105 assert!(!IfMatch::from(ETag::from_static("\"yolo\"")).is_any());
106 }
107
108 #[test]
109 fn precondition_fails() {
110 let if_match = IfMatch::from(ETag::from_static("\"foo\""));
111
112 let bar = ETag::from_static("\"bar\"");
113 let weak_foo = ETag::from_static("W/\"foo\"");
114
115 assert!(!if_match.precondition_passes(&bar));
116 assert!(!if_match.precondition_passes(&weak_foo));
117 }
118
119 #[test]
120 fn precondition_passes() {
121 let foo = ETag::from_static("\"foo\"");
122
123 let if_match = IfMatch::from(foo.clone());
124
125 assert!(if_match.precondition_passes(&foo));
126 }
127
128 #[test]
129 fn precondition_any() {
130 let foo = ETag::from_static("\"foo\"");
131
132 let if_match = IfMatch::any();
133
134 assert!(if_match.precondition_passes(&foo));
135 }
136}