From ade96aa559ae2964b4d906354ae7b21737a644ed Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:57:59 +0100 Subject: [PATCH] caldav: user enums for props --- crates/caldav/Cargo.toml | 1 + crates/caldav/src/resources/calendar.rs | 63 ++++++++++++------------ crates/caldav/src/resources/event.rs | 23 +++++---- crates/caldav/src/resources/principal.rs | 42 ++++++++-------- crates/caldav/src/resources/root.rs | 20 +++++--- crates/dav/Cargo.toml | 1 + crates/dav/src/resource.rs | 34 ++++++++----- 7 files changed, 104 insertions(+), 80 deletions(-) diff --git a/crates/caldav/Cargo.toml b/crates/caldav/Cargo.toml index e632615..a9f22d4 100644 --- a/crates/caldav/Cargo.toml +++ b/crates/caldav/Cargo.toml @@ -24,3 +24,4 @@ serde_json = "1.0" tokio = { version = "1.35", features = ["sync", "full"] } async-trait = "0.1" thiserror = "1.0" +strum = { version = "0.26.2", features = ["strum_macros", "derive"] } diff --git a/crates/caldav/src/resources/calendar.rs b/crates/caldav/src/resources/calendar.rs index f3bd829..804999c 100644 --- a/crates/caldav/src/resources/calendar.rs +++ b/crates/caldav/src/resources/calendar.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use quick_xml::{events::BytesText, Writer}; use rustical_auth::AuthInfo; use rustical_store::calendar::{Calendar, CalendarStore}; +use strum::{EnumString, IntoStaticStr, VariantNames}; use tokio::sync::RwLock; use crate::proptypes::{write_href_prop, write_string_prop}; @@ -19,10 +20,27 @@ pub struct CalendarResource { pub principal: String, } +#[derive(EnumString, Debug, VariantNames, IntoStaticStr)] +#[strum(serialize_all = "kebab-case")] +pub enum CalendarProp { + Resourcetype, + CurrentUserPrincipal, + Displayname, + SupportedCalendarComponentSet, + SupportedCalendarData, + Getcontenttype, + CalendarDescription, + Owner, + CalendarColor, + CurrentUserPrivilegeSet, + MaxResourceSize, +} + #[async_trait(?Send)] impl Resource for CalendarResource { type MemberType = Self; type UriComponents = (String, String); // principal, calendar_id + type PropType = CalendarProp; async fn acquire_from_request( req: HttpRequest, @@ -56,33 +74,19 @@ impl Resource for CalendarResource { Ok(vec![]) } - #[inline] - fn list_dead_props() -> Vec<&'static str> { - vec![ - "resourcetype", - "current-user-principal", - "displayname", - "supported-calendar-component-set", - "supported-calendar-data", - "getcontenttype", - "calendar-description", - "owner", - "calendar-color", - "current-user-privilege-set", - "max-resource-size", - ] - } - fn write_prop(&self, writer: &mut Writer, prop: &str) -> Result<()> { + fn write_prop(&self, writer: &mut Writer, prop: Self::PropType) -> Result<()> { match prop { - "resourcetype" => write_resourcetype(writer, vec!["C:calendar", "collection"])?, - "current-user-principal" | "owner" => { + CalendarProp::Resourcetype => { + write_resourcetype(writer, vec!["C:calendar", "collection"])? + } + CalendarProp::CurrentUserPrincipal | CalendarProp::Owner => { write_href_prop( writer, - prop, + prop.into(), &format!("{}/{}/", self.prefix, self.principal), )?; } - "displayname" => { + CalendarProp::Displayname => { let el = writer.create_element("displayname"); if let Some(name) = self.calendar.clone().name { el.write_text_content(BytesText::new(&name))?; @@ -90,7 +94,7 @@ impl Resource for CalendarResource { el.write_empty()?; } } - "calendar-color" => { + CalendarProp::CalendarColor => { let el = writer.create_element("IC:calendar-color"); if let Some(color) = self.calendar.clone().color { el.write_text_content(BytesText::new(&color))?; @@ -98,7 +102,7 @@ impl Resource for CalendarResource { el.write_empty()?; } } - "calendar-description" => { + CalendarProp::CalendarDescription => { let el = writer.create_element("C:calendar-description"); if let Some(description) = self.calendar.clone().description { el.write_text_content(BytesText::new(&description))?; @@ -106,7 +110,7 @@ impl Resource for CalendarResource { el.write_empty()?; } } - "supported-calendar-component-set" => { + CalendarProp::SupportedCalendarComponentSet => { writer .create_element("C:supported-calendar-component-set") .write_inner_content(|writer| { @@ -117,7 +121,7 @@ impl Resource for CalendarResource { Ok::<(), quick_xml::Error>(()) })?; } - "supported-calendar-data" => { + CalendarProp::SupportedCalendarData => { writer .create_element("C:supported-calendar-data") .write_inner_content(|writer| { @@ -132,13 +136,13 @@ impl Resource for CalendarResource { Ok::<(), quick_xml::Error>(()) })?; } - "getcontenttype" => { + CalendarProp::Getcontenttype => { write_string_prop(writer, "getcontenttype", "text/calendar")?; } - "max-resource-size" => { + CalendarProp::MaxResourceSize => { write_string_prop(writer, "max-resource-size", "10000000")?; } - "current-user-privilege-set" => { + CalendarProp::CurrentUserPrivilegeSet => { writer .create_element("current-user-privilege-set") // These are just hard-coded for now and will possibly change in the future @@ -163,9 +167,6 @@ impl Resource for CalendarResource { Ok::<(), quick_xml::Error>(()) })?; } - _ => { - return Err(anyhow!("invalid prop")); - } }; Ok(()) } diff --git a/crates/caldav/src/resources/event.rs b/crates/caldav/src/resources/event.rs index e77309c..c692ba5 100644 --- a/crates/caldav/src/resources/event.rs +++ b/crates/caldav/src/resources/event.rs @@ -7,6 +7,7 @@ use rustical_dav::resource::Resource; use rustical_store::calendar::CalendarStore; use rustical_store::event::Event; use std::sync::Arc; +use strum::{EnumString, VariantNames}; use tokio::sync::RwLock; pub struct EventResource { @@ -15,10 +16,19 @@ pub struct EventResource { pub event: Event, } +#[derive(EnumString, Debug, VariantNames)] +#[strum(serialize_all = "kebab-case")] +pub enum EventProp { + Getetag, + CalendarData, + Getcontenttype, +} + #[async_trait(?Send)] impl Resource for EventResource { type UriComponents = (String, String, String); // principal, calendar, event type MemberType = Self; + type PropType = EventProp; fn get_path(&self) -> &str { &self.path @@ -54,24 +64,19 @@ impl Resource for EventResource { fn write_prop( &self, writer: &mut quick_xml::Writer, - prop: &str, + prop: Self::PropType, ) -> Result<()> { match prop { - "getetag" => { + EventProp::Getetag => { write_string_prop(writer, "getetag", &self.event.get_etag())?; } - "calendar-data" => { + EventProp::CalendarData => { write_string_prop(writer, "C:calendar-data", &self.event.get_ics())?; } - "getcontenttype" => { + EventProp::Getcontenttype => { write_string_prop(writer, "getcontenttype", "text/calendar;charset=utf-8")?; } - _ => return Err(anyhow!("invalid prop!")), }; Ok(()) } - - fn list_dead_props() -> Vec<&'static str> { - vec!["getetag", "calendar-data", "getcontenttype"] - } } diff --git a/crates/caldav/src/resources/principal.rs b/crates/caldav/src/resources/principal.rs index 011e010..dff564b 100644 --- a/crates/caldav/src/resources/principal.rs +++ b/crates/caldav/src/resources/principal.rs @@ -8,6 +8,7 @@ use quick_xml::events::BytesText; use rustical_auth::AuthInfo; use rustical_dav::{resource::Resource, xml_snippets::write_resourcetype}; use rustical_store::calendar::CalendarStore; +use strum::{EnumString, IntoStaticStr, VariantNames}; use tokio::sync::RwLock; use super::calendar::CalendarResource; @@ -19,10 +20,22 @@ pub struct PrincipalCalendarsResource { cal_store: Arc>, } +#[derive(EnumString, Debug, VariantNames, IntoStaticStr)] +#[strum(serialize_all = "kebab-case")] +pub enum PrincipalProp { + Resourcetype, + CurrentUserPrincipal, + #[strum(serialize = "principal-URL")] + PrincipalUrl, + CalendarHomeSet, + CalendarUserAddressSet, +} + #[async_trait(?Send)] impl Resource for PrincipalCalendarsResource { type UriComponents = (); type MemberType = CalendarResource; + type PropType = PrincipalProp; fn get_path(&self) -> &str { &self.path @@ -71,20 +84,23 @@ impl Resource for PrincipalCalendarsResource { fn write_prop( &self, writer: &mut quick_xml::Writer, - prop: &str, + prop: Self::PropType, ) -> Result<()> { match prop { - "resourcetype" => write_resourcetype(writer, vec!["principal", "collection"])?, - "current-user-principal" | "principal-URL" => { + PrincipalProp::Resourcetype => { + write_resourcetype(writer, vec!["principal", "collection"])? + } + PrincipalProp::CurrentUserPrincipal | PrincipalProp::PrincipalUrl => { write_href_prop( writer, - prop, + prop.into(), &format!("{}/{}/", self.prefix, self.principal), )?; } - "calendar-home-set" | "calendar-user-address-set" => { + PrincipalProp::CalendarHomeSet | PrincipalProp::CalendarUserAddressSet => { + let propname: &'static str = prop.into(); writer - .create_element(&format!("C:{prop}")) + .create_element(&format!("C:{propname}")) .write_inner_content(|writer| { writer .create_element("href") @@ -95,21 +111,7 @@ impl Resource for PrincipalCalendarsResource { Ok::<(), quick_xml::Error>(()) })?; } - "allprops" => {} - _ => { - return Err(anyhow!("invalid prop")); - } }; Ok(()) } - - fn list_dead_props() -> Vec<&'static str> { - vec![ - "resourcetype", - "current-user-principal", - "principal-URL", - "calendar-home-set", - "calendar-user-address-set", - ] - } } diff --git a/crates/caldav/src/resources/root.rs b/crates/caldav/src/resources/root.rs index 9ad81f9..5451eb8 100644 --- a/crates/caldav/src/resources/root.rs +++ b/crates/caldav/src/resources/root.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use quick_xml::events::BytesText; use rustical_auth::AuthInfo; use rustical_dav::{resource::Resource, xml_snippets::write_resourcetype}; +use strum::{EnumString, VariantNames}; pub struct RootResource { prefix: String, @@ -11,10 +12,18 @@ pub struct RootResource { path: String, } +#[derive(EnumString, Debug, VariantNames)] +#[strum(serialize_all = "kebab-case")] +pub enum RootProp { + Resourcetype, + CurrentUserPrincipal, +} + #[async_trait(?Send)] impl Resource for RootResource { type UriComponents = (); type MemberType = Self; + type PropType = RootProp; fn get_path(&self) -> &str { &self.path @@ -40,11 +49,11 @@ impl Resource for RootResource { fn write_prop( &self, writer: &mut quick_xml::Writer, - prop: &str, + prop: Self::PropType, ) -> Result<()> { match prop { - "resourcetype" => write_resourcetype(writer, vec!["collection"])?, - "current-user-principal" => { + RootProp::Resourcetype => write_resourcetype(writer, vec!["collection"])?, + RootProp::CurrentUserPrincipal => { writer .create_element("current-user-principal") .write_inner_content(|writer| { @@ -57,12 +66,7 @@ impl Resource for RootResource { Ok::<(), quick_xml::Error>(()) })?; } - _ => return Err(anyhow!("invalid prop!")), }; Ok(()) } - - fn list_dead_props() -> Vec<&'static str> { - vec!["resourcetype", "current-user-principal"] - } } diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index 5bad7eb..e66e24e 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -11,3 +11,4 @@ derive_more = "0.99" futures-util = "0.3" quick-xml = "0.31" rustical_auth = { path = "../auth/" } +strum = "0.26.2" diff --git a/crates/dav/src/resource.rs b/crates/dav/src/resource.rs index 71159dd..4052195 100644 --- a/crates/dav/src/resource.rs +++ b/crates/dav/src/resource.rs @@ -1,10 +1,11 @@ -use std::io::Write; +use std::{io::Write, str::FromStr}; use actix_web::{http::StatusCode, HttpRequest}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use quick_xml::Writer; use rustical_auth::AuthInfo; +use strum::VariantNames; use crate::xml_snippets::{write_invalid_props_response, write_propstat_response}; @@ -16,6 +17,7 @@ use crate::xml_snippets::{write_invalid_props_response, write_propstat_response} pub trait Resource: Sized { type MemberType: Resource; type UriComponents: Sized; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) + type PropType: FromStr + VariantNames; async fn acquire_from_request( req: HttpRequest, @@ -27,8 +29,10 @@ pub trait Resource: Sized { fn get_path(&self) -> &str; async fn get_members(&self) -> Result>; - fn list_dead_props() -> Vec<&'static str>; - fn write_prop(&self, writer: &mut Writer, prop: &str) -> Result<()>; + fn list_dead_props() -> &'static [&'static str] { + Self::PropType::VARIANTS + } + fn write_prop(&self, writer: &mut Writer, prop: Self::PropType) -> Result<()>; } pub trait HandlePropfind { @@ -43,7 +47,7 @@ impl HandlePropfind for R { // allprops MUST be the only queried prop per spec return Err(anyhow!("allprops MUST be the only queried prop")); } - props = R::list_dead_props(); + props = R::list_dead_props().into(); } let mut invalid_props = Vec::<&str>::new(); @@ -53,14 +57,20 @@ impl HandlePropfind for R { write_propstat_response(&mut writer, self.get_path(), StatusCode::OK, |writer| { for prop in props { - // TODO: Fix error types - match self - .write_prop(writer, prop) - .map_err(|_e| quick_xml::Error::TextNotFound) - { - Ok(_) => {} - Err(_) => invalid_props.push(prop), - }; + if let Ok(valid_prop) = R::PropType::from_str(prop) { + // TODO: Fix error types + match self + .write_prop(writer, valid_prop) + .map_err(|_e| quick_xml::Error::TextNotFound) + { + // TODO: clean this mess up + Ok(_) => {} + // not really an invalid prop, but some error happened + Err(_) => invalid_props.push(prop), + }; + } else { + invalid_props.push(prop); + } } Ok(()) })?;