Expand description
§mailrs-dav
Server-side CalDAV (RFC 4791) and CardDAV (RFC 6352) handlers for
Rust mail / calendar / contacts servers — framework-agnostic, BYO data
layer via the CalendarStore and AddressBookStore traits.
At the time of writing this is the only standalone CalDAV / CardDAV server-side library on crates.io.
§What’s covered
- Principal discovery (
principal::principal_propfind) — PROPFIND on/dav/returnscurrent-user-principal,calendar-home-set,addressbook-home-set,principal-URL,supported-report-set. - CalDAV (
caldav) — calendar home + collection PROPFIND, REPORT (calendar-multiget / calendar-query), GET / PUT / DELETE on events,If-Match/If-None-Match: *precondition handling. - CardDAV (
carddav) — same shape for address books and contacts. - Pure parsing helpers (
parse) — iCalendar / vCard field extraction,Depthheader, multiget UID extraction. - OPTIONS / multistatus / etag (
xml).
§What’s not
- Calendar-query time-range filters (RFC 4791 §9.7). Most clients work
fine with the “return everything” form this crate emits; if you need
filtered queries, layer them on top of
crate::store::CalendarStore::list_events. - Free/busy reports (RFC 4791 §7.10) — separate spec, not implemented.
- VTIMEZONE / scheduling extensions (RFC 6638) — out of scope; the raw icalendar body round-trips intact, so clients that emit VTIMEZONE can still GET back what they PUT.
- ACL / privilege management (RFC 3744). The handlers emit a fixed
<D:all/>privilege set for the authenticated owner. - HTTP auth, routing, framework integration. This crate takes a
resolved
userandcalendar_id/book_id; the auth and route parsing live in your server-side wrapper.
§Quick start
use async_trait::async_trait;
use mailrs_dav::{
caldav, carddav,
store::{CalendarStore, AddressBookStore, StoreError},
types::{Calendar, Event, AddressBook, Contact, PutResult},
};
struct MyStore;
#[async_trait]
impl CalendarStore for MyStore {
async fn list_calendars(&self, _user: &str) -> Result<Vec<Calendar>, StoreError> { Ok(vec![]) }
async fn get_calendar(&self, _: &str, _: &str) -> Result<Option<Calendar>, StoreError> { Ok(None) }
async fn list_events(&self, _: i64) -> Result<Vec<Event>, StoreError> { Ok(vec![]) }
async fn get_event(&self, _: i64, _: &str) -> Result<Option<Event>, StoreError> { Ok(None) }
async fn event_etag(&self, _: i64, _: &str) -> Result<Option<String>, StoreError> { Ok(None) }
async fn put_event(&self, _: i64, _: &str, _: &str, etag: &str) -> Result<PutResult, StoreError> {
Ok(PutResult { created: true, etag: etag.into() })
}
async fn delete_event(&self, _: i64, _: &str) -> Result<bool, StoreError> { Ok(false) }
async fn ensure_default_calendar(&self, _: &str) -> Result<(), StoreError> { Ok(()) }
}
let store = MyStore;
// PROPFIND on a calendar collection (calendar id 42, Depth: 1)
let resp = caldav::calendar_propfind(&store, "alice@example.com", "Work", 42, 1)
.await
.unwrap();
assert_eq!(resp.status, 207);§How it slots into axum
ⓘ
use std::sync::Arc;
use axum::http::StatusCode;
use mailrs_dav::{caldav, xml::DavResponse};
async fn axum_event_get(
// resolved by your auth + routing layer
store: Arc<dyn mailrs_dav::store::CalendarStore>,
calendar_id: i64,
uid: String,
) -> impl axum::response::IntoResponse {
match caldav::event_get(store.as_ref(), calendar_id, &uid).await {
Ok(r) => into_axum(r),
Err(e) => into_axum(e.to_dav_response()),
}
}
fn into_axum(r: DavResponse) -> axum::response::Response {
let mut builder = axum::http::Response::builder()
.status(StatusCode::from_u16(r.status).unwrap());
for (k, v) in r.headers { builder = builder.header(k, v); }
builder.body(axum::body::Body::from(r.body)).unwrap()
}Re-exports§
pub use error::DavError;pub use store::AddressBookStore;pub use store::CalendarStore;pub use store::StoreError;pub use types::AddressBook;pub use types::Calendar;pub use types::Contact;pub use types::Event;pub use types::PutResult;pub use xml::DavResponse;pub use xml::etag_of;pub use xml::multistatus;pub use xml::options_response;pub use xml::xml_escape;
Modules§
- caldav
- CalDAV (RFC 4791) request handlers.
- carddav
- CardDAV (RFC 6352) request handlers.
- error
- CalDAV / CardDAV error variants used by handler return values.
- fixtures
- In-memory
CalendarStoreandAddressBookStoreimplementations suitable for tests, examples, and downstream-consumer test harnesses. - parse
- Pure parsing helpers: iCalendar / vCard line scrapers + the WebDAV
Depthheader parser. No async, no I/O. - principal
/dav/principal PROPFIND — the entry point CalDAV / CardDAV clients hit after the/.well-known/{caldav,carddav}redirect.- store
CalendarStoreandAddressBookStoretraits: the abstraction layer between DAV handlers and any backing store (PostgreSQL, SQLite, files, etc.).- types
- Minimal CalDAV / CardDAV-facing data types.
- xml
- Framework-agnostic HTTP response representation plus the small XML helpers that DAV handlers compose multistatus / propstat bodies from.