Migrate DAV props to serde. Big clusterfuck right now but it'll hopefully pay off

This commit is contained in:
Lennart
2024-03-14 19:24:23 +01:00
parent a145445277
commit b540362791
10 changed files with 246 additions and 190 deletions

View File

@@ -17,7 +17,6 @@ use tokio::sync::RwLock;
pub mod error;
pub mod resources;
pub mod routes;
pub mod tagname;
pub struct CalDavContext<C: CalendarStore + ?Sized> {
pub prefix: String,

View File

@@ -1,19 +1,16 @@
use std::{io::Write, sync::Arc};
use actix_web::{web::Data, HttpRequest};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use quick_xml::Writer;
use rustical_auth::AuthInfo;
use rustical_store::calendar::{Calendar, CalendarStore};
use strum::{EnumProperty, EnumString, IntoStaticStr, VariantNames};
use tokio::sync::RwLock;
use crate::tagname::TagName;
use rustical_dav::{
resource::Resource,
xml_snippets::{write_resourcetype, HrefElement, TextElement},
xml_snippets::{HrefElement, TextNode},
};
use rustical_store::calendar::{Calendar, CalendarStore};
use serde::Serialize;
use std::sync::Arc;
use strum::{EnumProperty, EnumString, IntoStaticStr, VariantNames};
use tokio::sync::RwLock;
pub struct CalendarResource<C: CalendarStore + ?Sized> {
pub cal_store: Arc<RwLock<C>>,
@@ -23,7 +20,98 @@ pub struct CalendarResource<C: CalendarStore + ?Sized> {
pub principal: String,
}
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty)]
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarComponent(#[serde(rename = "@name")] pub &'static str);
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarComponentSet {
#[serde(rename = "C:comp")]
pub comp: Vec<SupportedCalendarComponent>,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CalendarData {
content_type: &'static str,
version: &'static str,
}
impl Default for CalendarData {
fn default() -> Self {
Self {
content_type: "text/calendar;charset=utf-8",
version: "2.0",
}
}
}
#[derive(Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarData {
#[serde(rename = "C:calendar-data")]
calendar_data: CalendarData,
}
#[derive(Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Resourcetype {
#[serde(rename = "C:calendar")]
calendar: (),
collection: (),
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum UserPrivilege {
Read,
ReadAcl,
Write,
WriteAcl,
WriteContent,
ReadCurrentUserPrivilegeSet,
Bind,
Unbind,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct UserPrivilegeWrapper {
#[serde(rename = "$value")]
privilege: UserPrivilege,
}
impl From<UserPrivilege> for UserPrivilegeWrapper {
fn from(value: UserPrivilege) -> Self {
Self { privilege: value }
}
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct UserPrivilegeSet {
privilege: Vec<UserPrivilegeWrapper>,
}
impl Default for UserPrivilegeSet {
fn default() -> Self {
Self {
privilege: vec![
UserPrivilege::Read.into(),
UserPrivilege::ReadAcl.into(),
UserPrivilege::Write.into(),
UserPrivilege::WriteAcl.into(),
UserPrivilege::WriteContent.into(),
UserPrivilege::ReadCurrentUserPrivilegeSet.into(),
UserPrivilege::Bind.into(),
UserPrivilege::Unbind.into(),
],
}
}
}
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty, Clone)]
#[strum(serialize_all = "kebab-case")]
pub enum CalendarProp {
Resourcetype,
@@ -43,11 +131,27 @@ pub enum CalendarProp {
MaxResourceSize,
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum CalendarPropResponse {
Resourcetype(Resourcetype),
CurrentUser(HrefElement),
Displayname(TextNode),
CalendarColor(TextNode),
CalendarDescription(TextNode),
SupportedCalendarComponentSet(SupportedCalendarComponentSet),
SupportedCalendarData(SupportedCalendarData),
Getcontenttype(TextNode),
MaxResourceSize(TextNode),
CurrentUserPrivilegeSet(UserPrivilegeSet),
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> Resource for CalendarResource<C> {
type MemberType = Self;
type UriComponents = (String, String); // principal, calendar_id
type PropType = CalendarProp;
type PropResponse = CalendarPropResponse;
async fn acquire_from_request(
req: HttpRequest,
@@ -81,93 +185,44 @@ impl<C: CalendarStore + ?Sized> Resource for CalendarResource<C> {
Ok(vec![])
}
fn write_prop<W: Write>(&self, writer: &mut Writer<W>, prop: Self::PropType) -> Result<()> {
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
CalendarProp::Resourcetype => {
write_resourcetype(writer, vec!["C:calendar", "collection"])?
Ok(CalendarPropResponse::Resourcetype(Resourcetype::default()))
}
CalendarProp::CurrentUserPrincipal | CalendarProp::Owner => {
writer.write_serializable(
prop.tagname(),
&HrefElement::new(format!("{}/{}/", self.prefix, self.principal)),
)?;
}
CalendarProp::Displayname => {
let name = self.calendar.name.clone();
writer.write_serializable(prop.tagname(), &TextElement(name))?;
}
CalendarProp::CalendarColor => {
let color = self.calendar.color.clone();
writer.write_serializable(prop.tagname(), &TextElement(color))?;
}
CalendarProp::CalendarDescription => {
let description = self.calendar.description.clone();
writer.write_serializable(prop.tagname(), &TextElement(description))?;
Ok(CalendarPropResponse::CurrentUser(HrefElement::new(
format!("{}/{}/", self.prefix, self.principal),
)))
}
CalendarProp::Displayname => Ok(CalendarPropResponse::Displayname(TextNode(
self.calendar.name.clone(),
))),
CalendarProp::CalendarColor => Ok(CalendarPropResponse::CalendarColor(TextNode(
self.calendar.color.clone(),
))),
CalendarProp::CalendarDescription => Ok(CalendarPropResponse::CalendarDescription(
TextNode(self.calendar.description.clone()),
)),
CalendarProp::SupportedCalendarComponentSet => {
writer
.create_element(prop.tagname())
.write_inner_content(|writer| {
writer
.create_element("C:comp")
.with_attribute(("name", "VEVENT"))
.write_empty()?;
Ok::<(), quick_xml::Error>(())
})?;
Ok(CalendarPropResponse::SupportedCalendarComponentSet(
SupportedCalendarComponentSet {
comp: vec![SupportedCalendarComponent("VEVENT")],
},
))
}
CalendarProp::SupportedCalendarData => {
writer
.create_element(prop.tagname())
.write_inner_content(|writer| {
// <cal:calendar-data content-type="text/calendar" version="2.0" />
writer
.create_element("C:calendar-data")
.with_attributes(vec![
("content-type", "text/calendar"),
("version", "2.0"),
])
.write_empty()?;
Ok::<(), quick_xml::Error>(())
})?;
}
CalendarProp::Getcontenttype => {
writer.write_serializable(
prop.tagname(),
&TextElement(Some("text/calendar".to_owned())),
)?;
}
CalendarProp::MaxResourceSize => {
writer.write_serializable(
prop.tagname(),
&TextElement(Some("10000000".to_owned())),
)?;
}
CalendarProp::CurrentUserPrivilegeSet => {
writer
.create_element(prop.tagname())
// These are just hard-coded for now and will possibly change in the future
.write_inner_content(|writer| {
for privilege in [
"read",
"read-acl",
"write",
"write-acl",
"write-content",
"read-current-user-privilege-set",
"bind",
"unbind",
] {
writer
.create_element("privilege")
.write_inner_content(|writer| {
writer.create_element(privilege).write_empty()?;
Ok::<(), quick_xml::Error>(())
})?;
}
Ok::<(), quick_xml::Error>(())
})?;
}
};
Ok(())
CalendarProp::SupportedCalendarData => Ok(CalendarPropResponse::SupportedCalendarData(
SupportedCalendarData::default(),
)),
CalendarProp::Getcontenttype => Ok(CalendarPropResponse::Getcontenttype(TextNode(
Some("text/calendar;charset=utf-8".to_owned()),
))),
CalendarProp::MaxResourceSize => Ok(CalendarPropResponse::MaxResourceSize(TextNode(
Some("10000000".to_owned()),
))),
CalendarProp::CurrentUserPrivilegeSet => Ok(
CalendarPropResponse::CurrentUserPrivilegeSet(UserPrivilegeSet::default()),
),
}
}
}

View File

@@ -1,11 +1,11 @@
use crate::tagname::TagName;
use actix_web::{web::Data, HttpRequest};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use rustical_auth::AuthInfo;
use rustical_dav::{resource::Resource, xml_snippets::TextElement};
use rustical_dav::{resource::Resource, xml_snippets::TextNode};
use rustical_store::calendar::CalendarStore;
use rustical_store::event::Event;
use serde::Serialize;
use std::sync::Arc;
use strum::{EnumProperty, EnumString, IntoStaticStr, VariantNames};
use tokio::sync::RwLock;
@@ -16,7 +16,7 @@ pub struct EventResource<C: CalendarStore + ?Sized> {
pub event: Event,
}
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty)]
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty, Clone)]
#[strum(serialize_all = "kebab-case")]
pub enum EventProp {
Getetag,
@@ -25,11 +25,20 @@ pub enum EventProp {
Getcontenttype,
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum PrincipalPropResponse {
Getetag(TextNode),
CalendarData(TextNode),
Getcontenttype(TextNode),
}
#[async_trait(?Send)]
impl<C: CalendarStore + ?Sized> Resource for EventResource<C> {
type UriComponents = (String, String, String); // principal, calendar, event
type MemberType = Self;
type PropType = EventProp;
type PropResponse = PrincipalPropResponse;
fn get_path(&self) -> &str {
&self.path
@@ -62,29 +71,17 @@ impl<C: CalendarStore + ?Sized> Resource for EventResource<C> {
})
}
fn write_prop<W: std::io::Write>(
&self,
writer: &mut quick_xml::Writer<W>,
prop: Self::PropType,
) -> Result<()> {
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
EventProp::Getetag => {
writer.write_serializable(
prop.tagname(),
&TextElement(Some(self.event.get_etag())),
)?;
}
EventProp::CalendarData => {
writer
.write_serializable(prop.tagname(), &TextElement(Some(self.event.get_ics())))?;
}
EventProp::Getcontenttype => {
writer.write_serializable(
prop.tagname(),
&TextElement(Some("text/calendar;charset=utf-8".to_owned())),
)?;
}
};
Ok(())
EventProp::Getetag => Ok(PrincipalPropResponse::Getetag(TextNode(Some(
self.event.get_etag(),
)))),
EventProp::CalendarData => Ok(PrincipalPropResponse::CalendarData(TextNode(Some(
self.event.get_ics(),
)))),
EventProp::Getcontenttype => Ok(PrincipalPropResponse::Getcontenttype(TextNode(Some(
"text/calendar;charset=utf-8".to_owned(),
)))),
}
}
}

