rama_http_headers/common/
if_range.rs1use std::time::SystemTime;
2
3use rama_core::error::BoxError;
4use rama_core::telemetry::tracing;
5use rama_http_types::HeaderValue;
6
7use super::{ETag, LastModified};
8use crate::Error;
9use crate::util::{EntityTag, HttpDate, TryFromValues};
10
11#[derive(Clone, Debug, PartialEq)]
48pub struct IfRange(IfRange_);
49
50impl crate::TypedHeader for IfRange {
51 fn name() -> &'static ::rama_http_types::header::HeaderName {
52 &::rama_http_types::header::IF_RANGE
53 }
54}
55
56impl crate::HeaderDecode for IfRange {
57 fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
58 where
59 I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
60 {
61 crate::util::TryFromValues::try_from_values(values).map(IfRange)
62 }
63}
64
65impl crate::HeaderEncode for IfRange {
66 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
67 match HeaderValue::try_from(&self.0) {
68 Ok(value) => values.extend(::std::iter::once(value)),
69 Err(err) => {
70 tracing::debug!("failed to encode if-range value as header: {err}");
71 }
72 }
73 }
74}
75
76impl IfRange {
77 pub fn etag(tag: ETag) -> Self {
79 Self(IfRange_::EntityTag(tag.0))
80 }
81
82 #[must_use]
84 pub fn date(time: SystemTime) -> Self {
85 Self(IfRange_::Date(time.into()))
86 }
87
88 pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool {
91 match self.0 {
92 IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true),
93 IfRange_::EntityTag(ref entity) => {
94 etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true)
95 }
96 }
97 }
98}
99
100#[derive(Clone, Debug, PartialEq)]
101enum IfRange_ {
102 EntityTag(EntityTag),
104 Date(HttpDate),
106}
107
108impl TryFromValues for IfRange_ {
109 fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
110 where
111 I: Iterator<Item = &'i HeaderValue>,
112 {
113 values
114 .next()
115 .and_then(|val| {
116 if let Some(tag) = EntityTag::from_val(val) {
117 return Some(Self::EntityTag(tag));
118 }
119
120 let date = HttpDate::from_val(val)?;
121 Some(Self::Date(date))
122 })
123 .ok_or_else(Error::invalid)
124 }
125}
126
127impl<'a> TryFrom<&'a IfRange_> for HeaderValue {
128 type Error = BoxError;
129
130 fn try_from(if_range: &'a IfRange_) -> Result<Self, Self::Error> {
131 match *if_range {
132 IfRange_::EntityTag(ref tag) => Ok(tag.into()),
133 IfRange_::Date(ref date) => date.try_into(),
134 }
135 }
136}
137
138#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_is_modified_etag() {
156 let etag = ETag::from_static("\"xyzzy\"");
157 let if_range = IfRange::etag(etag.clone());
158
159 assert!(!if_range.is_modified(Some(&etag), None));
160
161 let etag = ETag::from_static("W/\"xyzzy\"");
162 assert!(if_range.is_modified(Some(&etag), None));
163 }
164}