From 1d763b5c8fc22be506e6b01fa6b19fc0e1b05f6e Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:00:36 +0200 Subject: [PATCH] Error typing for rustical_store as well as some refactoring --- Cargo.lock | 1 + crates/caldav/src/calendar/methods/delete.rs | 2 +- .../caldav/src/calendar/methods/mkcalendar.rs | 7 +-- crates/caldav/src/calendar/methods/report.rs | 9 ++-- crates/caldav/src/calendar/resource.rs | 2 +- crates/caldav/src/error.rs | 44 +++++++++++++++++++ crates/caldav/src/event/methods.rs | 24 ++-------- crates/caldav/src/event/resource.rs | 2 +- crates/caldav/src/lib.rs | 6 ++- crates/caldav/src/principal/mod.rs | 2 +- crates/dav/src/lib.rs | 2 + crates/store/Cargo.toml | 1 + crates/store/src/error.rs | 23 ++++++++++ crates/store/src/event.rs | 13 +++--- crates/store/src/lib.rs | 3 ++ crates/store/src/sqlite_store.rs | 23 +++++----- crates/store/src/store.rs | 17 +++---- crates/store/src/toml_store.rs | 29 ++++++------ src/app.rs | 2 +- src/main.rs | 2 +- 20 files changed, 135 insertions(+), 79 deletions(-) create mode 100644 crates/caldav/src/error.rs create mode 100644 crates/store/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index e1fc1a4..ae0e330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1818,6 +1818,7 @@ dependencies = [ "serde", "sha2", "sqlx", + "thiserror", "tokio", "toml", ] diff --git a/crates/caldav/src/calendar/methods/delete.rs b/crates/caldav/src/calendar/methods/delete.rs index 93821fc..6620b39 100644 --- a/crates/caldav/src/calendar/methods/delete.rs +++ b/crates/caldav/src/calendar/methods/delete.rs @@ -5,7 +5,7 @@ use actix_web::{ HttpResponse, }; use rustical_auth::{AuthInfoExtractor, CheckAuthentication}; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; pub async fn route_delete_calendar( context: Data>, diff --git a/crates/caldav/src/calendar/methods/mkcalendar.rs b/crates/caldav/src/calendar/methods/mkcalendar.rs index c0c0bcf..7603ba1 100644 --- a/crates/caldav/src/calendar/methods/mkcalendar.rs +++ b/crates/caldav/src/calendar/methods/mkcalendar.rs @@ -5,7 +5,7 @@ use actix_web::HttpResponse; use anyhow::Result; use rustical_auth::{AuthInfoExtractor, CheckAuthentication}; use rustical_store::calendar::Calendar; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Clone, Debug)] @@ -69,10 +69,7 @@ pub async fn route_mkcol_calendar { get_events_calendar_query(cal_query, &cid, &cal_store).await? @@ -178,7 +175,7 @@ pub async fn route_report_calendar { // TODO: Implement - return Err(Error::InternalError); + return Err(Error::NotImplemented); } PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into(), }; diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index d7517bd..baa3edf 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -6,7 +6,7 @@ use rustical_dav::error::Error; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml_snippets::{HrefElement, TextNode}; use rustical_store::calendar::Calendar; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use serde::Serialize; use std::sync::Arc; use strum::{EnumString, IntoStaticStr, VariantNames}; diff --git a/crates/caldav/src/error.rs b/crates/caldav/src/error.rs new file mode 100644 index 0000000..5afaefe --- /dev/null +++ b/crates/caldav/src/error.rs @@ -0,0 +1,44 @@ +use actix_web::{http::StatusCode, HttpResponse}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Unauthorized")] + Unauthorized, + + #[error("Not implemented")] + NotImplemented, + + #[error(transparent)] + StoreError(#[from] rustical_store::Error), + + #[error(transparent)] + DavError(#[from] rustical_dav::Error), + + #[error(transparent)] + XmlDecodeError(#[from] quick_xml::DeError), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +impl actix_web::ResponseError for Error { + fn status_code(&self) -> actix_web::http::StatusCode { + match self { + Error::StoreError(err) => match err { + rustical_store::Error::NotFound => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }, + Error::DavError(err) => err.status_code(), + Error::Unauthorized => StatusCode::UNAUTHORIZED, + Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST, + Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR, + Error::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } + fn error_response(&self) -> actix_web::HttpResponse { + match self { + Error::DavError(err) => err.error_response(), + _ => HttpResponse::build(self.status_code()).body(self.to_string()), + } + } +} diff --git a/crates/caldav/src/event/methods.rs b/crates/caldav/src/event/methods.rs index 84fa42a..d277097 100644 --- a/crates/caldav/src/event/methods.rs +++ b/crates/caldav/src/event/methods.rs @@ -1,27 +1,9 @@ use crate::CalDavContext; -use actix_web::http::StatusCode; +use crate::Error; use actix_web::web::{Data, Path}; -use actix_web::{HttpResponse, ResponseError}; +use actix_web::HttpResponse; use rustical_auth::{AuthInfoExtractor, CheckAuthentication}; -use rustical_store::store::CalendarStore; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error(transparent)] - Other(#[from] anyhow::Error), -} - -impl ResponseError for Error { - fn status_code(&self) -> actix_web::http::StatusCode { - match self { - Self::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } - fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()).body(self.to_string()) - } -} +use rustical_store::CalendarStore; pub async fn delete_event( context: Data>, diff --git a/crates/caldav/src/event/resource.rs b/crates/caldav/src/event/resource.rs index 5d06cd4..20124ac 100644 --- a/crates/caldav/src/event/resource.rs +++ b/crates/caldav/src/event/resource.rs @@ -6,7 +6,7 @@ use rustical_dav::error::Error; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml_snippets::TextNode; use rustical_store::event::Event; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use serde::Serialize; use std::sync::Arc; use strum::{EnumString, IntoStaticStr, VariantNames}; diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 7407ee8..91fe970 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -6,18 +6,20 @@ use event::resource::EventResource; use principal::PrincipalResource; use root::RootResource; use rustical_auth::CheckAuthentication; -use rustical_dav::error::Error; use rustical_dav::propfind::{route_propfind, ServicePrefix}; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; pub mod calendar; +pub mod error; pub mod event; pub mod principal; pub mod root; +pub use error::Error; + pub struct CalDavContext { pub store: Arc>, } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index a7121e4..dae4280 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use rustical_auth::AuthInfo; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml_snippets::HrefElement; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use serde::Serialize; use strum::{EnumString, IntoStaticStr, VariantNames}; use tokio::sync::RwLock; diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 47ae2bf..f0d862c 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -5,3 +5,5 @@ pub mod propfind; pub mod resource; pub mod xml; pub mod xml_snippets; + +pub use error::Error; diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 2d629a5..d9f5c28 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -29,3 +29,4 @@ regex = "1.10" lazy_static = "1.4" rstest = "0.18.2" rstest_reuse = "0.6.0" +thiserror = "1.0.61" diff --git a/crates/store/src/error.rs b/crates/store/src/error.rs new file mode 100644 index 0000000..a99d316 --- /dev/null +++ b/crates/store/src/error.rs @@ -0,0 +1,23 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Not found")] + NotFound, + + #[error(transparent)] + SqlxError(sqlx::Error), + + #[error(transparent)] + Other(#[from] anyhow::Error), + + #[error(transparent)] + ParserError(#[from] ical::parser::ParserError), +} + +impl From for Error { + fn from(value: sqlx::Error) -> Self { + match value { + sqlx::Error::RowNotFound => Error::NotFound, + err => Error::SqlxError(err), + } + } +} diff --git a/crates/store/src/event.rs b/crates/store/src/event.rs index b9b76da..c7082ec 100644 --- a/crates/store/src/event.rs +++ b/crates/store/src/event.rs @@ -1,4 +1,7 @@ -use crate::timestamps::{parse_datetime, parse_duration}; +use crate::{ + timestamps::{parse_datetime, parse_duration}, + Error, +}; use anyhow::{anyhow, Result}; use chrono::{Duration, NaiveDateTime, Timelike}; use ical::parser::{ical::component::IcalCalendar, Component}; @@ -51,14 +54,14 @@ impl Serialize for Event { impl Event { // https://datatracker.ietf.org/doc/html/rfc4791#section-4.1 // MUST NOT contain more than one calendar objects (VEVENT, VTODO, VJOURNAL) - pub fn from_ics(uid: String, ics: String) -> Result { + pub fn from_ics(uid: String, ics: String) -> Result { let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes())); - let cal = parser.next().ok_or(anyhow!("no calendar :("))??; + let cal = parser.next().ok_or(Error::NotFound)??; if parser.next().is_some() { - return Err(anyhow!("multiple calendars!")); + return Err(anyhow!("multiple calendars!").into()); } if cal.events.len() != 1 { - return Err(anyhow!("multiple or no events")); + return Err(anyhow!("multiple or no events").into()); } let event = Self { uid, cal, ics }; // Run getters now to validate the input and ensure that they'll work later on diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index 5a45150..bbd448a 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -1,6 +1,9 @@ pub mod calendar; +pub mod error; pub mod event; pub mod sqlite_store; pub mod store; pub mod timestamps; pub mod toml_store; +pub use error::Error; +pub use store::CalendarStore; diff --git a/crates/store/src/sqlite_store.rs b/crates/store/src/sqlite_store.rs index 6ee507a..f6c5dc2 100644 --- a/crates/store/src/sqlite_store.rs +++ b/crates/store/src/sqlite_store.rs @@ -2,7 +2,8 @@ use anyhow::Result; use async_trait::async_trait; use sqlx::{sqlite::SqliteConnectOptions, Pool, Sqlite, SqlitePool}; -use crate::{calendar::Calendar, event::Event, store::CalendarStore}; +use crate::event::Event; +use crate::{calendar::Calendar, CalendarStore, Error}; #[derive(Debug)] pub struct SqliteCalendarStore { @@ -22,16 +23,16 @@ struct EventRow { } impl TryFrom for Event { - type Error = anyhow::Error; + type Error = Error; - fn try_from(value: EventRow) -> Result { + fn try_from(value: EventRow) -> Result { Event::from_ics(value.uid, value.ics) } } #[async_trait] impl CalendarStore for SqliteCalendarStore { - async fn get_calendar(&self, id: &str) -> Result { + async fn get_calendar(&self, id: &str) -> Result { let cal = sqlx::query_as!( Calendar, "SELECT id, name, owner, description, color, timezone FROM calendars WHERE id = ?", @@ -42,7 +43,7 @@ impl CalendarStore for SqliteCalendarStore { Ok(cal) } - async fn get_calendars(&self, _owner: &str) -> Result> { + async fn get_calendars(&self, _owner: &str) -> Result, Error> { let cals = sqlx::query_as!( Calendar, "SELECT id, name, owner, description, color, timezone FROM calendars" @@ -52,7 +53,7 @@ impl CalendarStore for SqliteCalendarStore { Ok(cals) } - async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<()> { + async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error> { sqlx::query!( "INSERT INTO calendars (id, name, description, owner, color, timezone) VALUES (?, ?, ?, ?, ?, ?)", cid, @@ -65,14 +66,14 @@ impl CalendarStore for SqliteCalendarStore { Ok(()) } - async fn delete_calendar(&mut self, cid: &str) -> Result<()> { + async fn delete_calendar(&mut self, cid: &str) -> Result<(), Error> { sqlx::query!("DELETE FROM calendars WHERE id = ?", cid) .execute(&self.db) .await?; Ok(()) } - async fn get_events(&self, cid: &str) -> Result> { + async fn get_events(&self, cid: &str) -> Result, Error> { sqlx::query_as!(EventRow, "SELECT uid, ics FROM events WHERE cid = ?", cid) .fetch_all(&self.db) .await? @@ -81,7 +82,7 @@ impl CalendarStore for SqliteCalendarStore { .collect() } - async fn get_event(&self, cid: &str, uid: &str) -> Result { + async fn get_event(&self, cid: &str, uid: &str) -> Result { let event = sqlx::query_as!( EventRow, "SELECT uid, ics FROM events where cid = ? AND uid = ?", @@ -94,7 +95,7 @@ impl CalendarStore for SqliteCalendarStore { Ok(event) } - async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<()> { + async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error> { // Do this extra step to ensure that the input is actually valid let _ = Event::from_ics(uid.to_owned(), ics.to_owned())?; sqlx::query!( @@ -108,7 +109,7 @@ impl CalendarStore for SqliteCalendarStore { Ok(()) } - async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<()> { + async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<(), Error> { sqlx::query!("DELETE FROM events WHERE cid = ? AND uid = ?", cid, uid) .execute(&self.db) .await?; diff --git a/crates/store/src/store.rs b/crates/store/src/store.rs index 8a3f206..6436e29 100644 --- a/crates/store/src/store.rs +++ b/crates/store/src/store.rs @@ -1,17 +1,18 @@ use anyhow::Result; use async_trait::async_trait; +use crate::error::Error; use crate::{calendar::Calendar, event::Event}; #[async_trait] pub trait CalendarStore: Send + Sync + 'static { - async fn get_calendar(&self, id: &str) -> Result; - async fn get_calendars(&self, owner: &str) -> Result>; - async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<()>; - async fn delete_calendar(&mut self, cid: &str) -> Result<()>; + async fn get_calendar(&self, id: &str) -> Result; + async fn get_calendars(&self, owner: &str) -> Result, Error>; + async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error>; + async fn delete_calendar(&mut self, cid: &str) -> Result<(), Error>; - async fn get_events(&self, cid: &str) -> Result>; - async fn get_event(&self, cid: &str, uid: &str) -> Result; - async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<()>; - async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<()>; + async fn get_events(&self, cid: &str) -> Result, Error>; + async fn get_event(&self, cid: &str, uid: &str) -> Result; + async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error>; + async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<(), Error>; } diff --git a/crates/store/src/toml_store.rs b/crates/store/src/toml_store.rs index 49290ce..dbeba71 100644 --- a/crates/store/src/toml_store.rs +++ b/crates/store/src/toml_store.rs @@ -1,7 +1,6 @@ -use crate::calendar::Calendar; use crate::event::Event; -use crate::store::CalendarStore; -use anyhow::{anyhow, Result}; +use crate::{calendar::Calendar, CalendarStore, Error}; +use anyhow::anyhow; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::collections::{hash_map::Entry, HashMap}; @@ -31,7 +30,7 @@ impl TomlCalendarStore { } } - pub async fn save(&self) -> Result<()> { + pub async fn save(&self) -> anyhow::Result<()> { let output = toml::to_string_pretty(&self)?; if let Some(path) = &self.path { let mut file = File::create(path).await?; @@ -43,11 +42,11 @@ impl TomlCalendarStore { #[async_trait] impl CalendarStore for TomlCalendarStore { - async fn get_calendar(&self, id: &str) -> Result { - Ok(self.calendars.get(id).ok_or(anyhow!("not found"))?.clone()) + async fn get_calendar(&self, id: &str) -> Result { + Ok(self.calendars.get(id).ok_or(Error::NotFound)?.clone()) } - async fn get_calendars(&self, user: &str) -> Result> { + async fn get_calendars(&self, user: &str) -> Result, Error> { Ok(self .calendars .values() @@ -56,9 +55,9 @@ impl CalendarStore for TomlCalendarStore { .collect()) } - async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<()> { + async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error> { match self.calendars.entry(cid) { - Entry::Occupied(_) => Err(anyhow!("calendar already exists")), + Entry::Occupied(_) => Err(anyhow!("calendar already exists").into()), Entry::Vacant(v) => { v.insert(calendar); self.save().await.unwrap(); @@ -67,13 +66,13 @@ impl CalendarStore for TomlCalendarStore { } } - async fn delete_calendar(&mut self, cid: &str) -> Result<()> { + async fn delete_calendar(&mut self, cid: &str) -> Result<(), Error> { self.events.remove(cid); self.save().await.unwrap(); Ok(()) } - async fn get_events(&self, cid: &str) -> Result> { + async fn get_events(&self, cid: &str) -> Result, Error> { if let Some(events) = self.events.get(cid) { Ok(events.values().cloned().collect()) } else { @@ -81,19 +80,19 @@ impl CalendarStore for TomlCalendarStore { } } - async fn get_event(&self, cid: &str, uid: &str) -> Result { + async fn get_event(&self, cid: &str, uid: &str) -> Result { let events = self.events.get(cid).ok_or(anyhow!("not found"))?; - Ok(events.get(uid).ok_or(anyhow!("not found"))?.clone()) + Ok(events.get(uid).ok_or(Error::NotFound)?.clone()) } - async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<()> { + async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error> { let events = self.events.entry(cid).or_default(); events.insert(uid.clone(), Event::from_ics(uid, ics)?); self.save().await.unwrap(); Ok(()) } - async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<()> { + async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<(), Error> { if let Some(events) = self.events.get_mut(cid) { events.remove(uid); self.save().await?; diff --git a/src/app.rs b/src/app.rs index d376cb6..ced8015 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,7 +5,7 @@ use actix_web::dev::{ServiceFactory, ServiceRequest, ServiceResponse}; use actix_web::middleware::{Logger, NormalizePath}; use actix_web::{web, App}; use rustical_auth::CheckAuthentication; -use rustical_store::store::CalendarStore; +use rustical_store::CalendarStore; use tokio::sync::RwLock; pub fn make_app( diff --git a/src/main.rs b/src/main.rs index b5ebdd1..8c2b3c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,8 @@ use clap::Parser; use config::{CalendarStoreConfig, SqliteCalendarStoreConfig, TomlCalendarStoreConfig}; use rustical_auth::AuthProvider; use rustical_store::sqlite_store::{create_db_pool, SqliteCalendarStore}; -use rustical_store::store::CalendarStore; use rustical_store::toml_store::TomlCalendarStore; +use rustical_store::CalendarStore; use std::fs; use std::sync::Arc; use tokio::sync::RwLock;