aboutsummaryrefslogtreecommitdiffstats
path: root/filamento/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'filamento/src/error.rs')
-rw-r--r--filamento/src/error.rs344
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))
+ }
+}