use http::{Method, StatusCode};
use crate::{
CheckSupportError,
requests::{DavRequest, PreparedRequest},
};
#[derive(thiserror::Error, Debug)]
pub enum CheckSupportParseError {
#[error("DAV header missing from the response")]
MissingHeader,
#[error("requested support not advertised by the server")]
NotAdvertised,
#[error("DAV header is not a valid string: {0}")]
HeaderNotAscii(#[from] http::header::ToStrError),
#[error("http request returned {0}")]
BadStatusCode(StatusCode),
}
impl From<StatusCode> for CheckSupportParseError {
fn from(status: StatusCode) -> Self {
CheckSupportParseError::BadStatusCode(status)
}
}
pub struct CheckSupport<'a> {
uri: &'a hyper::Uri,
expectation: &'a str,
}
impl<'a> CheckSupport<'a> {
#[must_use]
pub fn new(uri: &'a hyper::Uri, expectation: &'a str) -> Self {
Self { uri, expectation }
}
#[must_use]
pub fn caldav(uri: &'a hyper::Uri) -> Self {
Self::new(uri, "calendar-access")
}
#[must_use]
pub fn carddav(uri: &'a hyper::Uri) -> Self {
Self::new(uri, "addressbook")
}
}
pub type CheckSupportResponse = ();
impl DavRequest for CheckSupport<'_> {
type Response = CheckSupportResponse;
type ParseError = CheckSupportParseError;
type Error<E> = CheckSupportError<E>;
fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
Ok(PreparedRequest {
method: Method::OPTIONS,
path: self.uri.path().to_string(),
body: String::new(),
headers: vec![],
})
}
fn parse_response(
&self,
parts: &http::response::Parts,
_body: &[u8],
) -> Result<Self::Response, Self::ParseError> {
if !parts.status.is_success() {
return Err(CheckSupportParseError::BadStatusCode(parts.status));
}
let header = parts
.headers
.get("DAV")
.ok_or(CheckSupportParseError::MissingHeader)?
.to_str()?;
log::debug!("DAV header: '{header}'");
if header
.split(',')
.any(|part| part.trim() == self.expectation)
{
Ok(())
} else {
Err(CheckSupportParseError::NotAdvertised)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
#[test]
fn test_prepare_request() {
let uri = hyper::Uri::from_static("/dav/");
let req = CheckSupport::new(&uri, "calendar-access");
let prepared = req.prepare_request().unwrap();
assert_eq!(prepared.method, Method::OPTIONS);
assert_eq!(prepared.path, "/dav/");
assert_eq!(prepared.body, "");
assert_eq!(prepared.headers, vec![]);
}
#[test]
fn test_parse_response_success() {
let uri = hyper::Uri::from_static("/");
let req = CheckSupport::new(&uri, "calendar-access");
let response = http::Response::builder()
.status(StatusCode::OK)
.header("DAV", "1, 2, calendar-access")
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(result.is_ok());
}
#[test]
fn test_parse_response_missing_header() {
let uri = hyper::Uri::from_static("/");
let req = CheckSupport::new(&uri, "calendar-access");
let response = http::Response::builder()
.status(StatusCode::OK)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(result.is_err());
assert!(matches!(result, Err(CheckSupportParseError::MissingHeader)));
}
#[test]
fn test_parse_response_not_advertised() {
let uri = hyper::Uri::from_static("/");
let req = CheckSupport::new(&uri, "calendar-access");
let response = http::Response::builder()
.status(StatusCode::OK)
.header("DAV", "1, 2, 3")
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(result.is_err());
assert!(matches!(result, Err(CheckSupportParseError::NotAdvertised)));
}
#[test]
fn test_parse_response_bad_status() {
let uri = hyper::Uri::from_static("/");
let req = CheckSupport::new(&uri, "calendar-access");
let response = http::Response::builder()
.status(StatusCode::NOT_FOUND)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(result.is_err());
}
}