use headers::{
ETag, Header, HeaderMap, HeaderMapExt, IfMatch, IfModifiedSince, IfNoneMatch,
IfUnmodifiedSince, LastModified,
};
use http::{Method, StatusCode};
use if_chain::if_chain;
pub struct PreconditionsEvaluator<'p> {
pub req_method: Method,
pub req_headers: &'p HeaderMap,
pub res_is_represented: bool,
pub res_last_modified: Option<&'p LastModified>,
pub selected_rep_etag: Option<&'p ETag>,
}
impl<'p> PreconditionsEvaluator<'p> {
pub fn evaluate(&self) -> PreconditionsResolvedAction {
self.step1()
.or_else(|| {
self.step2().or_else(|| {
self.step3()
.or_else(|| self.step4().or_else(|| self.step5()))
})
})
.unwrap_or_else(|| self.step6())
}
#[inline]
fn h<H: Header>(&self) -> Option<H> {
self.req_headers.typed_get()
}
fn step1(&self) -> Option<PreconditionsResolvedAction> {
self.h::<IfMatch>().and_then(|if_match| {
let passes = self
.selected_rep_etag
.map(|etag| if_match.precondition_passes(etag))
.unwrap_or(false);
if passes {
self.step3()
} else {
Some(PreconditionsResolvedAction::Return(
StatusCode::PRECONDITION_FAILED,
))
}
})
}
fn step2(&self) -> Option<PreconditionsResolvedAction> {
if_chain! {
if let Some(if_unmodified_since) = self.h::<IfUnmodifiedSince>();
if let Some(res_last_modified) = self.res_last_modified;
if self.h::<IfMatch>().is_none();
then {
let passes = if_unmodified_since
.precondition_passes((*res_last_modified).into());
if passes {
self.step3()
} else {
Some(PreconditionsResolvedAction::Return(StatusCode::PRECONDITION_FAILED))
}
}
else {None}
}
}
fn step3(&self) -> Option<PreconditionsResolvedAction> {
self.h::<IfNoneMatch>().and_then(|if_none_match| {
let passes = if (if_none_match == IfNoneMatch::any()) && self.res_is_represented {
false
} else {
self.selected_rep_etag
.map(|etag| if_none_match.precondition_passes(etag))
.unwrap_or(true)
};
if passes {
self.step5()
} else {
Some(PreconditionsResolvedAction::Return(
if [Method::GET, Method::HEAD].contains(&self.req_method) {
StatusCode::NOT_MODIFIED
} else {
StatusCode::PRECONDITION_FAILED
},
))
}
})
}
fn step4(&self) -> Option<PreconditionsResolvedAction> {
if_chain! {
if [Method::GET, Method::HEAD].contains(&self.req_method);
if let Some(if_modified_since) = self.h::<IfModifiedSince>();
if let Some(res_last_modified) = self.res_last_modified;
if self.h::<IfNoneMatch>().is_none();
then {
let passes = if_modified_since.is_modified((*res_last_modified).into());
if passes {
self.step5()
} else {
Some(PreconditionsResolvedAction::Return(StatusCode::NOT_MODIFIED))
}
}
else {
None
}
}
}
#[inline]
fn step5(&self) -> Option<PreconditionsResolvedAction> {
if [Method::GET].contains(&self.req_method) {
Some(PreconditionsResolvedAction::ApplyMethod)
} else {
None
}
}
#[inline]
fn step6(&self) -> PreconditionsResolvedAction {
PreconditionsResolvedAction::ApplyMethod
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PreconditionsResolvedAction {
ApplyMethod,
Return(StatusCode),
}
impl PreconditionsResolvedAction {
pub fn as_return(&self) -> Option<&StatusCode> {
match self {
Self::ApplyMethod => None,
Self::Return(c) => Some(c),
}
}
}