#![deny(clippy::pedantic)]
#![deny(clippy::unwrap_used)]
#![cfg_attr(not(test), deny(clippy::indexing_slicing))]
#![allow(clippy::module_name_repetitions)]
#![forbid(unsafe_code)]
#![forbid(clippy::print_stdout)]
use std::{backtrace::Backtrace, str::FromStr, sync::Arc};
pub use libdav;
#[cfg(feature = "jmap")]
pub use libjmap;
pub mod addressbook;
mod atomic;
pub mod base;
pub mod caldav;
pub mod calendar;
pub mod carddav;
mod dav;
pub mod disco;
pub mod hash;
#[cfg(feature = "jmap")]
pub mod jmap;
pub mod readonly;
mod simple_component;
pub mod sync;
pub mod vdir;
pub mod watch;
pub mod webcal;
type Result<T, E = crate::Error> = std::result::Result<T, E>;
#[derive(Debug, PartialEq)]
pub enum ErrorKind {
DoesNotExist,
NotACollection,
NotAStorage,
AccessDenied,
Io,
InvalidData,
InvalidInput,
ReadOnly,
CollectionNotEmpty,
PreconditionFailed,
Unavailable,
Unsupported,
Uncategorised,
}
impl ErrorKind {
fn error<E>(self, source: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::new(self, source)
}
#[must_use]
const fn as_str(&self) -> &'static str {
match self {
ErrorKind::DoesNotExist => "resource does not exist",
ErrorKind::NotACollection => "resource exists, but is not a collection",
ErrorKind::NotAStorage => "resource exists, but is not a storage",
ErrorKind::AccessDenied => "access to the resource was denied",
ErrorKind::Io => "input/output error",
ErrorKind::InvalidData => "operation returned data, but it is not valid",
ErrorKind::InvalidInput => "input data is invalid",
ErrorKind::ReadOnly => "the resource is read-only",
ErrorKind::CollectionNotEmpty => "the collection is not empty",
ErrorKind::PreconditionFailed => "a required condition was not met",
ErrorKind::Unavailable => "the operation is not possible on this instance",
ErrorKind::Unsupported => "the operation is not supported",
ErrorKind::Uncategorised => "uncategorised error",
}
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
backtrace: Backtrace,
}
impl Error {
fn new<E>(kind: ErrorKind, source: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error {
kind,
source: Some(source.into()),
backtrace: Backtrace::capture(),
}
}
pub fn backtrace(&self) -> &Backtrace {
&self.backtrace
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Error {
kind,
source: None,
backtrace: Backtrace::capture(),
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
let kind = match value.kind() {
std::io::ErrorKind::NotFound => ErrorKind::DoesNotExist,
std::io::ErrorKind::PermissionDenied => ErrorKind::AccessDenied,
std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput,
std::io::ErrorKind::InvalidData => ErrorKind::InvalidData,
_ => ErrorKind::Io,
};
Error {
kind,
source: Some(value.into()),
backtrace: Backtrace::capture(),
}
}
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.write_str(self.as_str())
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.source {
Some(ref s) => write!(fmt, "{}: {}", self.kind, s),
None => self.kind.fmt(fmt),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(e) => Some(e.as_ref()),
None => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Etag(String);
impl Etag {
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl<T> From<T> for Etag
where
String: From<T>,
{
fn from(value: T) -> Self {
Etag(value.into())
}
}
impl AsRef<str> for Etag {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Etag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
pub type Href = String;
#[derive(PartialEq, Debug, Clone, Eq, Hash)]
pub struct CollectionId(Arc<str>);
impl AsRef<str> for CollectionId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for CollectionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, thiserror::Error)]
pub enum CollectionIdError {
#[error("collection id must not contain a slash")]
Slash,
#[error("collection id must not be '..'")]
DoublePeriod,
#[error("collection id must not be '.'")]
SinglePeriod,
}
impl FromStr for CollectionId {
type Err = CollectionIdError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
s if s.chars().any(|c| c == '/') => Err(CollectionIdError::Slash),
".." => Err(CollectionIdError::DoublePeriod),
"." => Err(CollectionIdError::SinglePeriod),
s => Ok(CollectionId(Arc::from(s))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ItemKind {
AddressBook,
Calendar,
}