View File

@@ -1,13 +1,10 @@
use crate::tagname::TagName;
use actix_web::{web::Data, HttpRequest};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use rustical_auth::AuthInfo;
use rustical_dav::{
resource::Resource,
xml_snippets::{write_resourcetype, HrefElement},
};
use rustical_dav::{resource::Resource, xml_snippets::HrefElement};
use rustical_store::calendar::CalendarStore;
use serde::Serialize;
use std::sync::Arc;
use strum::{EnumProperty, EnumString, IntoStaticStr, VariantNames};
use tokio::sync::RwLock;
@@ -21,7 +18,21 @@ pub struct PrincipalCalendarsResource<C: CalendarStore + ?Sized> {
cal_store: Arc<RwLock<C>>,
}
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty)]
#[derive(Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Resourcetype {
principal: (),
collection: (),
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum PrincipalPropResponse {
Resourcetype(Resourcetype),
CurrentUser(HrefElement),
}
#[derive(EnumString, Debug, VariantNames, IntoStaticStr, EnumProperty, Clone)]
#[strum(serialize_all = "kebab-case")]
pub enum PrincipalProp {
Resourcetype,
@@ -39,6 +50,7 @@ impl<C: CalendarStore + ?Sized> Resource for PrincipalCalendarsResource<C> {
type UriComponents = ();
type MemberType = CalendarResource<C>;
type PropType = PrincipalProp;
type PropResponse = PrincipalPropResponse;
fn get_path(&self) -> &str {
&self.path
@@ -83,26 +95,17 @@ impl<C: CalendarStore + ?Sized> Resource for PrincipalCalendarsResource<C> {
path: req.path().to_string(),
})
}
fn write_prop<W: std::io::Write>(
&self,
writer: &mut quick_xml::Writer<W>,
prop: Self::PropType,
) -> Result<()> {
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
PrincipalProp::Resourcetype => {
write_resourcetype(writer, vec!["principal", "collection"])?
Ok(PrincipalPropResponse::Resourcetype(Resourcetype::default()))
}
PrincipalProp::CurrentUserPrincipal
| PrincipalProp::PrincipalUrl
| PrincipalProp::CalendarHomeSet
| PrincipalProp::CalendarUserAddressSet => {
writer.write_serializable(
prop.tagname(),
&HrefElement::new(format!("{}/{}/", self.prefix, self.principal)),
)?;
}
};
Ok(())
| PrincipalProp::CalendarUserAddressSet => Ok(PrincipalPropResponse::CurrentUser(
HrefElement::new(format!("{}/{}/", self.prefix, self.principal)),
)),
}
}
}

