diff options
Diffstat (limited to 'filamento/src/error.rs')
| -rw-r--r-- | filamento/src/error.rs | 344 |
1 files changed, 315 insertions, 29 deletions
diff --git a/filamento/src/error.rs b/filamento/src/error.rs index 5111413..be7af92 100644 --- a/filamento/src/error.rs +++ b/filamento/src/error.rs @@ -1,15 +1,28 @@ -use std::{string::FromUtf8Error, sync::Arc}; +// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden> +// +// SPDX-License-Identifier: AGPL-3.0-or-later +use std::{num::TryFromIntError, string::FromUtf8Error, sync::Arc}; + +use base64::DecodeError; +use image::ImageError; use jid::JID; -use lampada::error::{ConnectionError, ReadError, WriteError}; +use jid::JIDError; +use lampada::error::{ActorError, ReadError, WriteError}; use stanza::client::{Stanza, iq::Query}; use thiserror::Error; pub use lampada::error::CommandError; +pub use lampada::error::ConnectionError; +use tokio::sync::mpsc::error::SendError; +use tokio::sync::oneshot::error::RecvError; + +use crate::db::DbCommand; +use crate::files::FileStore; // for the client logic impl #[derive(Debug, Error, Clone)] -pub enum Error { +pub enum Error<Fs: FileStore> { #[error("core error: {0}")] Connection(#[from] ConnectionError), #[error("received unrecognized/unsupported content")] @@ -17,7 +30,7 @@ pub enum Error { // TODO: include content // UnrecognizedContent(peanuts::element::Content), #[error("iq receive error: {0}")] - Iq(#[from] IqError), + Iq(#[from] IqProcessError), // TODO: change to Connecting(ConnectingError) #[error("connecting: {0}")] Connecting(#[from] ConnectionJobError), @@ -33,11 +46,11 @@ pub enum Error { #[error("message send error: {0}")] MessageSend(#[from] MessageSendError), #[error("message receive error: {0}")] - MessageRecv(#[from] MessageRecvError), + MessageRecv(#[from] MessageRecvError<Fs>), #[error("subscripbe error: {0}")] Subscribe(#[from] SubscribeError), #[error("publish error: {0}")] - Publish(#[from] PublishError), + Publish(#[from] PEPError), } #[derive(Debug, Error, Clone)] @@ -53,13 +66,29 @@ pub enum MessageSendError { } #[derive(Debug, Error, Clone)] -pub enum MessageRecvError { +pub enum MessageRecvError<Fs: FileStore> { #[error("could not add to message history: {0}")] MessageHistory(#[from] DatabaseError), #[error("missing from")] MissingFrom, #[error("could not update user nick: {0}")] NickUpdate(DatabaseError), + #[error("could not update user avatar: {0}")] + AvatarUpdate(#[from] AvatarUpdateError<Fs>), +} + +#[derive(Debug, Error, Clone)] +pub enum AvatarUpdateError<Fs: FileStore> { + #[error("could not save to disk: {0}")] + FileStore(Fs::Err), + #[error("could not fetch avatar data: {0}")] + PEPError(#[from] CommandError<PEPError>), + #[error("base64 decode: {0}")] + Base64(#[from] DecodeError), + #[error("pep node missing avatar data")] + MissingData, + #[error("database: {0}")] + Database(#[from] DatabaseError), } #[derive(Debug, Error, Clone)] @@ -97,6 +126,17 @@ pub enum RosterError { StanzaError(#[from] stanza::client::error::Error), #[error("could not reply to roster push: {0}")] PushReply(WriteError), + #[error("actor error: {0}")] + Actor(ActorError), +} + +impl From<CommandError<RosterError>> for RosterError { + fn from(value: CommandError<RosterError>) -> Self { + match value { + CommandError::Actor(actor_error) => Self::Actor(actor_error), + CommandError::Error(e) => e, + } + } } #[derive(Debug, Error, Clone)] @@ -134,17 +174,104 @@ pub enum ResponseError { } #[derive(Debug, Error, Clone)] -#[error("database error: {0}")] -pub struct DatabaseError(pub Arc<sqlx::Error>); +pub enum DatabaseError { + #[error("database error: {0}")] + Database(Serializeable<Arc<rusqlite::Error>>), + #[error("database command send: {0}")] + Send(Arc<SendError<DbCommand>>), + #[error("database result recv: {0}")] + Recv(#[from] RecvError), +} + +impl From<SendError<DbCommand>> for DatabaseError { + fn from(e: SendError<DbCommand>) -> Self { + Self::Send(Arc::new(e)) + } +} + +pub enum Serializeable<T> { + String(String), + Unserialized(T), +} + +impl<T: std::fmt::Display> std::fmt::Display for Serializeable<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Serializeable::String(s) => s.fmt(f), + Serializeable::Unserialized(t) => t.fmt(f), + } + } +} + +impl<T: std::fmt::Debug> std::fmt::Debug for Serializeable<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Serializeable::String(s) => s.fmt(f), + Serializeable::Unserialized(t) => t.fmt(f), + } + } +} + +impl<T: Clone> Clone for Serializeable<T> { + fn clone(&self) -> Self { + match self { + Serializeable::String(s) => Self::String(s.clone()), + Serializeable::Unserialized(t) => Self::Unserialized(t.clone()), + } + } +} + +#[cfg(feature = "serde")] +struct StringVisitor; + +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for StringVisitor { + type Value = String; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_string<E>(self, v: String) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(v) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for DatabaseError { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let string = deserializer.deserialize_string(StringVisitor)?; + Ok(Self::Database(Serializeable::String(string))) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for DatabaseError { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + match &self.0 { + Serializeable::String(s) => serializer.serialize_str(s), + Serializeable::Unserialized(u) => serializer.serialize_str(&u.to_string()), + } + } +} -impl From<sqlx::Error> for DatabaseError { - fn from(e: sqlx::Error) -> Self { - Self(Arc::new(e)) +impl From<rusqlite::Error> for DatabaseError { + fn from(e: rusqlite::Error) -> Self { + Self::Database(Serializeable::Unserialized(Arc::new(e))) } } -impl From<sqlx::Error> for DatabaseOpenError { - fn from(e: sqlx::Error) -> Self { +impl From<rusqlite::Error> for DatabaseOpenError { + fn from(e: rusqlite::Error) -> Self { Self::Error(Arc::new(e)) } } @@ -161,25 +288,149 @@ pub enum IqError { } #[derive(Debug, Error, Clone)] +pub enum IqProcessError { + #[error("iq error")] + Iq(#[from] IqError), + #[error("roster push")] + Roster(#[from] RosterError), +} + +#[derive(Debug, Error, Clone)] pub enum DatabaseOpenError { + #[cfg(target_arch = "wasm32")] + #[error("opfs: {0}")] + OpfsSAH(#[from] OpfsSAHError), #[error("error: {0}")] - Error(Arc<sqlx::Error>), - #[error("migration: {0}")] - Migration(Arc<sqlx::migrate::MigrateError>), + Error(Arc<rusqlite::Error>), + // #[error("migration: {0}")] + // Migration(Arc<rusqlite::migrate::MigrateError>), #[error("io: {0}")] - Io(Arc<tokio::io::Error>), + Io(Arc<std::io::Error>), #[error("invalid path")] InvalidPath, + #[error("tokio oneshot recv error: {0}")] + Recv(#[from] tokio::sync::oneshot::error::RecvError), } -impl From<sqlx::migrate::MigrateError> for DatabaseOpenError { - fn from(e: sqlx::migrate::MigrateError) -> Self { - Self::Migration(Arc::new(e)) +// impl From<sqlx::migrate::MigrateError> for DatabaseOpenError { +// fn from(e: sqlx::migrate::MigrateError) -> Self { +// Self::Migration(Arc::new(e)) +// } +// } + +#[cfg(target_arch = "wasm32")] +impl From<rusqlite::ffi::OpfsSAHError> for OpfsSAHError { + fn from(e: rusqlite::ffi::OpfsSAHError) -> Self { + use wasm_bindgen::UnwrapThrowExt; + match e { + rusqlite::ffi::OpfsSAHError::Vfs(_register_vfs_error) => Self::VfsRegistration, + rusqlite::ffi::OpfsSAHError::ImportDb(_import_db_error) => Self::ImportDb, + rusqlite::ffi::OpfsSAHError::NotSuported => Self::NotSupported, + rusqlite::ffi::OpfsSAHError::GetDirHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetDirHandle(message) + } + rusqlite::ffi::OpfsSAHError::GetFileHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetFileHandle(message) + } + rusqlite::ffi::OpfsSAHError::CreateSyncAccessHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::CreateSyncAccessHandle(message) + } + rusqlite::ffi::OpfsSAHError::IterHandle(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::IterHandle(message) + } + rusqlite::ffi::OpfsSAHError::GetPath(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetPath(message) + } + rusqlite::ffi::OpfsSAHError::RemoveEntity(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::RemoveEntity(message) + } + rusqlite::ffi::OpfsSAHError::GetSize(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::GetSize(message) + } + rusqlite::ffi::OpfsSAHError::Read(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Read(message) + } + rusqlite::ffi::OpfsSAHError::Write(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Write(message) + } + rusqlite::ffi::OpfsSAHError::Flush(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Flush(message) + } + rusqlite::ffi::OpfsSAHError::Truncate(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Truncate(message) + } + rusqlite::ffi::OpfsSAHError::Reflect(js_value) => { + let error: js_sys::Error = js_value.into(); + let message = error.message().as_string().unwrap_throw(); + Self::Reflect(message) + } + rusqlite::ffi::OpfsSAHError::Generic(s) => Self::Generic(s), + rusqlite::ffi::OpfsSAHError::Custom(s) => Self::Generic(s), + } } } -impl From<tokio::io::Error> for DatabaseOpenError { - fn from(e: tokio::io::Error) -> Self { +#[cfg(target_arch = "wasm32")] +#[derive(Debug, Error, Clone)] +pub enum OpfsSAHError { + #[error("VFS registration")] + VfsRegistration, + #[error("import db error")] + ImportDb, + #[error("not supported")] + NotSupported, + #[error("get dir handle: {0}")] + GetDirHandle(String), + #[error("get file handle: {0}")] + GetFileHandle(String), + #[error("create sync access handle: {0}")] + CreateSyncAccessHandle(String), + #[error("iter handle: {0}")] + IterHandle(String), + #[error("get path: {0}")] + GetPath(String), + #[error("remove entity: {0}")] + RemoveEntity(String), + #[error("get size: {0}")] + GetSize(String), + #[error("read: {0}")] + Read(String), + #[error("write: {0}")] + Write(String), + #[error("flush: {0}")] + Flush(String), + #[error("truncate: {0}")] + Truncate(String), + #[error("reflect: {0}")] + Reflect(String), + #[error("generic: {0}")] + Generic(String), +} + +impl From<std::io::Error> for DatabaseOpenError { + fn from(e: std::io::Error) -> Self { Self::Io(Arc::new(e)) } } @@ -198,12 +449,14 @@ pub enum PresenceError { Unsupported, #[error("missing from")] MissingFrom, - #[error("stanza error: {0}")] - StanzaError(#[from] stanza::client::error::Error), + #[error("stanza error: {0:?}")] + StanzaError(Option<stanza::client::error::Error>), + #[error("received subscription request from a non-bare jid")] + InvalidSubscriptionRequest(#[from] JIDError), } #[derive(Debug, Error, Clone)] -pub enum PublishError { +pub enum PEPError { #[error("received mismatched query")] MismatchedQuery(Query), #[error("missing query")] @@ -216,12 +469,19 @@ pub enum PublishError { UnexpectedStanza(Stanza), #[error("iq response: {0}")] IqResponse(#[from] IqRequestError), + #[error("missing pep item")] + MissingItem, + #[error("incorrect item id: expected {0}, got {1}")] + IncorrectItemID(String, String), + #[error("unsupported pep item")] + UnsupportedItem, + // TODO: should the item be in the error? } #[derive(Debug, Error, Clone)] pub enum NickError { #[error("publishing nick: {0}")] - Publish(#[from] CommandError<PublishError>), + Publish(#[from] CommandError<PEPError>), #[error("updating database: {0}")] Database(#[from] DatabaseError), #[error("disconnected")] @@ -267,5 +527,31 @@ pub enum CapsNodeConversionError { #[error("missing hashtag")] MissingHashtag, } -// #[derive(Debug, Error, Clone)] -// pub enum CapsError {} + +#[derive(Debug, Error, Clone)] +pub enum AvatarPublishError<Fs: FileStore> { + #[error("disconnected")] + Disconnected, + #[error("image read: {0}")] + Read(Arc<std::io::Error>), + #[error("image: {0}")] + Image(Arc<ImageError>), + #[error("pep publish: {0}")] + Publish(#[from] CommandError<PEPError>), + #[error("bytes number conversion: {0}")] + FromInt(#[from] TryFromIntError), + #[error("could not save to disk")] + FileStore(Fs::Err), +} + +impl<Fs: FileStore> From<std::io::Error> for AvatarPublishError<Fs> { + fn from(value: std::io::Error) -> Self { + Self::Read(Arc::new(value)) + } +} + +impl<Fs: FileStore> From<ImageError> for AvatarPublishError<Fs> { + fn from(value: ImageError) -> Self { + Self::Image(Arc::new(value)) + } +} |
