use http::Method;
use crate::{
dav::WebDavError,
requests::{DavRequest, ParseResponseError, PreparedRequest},
};
#[derive(Debug, Clone, Copy)]
pub struct NotConfigured;
#[derive(Debug, Clone)]
pub struct WithEtag(String);
#[derive(Debug, Clone, Copy)]
pub struct Force;
#[derive(Debug, Clone)]
pub struct Delete<'a, Mode> {
href: &'a str,
mode: Mode,
}
impl<'a> Delete<'a, NotConfigured> {
#[must_use]
pub fn new(href: &'a str) -> Self {
Self {
href,
mode: NotConfigured,
}
}
#[must_use]
pub fn with_etag(self, etag: impl Into<String>) -> Delete<'a, WithEtag> {
Delete {
href: self.href,
mode: WithEtag(etag.into()),
}
}
#[must_use]
pub fn force(self) -> Delete<'a, Force> {
Delete {
href: self.href,
mode: Force,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteResponse;
impl DavRequest for Delete<'_, WithEtag> {
type Response = DeleteResponse;
type ParseError = ParseResponseError;
type Error<E> = WebDavError<E>;
fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
Ok(PreparedRequest {
method: Method::DELETE,
path: self.href.to_string(),
body: String::new(),
headers: vec![("If-Match".to_string(), self.mode.0.clone())],
})
}
fn parse_response(
&self,
parts: &http::response::Parts,
_body: &[u8],
) -> Result<Self::Response, ParseResponseError> {
if !parts.status.is_success() {
return Err(ParseResponseError::BadStatusCode(parts.status));
}
Ok(DeleteResponse)
}
}
impl DavRequest for Delete<'_, Force> {
type Response = DeleteResponse;
type ParseError = ParseResponseError;
type Error<E> = WebDavError<E>;
fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
Ok(PreparedRequest {
method: Method::DELETE,
path: self.href.to_string(),
body: String::new(),
headers: vec![],
})
}
fn parse_response(
&self,
parts: &http::response::Parts,
_body: &[u8],
) -> Result<Self::Response, ParseResponseError> {
if !parts.status.is_success() {
return Err(ParseResponseError::BadStatusCode(parts.status));
}
Ok(DeleteResponse)
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
#[test]
fn test_prepare_request_with_etag() {
let req = Delete::new("/calendars/personal/event.ics").with_etag("\"abc123\"");
let prepared = req.prepare_request().unwrap();
assert_eq!(prepared.method, Method::DELETE);
assert_eq!(prepared.path, "/calendars/personal/event.ics");
assert!(prepared.body.is_empty());
assert!(
prepared
.headers
.contains(&("If-Match".to_string(), "\"abc123\"".to_string()))
);
}
#[test]
fn test_prepare_request_force() {
let req = Delete::new("/calendars/old/").force();
let prepared = req.prepare_request().unwrap();
assert_eq!(prepared.method, Method::DELETE);
assert_eq!(prepared.path, "/calendars/old/");
assert!(prepared.body.is_empty());
assert!(prepared.headers.is_empty());
}
#[test]
fn test_parse_response_success() {
let req = Delete::new("/calendars/personal/event.ics").with_etag("\"abc123\"");
let response = http::Response::builder()
.status(StatusCode::NO_CONTENT)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"").unwrap();
assert_eq!(result, DeleteResponse);
}
#[test]
fn test_parse_response_success_ok() {
let req = Delete::new("/calendars/old/").force();
let response = http::Response::builder()
.status(StatusCode::OK)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"").unwrap();
assert_eq!(result, DeleteResponse);
}
#[test]
fn test_parse_response_not_found() {
let req = Delete::new("/calendars/nonexistent/").force();
let response = http::Response::builder()
.status(StatusCode::NOT_FOUND)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(matches!(
result,
Err(ParseResponseError::BadStatusCode(StatusCode::NOT_FOUND))
));
}
#[test]
fn test_parse_response_precondition_failed() {
let req = Delete::new("/calendars/personal/event.ics").with_etag("\"old-etag\"");
let response = http::Response::builder()
.status(StatusCode::PRECONDITION_FAILED)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(matches!(
result,
Err(ParseResponseError::BadStatusCode(
StatusCode::PRECONDITION_FAILED
))
));
}
#[test]
fn test_with_etag_accepts_string() {
let etag = String::from("\"abc123\"");
let req = Delete::new("/path").with_etag(etag);
let prepared = req.prepare_request().unwrap();
assert!(
prepared
.headers
.contains(&("If-Match".to_string(), "\"abc123\"".to_string()))
);
}
#[test]
fn test_with_etag_accepts_str() {
let req = Delete::new("/path").with_etag("\"abc123\"");
let prepared = req.prepare_request().unwrap();
assert!(
prepared
.headers
.contains(&("If-Match".to_string(), "\"abc123\"".to_string()))
);
}
}