View File

@@ -1,10 +1,9 @@
use crate::tagname::TagName;
use actix_web::HttpRequest;
use anyhow::Result;
use async_trait::async_trait;
use quick_xml::events::BytesText;
use rustical_auth::AuthInfo;
use rustical_dav::{resource::Resource, xml_snippets::write_resourcetype};
use rustical_dav::{resource::Resource, xml_snippets::HrefElement};
use serde::Serialize;
use strum::{EnumProperty, EnumString, IntoStaticStr, VariantNames};
pub struct RootResource {
@@ -13,18 +12,32 @@ pub struct RootResource {
path: String,
}
#[derive(EnumString, Debug, VariantNames, EnumProperty, IntoStaticStr)]
#[derive(EnumString, Debug, VariantNames, EnumProperty, IntoStaticStr, Clone)]
#[strum(serialize_all = "kebab-case")]
pub enum RootProp {
Resourcetype,
CurrentUserPrincipal,
}
#[derive(Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Resourcetype {
collection: (),
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum RootPropResponse {
Resourcetype(Resourcetype),
CurrentUser(HrefElement),
}
#[async_trait(?Send)]
impl Resource for RootResource {
type UriComponents = ();
type MemberType = Self;
type PropType = RootProp;
type PropResponse = RootPropResponse;
fn get_path(&self) -> &str {
&self.path
@@ -47,27 +60,12 @@ impl Resource for RootResource {
})
}
fn write_prop<W: std::io::Write>(
&self,
writer: &mut quick_xml::Writer<W>,
prop: Self::PropType,
) -> Result<()> {
fn get_prop(&self, prop: Self::PropType) -> Result<Self::PropResponse> {
match prop {
RootProp::Resourcetype => write_resourcetype(writer, vec!["collection"])?,
RootProp::CurrentUserPrincipal => {
writer
.create_element(prop.tagname())
.write_inner_content(|writer| {
writer
.create_element("href")
.write_text_content(BytesText::new(&format!(
"{}/{}",
self.prefix, self.principal
)))?;
Ok::<(), quick_xml::Error>(())
})?;
}
};
Ok(())
RootProp::Resourcetype => Ok(RootPropResponse::Resourcetype(Resourcetype::default())),
RootProp::CurrentUserPrincipal => Ok(RootPropResponse::CurrentUser(HrefElement::new(
format!("{}/{}/", self.prefix, self.principal),
))),
}
}
}

View File

@@ -1,11 +0,0 @@
use strum::EnumProperty;
pub trait TagName {
fn tagname(self) -> &'static str;
}
impl<P: EnumProperty + Into<&'static str>> TagName for P {
fn tagname(self) -> &'static str {
self.get_str("tagname").unwrap_or(self.into())
}
}