use crate::ResponseMetadata;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Count {
Exact,
Planned,
Estimated,
}
impl Count {
pub(crate) const fn preference(self) -> &'static str {
match self {
Self::Exact => "count=exact",
Self::Planned => "count=planned",
Self::Estimated => "count=estimated",
}
}
}
#[derive(Debug)]
pub struct Counted<T> {
pub data: T,
pub count: u64,
pub metadata: ResponseMetadata,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum CountError {
#[error("the server did not return Content-Range for the requested count")]
MissingContentRange,
#[error("the server returned an unavailable Content-Range total")]
UnavailableTotal,
#[error("the server returned an invalid Content-Range")]
InvalidContentRange,
}
fn decimal(value: &str) -> Option<u64> {
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
value.parse().ok()
}
pub fn response_count(headers: &reqwest::header::HeaderMap) -> Result<u64, CountError> {
let mut headers = headers.get_all("content-range").iter();
let header = headers.next().ok_or(CountError::MissingContentRange)?;
if headers.next().is_some() {
return Err(CountError::InvalidContentRange);
}
let value = header
.to_str()
.map_err(|_invalid_header| CountError::InvalidContentRange)?;
let (range, total) = value
.split_once('/')
.ok_or(CountError::InvalidContentRange)?;
if range != "*" {
let (low, high) = range
.split_once('-')
.ok_or(CountError::InvalidContentRange)?;
let low = decimal(low).ok_or(CountError::InvalidContentRange)?;
let high = decimal(high).ok_or(CountError::InvalidContentRange)?;
if low > high {
return Err(CountError::InvalidContentRange);
}
}
if total == "*" {
return Err(CountError::UnavailableTotal);
}
decimal(total).ok_or(CountError::InvalidContentRange)
}