Files
Stalwart/crates/dav-proto/src/schema/response.rs
2025-05-16 16:20:05 +02:00

403 lines
13 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Display;
use calcard::{
icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty},
vcard::{VCardParameterName, VCardProperty},
};
use hyper::StatusCode;
use super::{
property::{DavProperty, Privilege},
request::{DavPropertyValue, Filter},
Namespaces,
};
pub struct MultiStatus {
pub namespaces: Namespaces,
pub response: List<Response>,
pub response_description: Option<ResponseDescription>,
pub sync_token: Option<SyncToken>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct Response {
pub href: Href,
pub typ: ResponseType,
pub error: Option<Condition>,
pub response_description: Option<ResponseDescription>,
pub location: Option<Location>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum ResponseType {
PropStat(List<PropStat>),
Status { href: List<Href>, status: Status },
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Status(pub StatusCode);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Location(pub Href);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct ResponseDescription(pub String);
#[repr(transparent)]
pub struct SyncToken(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Href(pub String);
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct List<T: Display>(pub Vec<T>);
pub struct MkColResponse {
pub namespaces: Namespaces,
pub propstat: List<PropStat>,
pub mkcalendar: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct PropStat {
pub prop: Prop,
pub status: Status,
pub error: Option<Condition>,
pub response_description: Option<ResponseDescription>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Prop(pub List<DavPropertyValue>);
pub struct PropResponse {
pub namespaces: Namespaces,
pub properties: List<DavPropertyValue>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct SupportedPrivilege {
pub privilege: Privilege,
pub abstract_: bool,
pub description: String,
pub supported_privilege: List<SupportedPrivilege>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct Ace {
pub principal: Principal,
pub invert: bool,
pub grant_deny: GrantDeny,
pub protected: bool,
pub inherited: Option<Href>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum GrantDeny {
Grant(List<Privilege>),
Deny(List<Privilege>),
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum Principal {
Href(Href),
Response(Response),
All,
#[default]
Authenticated,
Unauthenticated,
Property(List<DavPropertyValue>),
Self_,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct AclRestrictions {
pub grant_only: bool,
pub no_invert: bool,
pub deny_before_grant: bool,
pub required_principal: Option<RequiredPrincipal>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum RequiredPrincipal {
All,
Authenticated,
Unauthenticated,
Self_,
Href(List<Href>),
Property(Vec<DavPropertyValue>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct PrincipalSearchPropertySet {
pub namespaces: Namespaces,
pub properties: List<PrincipalSearchProperty>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct PrincipalSearchProperty {
pub name: DavProperty,
pub description: String,
}
pub struct ErrorResponse {
pub namespaces: Namespaces,
pub error: Condition,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum Condition {
Base(BaseCondition),
Cal(CalCondition),
Card(CardCondition),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum BaseCondition {
NoConflictingLock(List<Href>),
LockTokenSubmitted(List<Href>),
LockTokenMatchesRequestUri,
CannotModifyProtectedProperty,
NoExternalEntities,
PreservedLiveProperties,
PropFindFiniteDepth,
ResourceMustBeNull,
NeedPrivileges(List<Resource>),
NoAceConflict,
NoProtectedAceConflict,
NoInheritedAceConflict,
LimitedNumberOfAces,
DenyBeforeGrant,
GrantOnly,
NoInvert,
NoAbstract,
NotSupportedPrivilege,
MissingRequiredPrincipal,
RecognizedPrincipal,
AllowedPrincipal,
NumberOfMatchesWithinLimit,
QuotaNotExceeded,
ValidResourceType,
ValidSyncToken,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct Resource {
pub href: Href,
pub privilege: Privilege,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum CalCondition {
CalendarCollectionLocationOk,
ValidCalendarData,
ValidFilter,
ValidCalendarObjectResource,
ValidTimezone,
NoUidConflict(Href),
InitializeCalendarCollection,
SupportedCalendarData,
SupportedFilter(
Vec<Filter<Vec<ICalendarComponentType>, ICalendarProperty, ICalendarParameterName>>,
),
SupportedCollation(String),
MinDateTime,
MaxDateTime,
MaxResourceSize(u32),
MaxInstances,
MaxAttendeesPerInstance,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum CardCondition {
SupportedAddressData,
SupportedAddressDataConversion,
SupportedFilter(Vec<Filter<(), VCardProperty, VCardParameterName>>),
SupportedCollation(String),
ValidAddressData,
NoUidConflict(Href),
MaxResourceSize(u32),
AddressBookCollectionLocationOk,
}
impl BaseCondition {
pub fn status(&self) -> StatusCode {
match self {
BaseCondition::NoConflictingLock(_) => StatusCode::LOCKED,
BaseCondition::CannotModifyProtectedProperty => StatusCode::FORBIDDEN,
BaseCondition::LockTokenSubmitted(_) => StatusCode::LOCKED,
BaseCondition::LockTokenMatchesRequestUri => StatusCode::CONFLICT,
BaseCondition::NoExternalEntities => StatusCode::FORBIDDEN,
BaseCondition::PreservedLiveProperties => StatusCode::CONFLICT,
BaseCondition::PropFindFiniteDepth => StatusCode::FORBIDDEN,
BaseCondition::ResourceMustBeNull => StatusCode::CONFLICT,
BaseCondition::NeedPrivileges(_) => StatusCode::FORBIDDEN,
BaseCondition::NumberOfMatchesWithinLimit => StatusCode::FORBIDDEN,
_ => StatusCode::FORBIDDEN,
}
}
}
impl From<String> for Href {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for Href {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl MultiStatus {
pub fn is_empty(&self) -> bool {
self.response.0.is_empty()
}
}
impl BaseCondition {
pub fn display_name(&self) -> &'static str {
match self {
BaseCondition::NoConflictingLock(_) => "NoConflictingLock",
BaseCondition::CannotModifyProtectedProperty => "CannotModifyProtectedProperty",
BaseCondition::LockTokenSubmitted(_) => "LockTokenSubmitted",
BaseCondition::LockTokenMatchesRequestUri => "LockTokenMatchesRequestUri",
BaseCondition::NoExternalEntities => "NoExternalEntities",
BaseCondition::PreservedLiveProperties => "PreservedLiveProperties",
BaseCondition::PropFindFiniteDepth => "PropFindFiniteDepth",
BaseCondition::ResourceMustBeNull => "ResourceMustBeNull",
BaseCondition::NeedPrivileges(_) => "NeedPrivileges",
BaseCondition::NoAceConflict => "NoAceConflict",
BaseCondition::NoProtectedAceConflict => "NoProtectedAceConflict",
BaseCondition::NoInheritedAceConflict => "NoInheritedAceConflict",
BaseCondition::LimitedNumberOfAces => "LimitedNumberOfAces",
BaseCondition::DenyBeforeGrant => "DenyBeforeGrant",
BaseCondition::GrantOnly => "GrantOnly",
BaseCondition::NoInvert => "NoInvert",
BaseCondition::NoAbstract => "NoAbstract",
BaseCondition::NotSupportedPrivilege => "NotSupportedPrivilege",
BaseCondition::MissingRequiredPrincipal => "MissingRequiredPrincipal",
BaseCondition::RecognizedPrincipal => "RecognizedPrincipal",
BaseCondition::AllowedPrincipal => "AllowedPrincipal",
BaseCondition::NumberOfMatchesWithinLimit => "NumberOfMatchesWithinLimit",
BaseCondition::QuotaNotExceeded => "QuotaNotExceeded",
BaseCondition::ValidResourceType => "ValidResourceType",
BaseCondition::ValidSyncToken => "ValidSyncToken",
}
}
}
impl CalCondition {
pub fn display_name(&self) -> &'static str {
match self {
CalCondition::CalendarCollectionLocationOk => "CalendarCollectionLocationOk",
CalCondition::ValidCalendarData => "ValidCalendarData",
CalCondition::ValidFilter => "ValidFilter",
CalCondition::ValidCalendarObjectResource => "ValidCalendarObjectResource",
CalCondition::ValidTimezone => "ValidTimezone",
CalCondition::NoUidConflict(_) => "NoUidConflict",
CalCondition::InitializeCalendarCollection => "InitializeCalendarCollection",
CalCondition::SupportedCalendarData => "SupportedCalendarData",
CalCondition::SupportedFilter(_) => "SupportedFilter",
CalCondition::SupportedCollation(_) => "SupportedCollation",
CalCondition::MinDateTime => "MinDateTime",
CalCondition::MaxDateTime => "MaxDateTime",
CalCondition::MaxResourceSize(_) => "MaxResourceSize",
CalCondition::MaxInstances => "MaxInstances",
CalCondition::MaxAttendeesPerInstance => "MaxAttendeesPerInstance",
}
}
}
impl CardCondition {
pub fn display_name(&self) -> &'static str {
match self {
CardCondition::SupportedAddressData => "SupportedAddressData",
CardCondition::SupportedAddressDataConversion => "SupportedAddressDataConversion",
CardCondition::SupportedFilter(_) => "SupportedFilter",
CardCondition::SupportedCollation(_) => "SupportedCollation",
CardCondition::ValidAddressData => "ValidAddressData",
CardCondition::NoUidConflict(_) => "NoUidConflict",
CardCondition::MaxResourceSize(_) => "MaxResourceSize",
CardCondition::AddressBookCollectionLocationOk => "AddressBookCollectionLocationOk",
}
}
}
impl Condition {
pub fn display_name(&self) -> &'static str {
match self {
Condition::Base(base) => base.display_name(),
Condition::Cal(cal) => cal.display_name(),
Condition::Card(card) => card.display_name(),
}
}
}
#[cfg(test)]
mod serde_impl {
use super::Status;
use hyper::StatusCode;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for Status {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize the status code as a u16
serializer.serialize_u16(self.0.as_u16())
}
}
impl<'de> Deserialize<'de> for Status {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize as u16
let status_value = u16::deserialize(deserializer)?;
// Convert u16 to StatusCode
let status_code = StatusCode::try_from(status_value).map_err(|_| {
serde::de::Error::custom(format!("Invalid status code: {}", status_value))
})?;
Ok(Status(status_code))
}
}
}