use crate::date::HttpDate;
use crate::etag::{ETagList, EntityTag, parse_etag_list};
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConditionalError {
Empty,
InvalidFormat,
ETagError,
DateError,
}
impl fmt::Display for ConditionalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConditionalError::Empty => write!(f, "empty conditional header"),
ConditionalError::InvalidFormat => write!(f, "invalid conditional header format"),
ConditionalError::ETagError => write!(f, "invalid etag in conditional header"),
ConditionalError::DateError => write!(f, "invalid date in conditional header"),
}
}
}
impl std::error::Error for ConditionalError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfMatch(ETagList);
impl IfMatch {
pub fn parse(input: &str) -> Result<Self, ConditionalError> {
parse_etag_list(input)
.map(IfMatch)
.map_err(|_| ConditionalError::ETagError)
}
pub fn is_any(&self) -> bool {
self.0.is_any()
}
pub fn matches(&self, etag: &EntityTag) -> bool {
self.0.contains_strong(etag)
}
pub fn etags(&self) -> &ETagList {
&self.0
}
}
impl fmt::Display for IfMatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfNoneMatch(ETagList);
impl IfNoneMatch {
pub fn parse(input: &str) -> Result<Self, ConditionalError> {
parse_etag_list(input)
.map(IfNoneMatch)
.map_err(|_| ConditionalError::ETagError)
}
pub fn is_any(&self) -> bool {
self.0.is_any()
}
pub fn matches(&self, etag: &EntityTag) -> bool {
!self.0.contains_weak(etag)
}
pub fn etags(&self) -> &ETagList {
&self.0
}
}
impl fmt::Display for IfNoneMatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfModifiedSince(HttpDate);
impl IfModifiedSince {
pub fn parse(input: &str) -> Result<Self, ConditionalError> {
HttpDate::parse(input)
.map(IfModifiedSince)
.map_err(|_| ConditionalError::DateError)
}
pub fn date(&self) -> &HttpDate {
&self.0
}
pub fn is_modified(&self, last_modified: &HttpDate) -> bool {
last_modified > &self.0
}
}
impl fmt::Display for IfModifiedSince {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfUnmodifiedSince(HttpDate);
impl IfUnmodifiedSince {
pub fn parse(input: &str) -> Result<Self, ConditionalError> {
HttpDate::parse(input)
.map(IfUnmodifiedSince)
.map_err(|_| ConditionalError::DateError)
}
pub fn date(&self) -> &HttpDate {
&self.0
}
}
impl fmt::Display for IfUnmodifiedSince {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IfRange {
ETag(EntityTag),
Date(HttpDate),
}
impl IfRange {
pub fn parse(input: &str) -> Result<Self, ConditionalError> {
let input = input.trim();
if input.is_empty() {
return Err(ConditionalError::Empty);
}
if input.starts_with('"') || input.starts_with("W/") || input.starts_with("w/") {
EntityTag::parse(input)
.map(IfRange::ETag)
.map_err(|_| ConditionalError::ETagError)
} else {
HttpDate::parse(input)
.map(IfRange::Date)
.map_err(|_| ConditionalError::DateError)
}
}
pub fn is_etag(&self) -> bool {
matches!(self, IfRange::ETag(_))
}
pub fn is_date(&self) -> bool {
matches!(self, IfRange::Date(_))
}
pub fn etag(&self) -> Option<&EntityTag> {
match self {
IfRange::ETag(e) => Some(e),
IfRange::Date(_) => None,
}
}
pub fn date(&self) -> Option<&HttpDate> {
match self {
IfRange::ETag(_) => None,
IfRange::Date(d) => Some(d),
}
}
}
impl fmt::Display for IfRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IfRange::ETag(e) => write!(f, "{}", e),
IfRange::Date(d) => write!(f, "{}", d),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_if_match_single() {
let im = IfMatch::parse("\"abc\"").unwrap();
let etag = EntityTag::strong("abc").unwrap();
assert!(im.matches(&etag));
let other = EntityTag::strong("xyz").unwrap();
assert!(!im.matches(&other));
}
#[test]
fn test_if_match_multiple() {
let im = IfMatch::parse("\"a\", \"b\", \"c\"").unwrap();
assert!(im.matches(&EntityTag::strong("b").unwrap()));
assert!(!im.matches(&EntityTag::strong("d").unwrap()));
}
#[test]
fn test_if_match_any() {
let im = IfMatch::parse("*").unwrap();
assert!(im.is_any());
assert!(im.matches(&EntityTag::strong("anything").unwrap()));
}
#[test]
fn test_if_match_weak_not_match() {
let im = IfMatch::parse("W/\"abc\"").unwrap();
let etag = EntityTag::strong("abc").unwrap();
assert!(!im.matches(&etag));
}
#[test]
fn test_if_none_match_single() {
let inm = IfNoneMatch::parse("\"abc\"").unwrap();
let etag = EntityTag::strong("abc").unwrap();
assert!(!inm.matches(&etag));
let other = EntityTag::strong("xyz").unwrap();
assert!(inm.matches(&other)); }
#[test]
fn test_if_none_match_any() {
let inm = IfNoneMatch::parse("*").unwrap();
assert!(inm.is_any());
assert!(!inm.matches(&EntityTag::strong("anything").unwrap()));
}
#[test]
fn test_if_none_match_weak() {
let inm = IfNoneMatch::parse("W/\"abc\"").unwrap();
let etag = EntityTag::strong("abc").unwrap();
assert!(!inm.matches(&etag)); }
#[test]
fn test_if_modified_since() {
let ims = IfModifiedSince::parse("Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
assert_eq!(ims.date().day(), 6);
assert_eq!(ims.date().month(), 11);
assert_eq!(ims.date().year(), 1994);
}
#[test]
fn test_if_modified_since_is_modified() {
let ims = IfModifiedSince::parse("Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
let same = HttpDate::parse("Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
assert!(!ims.is_modified(&same));
let older = HttpDate::parse("Sat, 05 Nov 1994 08:49:37 GMT").unwrap();
assert!(!ims.is_modified(&older));
let newer = HttpDate::parse("Mon, 07 Nov 1994 08:49:37 GMT").unwrap();
assert!(ims.is_modified(&newer));
}
#[test]
fn test_if_unmodified_since() {
let ius = IfUnmodifiedSince::parse("Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
assert_eq!(ius.date().day(), 6);
}
#[test]
fn test_if_range_etag() {
let ir = IfRange::parse("\"abc123\"").unwrap();
assert!(ir.is_etag());
assert_eq!(ir.etag().unwrap().tag(), "abc123");
}
#[test]
fn test_if_range_weak_etag() {
let ir = IfRange::parse("W/\"abc123\"").unwrap();
assert!(ir.is_etag());
assert!(ir.etag().unwrap().is_weak());
}
#[test]
fn test_if_range_date() {
let ir = IfRange::parse("Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
assert!(ir.is_date());
assert_eq!(ir.date().unwrap().day(), 6);
}
#[test]
fn test_if_match_display() {
let im = IfMatch::parse("\"a\", \"b\"").unwrap();
assert_eq!(im.to_string(), "\"a\", \"b\"");
}
#[test]
fn test_if_range_display() {
let ir = IfRange::parse("\"abc\"").unwrap();
assert_eq!(ir.to_string(), "\"abc\"");
}
}