Error typing for rustical_store as well as some refactoring

This commit is contained in:
Lennart
2024-06-01 13:00:36 +02:00
parent 7fcd9a17f5
commit 1d763b5c8f
20 changed files with 135 additions and 79 deletions

View File

@@ -29,3 +29,4 @@ regex = "1.10"
lazy_static = "1.4"
rstest = "0.18.2"
rstest_reuse = "0.6.0"
thiserror = "1.0.61"

23
crates/store/src/error.rs Normal file
View File

@@ -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<sqlx::Error> for Error {
fn from(value: sqlx::Error) -> Self {
match value {
sqlx::Error::RowNotFound => Error::NotFound,
err => Error::SqlxError(err),
}
}
}

View File

@@ -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<Self> {
pub fn from_ics(uid: String, ics: String) -> Result<Self, Error> {
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

View File

@@ -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;

View File

@@ -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<EventRow> for Event {
type Error = anyhow::Error;
type Error = Error;
fn try_from(value: EventRow) -> Result<Self> {
fn try_from(value: EventRow) -> Result<Self, Error> {
Event::from_ics(value.uid, value.ics)
}
}
#[async_trait]
impl CalendarStore for SqliteCalendarStore {
async fn get_calendar(&self, id: &str) -> Result<Calendar> {
async fn get_calendar(&self, id: &str) -> Result<Calendar, Error> {
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<Vec<Calendar>> {
async fn get_calendars(&self, _owner: &str) -> Result<Vec<Calendar>, 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<Vec<Event>> {
async fn get_events(&self, cid: &str) -> Result<Vec<Event>, 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<Event> {
async fn get_event(&self, cid: &str, uid: &str) -> Result<Event, Error> {
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?;

View File

@@ -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<Calendar>;
async fn get_calendars(&self, owner: &str) -> Result<Vec<Calendar>>;
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<Calendar, Error>;
async fn get_calendars(&self, owner: &str) -> Result<Vec<Calendar>, 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<Vec<Event>>;
async fn get_event(&self, cid: &str, uid: &str) -> Result<Event>;
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<Vec<Event>, Error>;
async fn get_event(&self, cid: &str, uid: &str) -> Result<Event, Error>;
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>;
}

View File

@@ -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<Calendar> {
Ok(self.calendars.get(id).ok_or(anyhow!("not found"))?.clone())
async fn get_calendar(&self, id: &str) -> Result<Calendar, Error> {
Ok(self.calendars.get(id).ok_or(Error::NotFound)?.clone())
}
async fn get_calendars(&self, user: &str) -> Result<Vec<Calendar>> {
async fn get_calendars(&self, user: &str) -> Result<Vec<Calendar>, 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<Vec<Event>> {
async fn get_events(&self, cid: &str) -> Result<Vec<Event>, 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<Event> {
async fn get_event(&self, cid: &str, uid: &str) -> Result<Event, Error> {
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?;