use std::str::FromStr;
use http::HeaderValue;
use http::header::InvalidHeaderValue;
use super::etag::{ETag, ParseETagError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ETagCondition {
ETag(ETag),
Any,
}
#[derive(Debug, thiserror::Error)]
pub enum ParseETagConditionError {
#[error("ParseETagConditionError: InvalidFormat")]
InvalidFormat,
#[error("ParseETagConditionError: InvalidChar")]
InvalidChar,
#[error("ParseETagConditionError: {0}")]
ETagError(#[from] ParseETagError),
}
impl ETagCondition {
pub fn parse_http_header(src: &[u8]) -> Result<Self, ParseETagConditionError> {
if src == b"*" {
return Ok(ETagCondition::Any);
}
let etag = ETag::parse_http_header(src)?;
Ok(ETagCondition::ETag(etag))
}
pub fn to_http_header(&self) -> Result<HeaderValue, InvalidHeaderValue> {
match self {
ETagCondition::ETag(etag) => etag.to_http_header(),
ETagCondition::Any => HeaderValue::try_from("*"),
}
}
#[must_use]
pub fn as_etag(&self) -> Option<&ETag> {
match self {
ETagCondition::ETag(etag) => Some(etag),
ETagCondition::Any => None,
}
}
#[must_use]
pub fn into_etag(self) -> Option<ETag> {
match self {
ETagCondition::ETag(etag) => Some(etag),
ETagCondition::Any => None,
}
}
#[must_use]
pub fn is_any(&self) -> bool {
matches!(self, ETagCondition::Any)
}
}
impl FromStr for ETagCondition {
type Err = ParseETagConditionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_http_header(s.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::{ETag, ETagCondition, ParseETagConditionError};
#[test]
fn parse_wildcard() {
let cond = ETagCondition::parse_http_header(b"*").expect("parse wildcard");
assert!(cond.is_any());
assert_eq!(cond.as_etag(), None);
}
#[test]
fn parse_strong_etag() {
let cond = ETagCondition::parse_http_header(b"\"abc123\"").expect("parse strong etag");
assert!(!cond.is_any());
let etag = cond.as_etag().expect("should be etag");
assert_eq!(etag.as_strong(), Some("abc123"));
}
#[test]
fn parse_weak_etag() {
let cond = ETagCondition::parse_http_header(b"W/\"xyz\"").expect("parse weak etag");
assert!(!cond.is_any());
let etag = cond.as_etag().expect("should be etag");
assert_eq!(etag.as_weak(), Some("xyz"));
}
#[test]
fn to_header_wildcard() {
let cond = ETagCondition::Any;
let hv = cond.to_http_header().expect("wildcard header");
assert_eq!(hv.as_bytes(), b"*");
}
#[test]
fn to_header_strong_etag() {
let cond = ETagCondition::ETag(ETag::Strong("abc123".to_string()));
let hv = cond.to_http_header().expect("strong etag header");
assert_eq!(hv.as_bytes(), b"\"abc123\"");
}
#[test]
fn to_header_weak_etag() {
let cond = ETagCondition::ETag(ETag::Weak("xyz".to_string()));
let hv = cond.to_http_header().expect("weak etag header");
assert_eq!(hv.as_bytes(), b"W/\"xyz\"");
}
#[test]
fn parse_and_header_roundtrip() {
let cases = [("*", true), ("\"abc\"", false), ("W/\"xyz\"", false)];
for (input, is_any) in cases {
let cond = ETagCondition::parse_http_header(input.as_bytes()).expect("parse");
assert_eq!(cond.is_any(), is_any);
let hv = cond.to_http_header().expect("to header");
let parsed_back = ETagCondition::parse_http_header(hv.as_bytes()).expect("parse back");
assert_eq!(cond, parsed_back);
}
}
#[test]
fn from_str_trait() {
let cond: ETagCondition = "*".parse().expect("parse wildcard from str");
assert!(cond.is_any());
let cond: ETagCondition = "\"abc123\"".parse().expect("parse strong from str");
assert!(!cond.is_any());
let etag = cond.as_etag().expect("should be etag");
assert_eq!(etag.as_strong(), Some("abc123"));
let cond: ETagCondition = "W/\"xyz\"".parse().expect("parse weak from str");
assert!(!cond.is_any());
let etag = cond.as_etag().expect("should be etag");
assert_eq!(etag.as_weak(), Some("xyz"));
}
#[test]
fn parse_invalid() {
let err = ETagCondition::parse_http_header(b"").unwrap_err();
assert!(matches!(
err,
ParseETagConditionError::InvalidFormat | ParseETagConditionError::ETagError(_)
));
let err = ETagCondition::parse_http_header(b"**").unwrap_err();
assert!(matches!(
err,
ParseETagConditionError::InvalidFormat | ParseETagConditionError::ETagError(_)
));
let err = ETagCondition::parse_http_header(b"* ").unwrap_err();
assert!(matches!(
err,
ParseETagConditionError::InvalidFormat | ParseETagConditionError::ETagError(_)
));
let err = ETagCondition::parse_http_header(b"\"unclosed").unwrap_err();
assert!(matches!(
err,
ParseETagConditionError::InvalidFormat | ParseETagConditionError::ETagError(_)
));
}
#[test]
fn parse_unquoted_values() {
let cond = ETagCondition::parse_http_header(b"ABCORZ").expect("parse simple string");
assert_eq!(cond.as_etag().unwrap().as_strong(), Some("ABCORZ"));
let cond = ETagCondition::parse_http_header(b"4fcec74691ff529f6d016ec3629ff11b").expect("parse md5 hash");
assert_eq!(cond.as_etag().unwrap().as_strong(), Some("4fcec74691ff529f6d016ec3629ff11b"));
let cond = ETagCondition::parse_http_header(b"4fcec74691ff529f6d016ec3629ff11b-5").expect("parse multipart etag");
assert_eq!(cond.as_etag().unwrap().as_strong(), Some("4fcec74691ff529f6d016ec3629ff11b-5"));
let cond = ETagCondition::parse_http_header(b"a").expect("parse single char");
assert!(!cond.is_any()); assert_eq!(cond.as_etag().unwrap().as_strong(), Some("a"));
let cond = ETagCondition::parse_http_header(b"1").expect("parse single digit");
assert!(!cond.is_any()); assert_eq!(cond.as_etag().unwrap().as_strong(), Some("1"));
}
}