use std::{num::TryFromIntError, string::FromUtf8Error, sync::Arc};
use base64::DecodeError;
use image::ImageError;
use jid::JID;
use lampada::error::{ActorError, ConnectionError, ReadError, WriteError};
use stanza::client::{Stanza, iq::Query};
use thiserror::Error;
pub use lampada::error::CommandError;
use crate::files::FileStore;
// for the client logic impl
#[derive(Debug, Error, Clone)]
pub enum Error<Fs: FileStore> {
#[error("core error: {0}")]
Connection(#[from] ConnectionError),
#[error("received unrecognized/unsupported content")]
UnrecognizedContent,
// TODO: include content
// UnrecognizedContent(peanuts::element::Content),
#[error("iq receive error: {0}")]
Iq(#[from] IqProcessError),
// TODO: change to Connecting(ConnectingError)
#[error("connecting: {0}")]
Connecting(#[from] ConnectionJobError),
#[error("presence: {0}")]
Presence(#[from] PresenceError),
#[error("set status: {0}")]
SetStatus(#[from] StatusError),
// TODO: have different ones for get/update/set
#[error("roster: {0}")]
Roster(#[from] RosterError),
#[error("stream error: {0}")]
Stream(#[from] stanza::stream::Error),
#[error("message send error: {0}")]
MessageSend(#[from] MessageSendError),
#[error("message receive error: {0}")]
MessageRecv(#[from] MessageRecvError<Fs>),
#[error("subscripbe error: {0}")]
Subscribe(#[from] SubscribeError),
#[error("publish error: {0}")]
Publish(#[from] PEPError),
}
#[derive(Debug, Error, Clone)]
pub enum MessageSendError {
#[error("could not add to message history: {0}")]
MessageHistory(#[from] DatabaseError),
#[error("could not mark chat as chatted: {0}")]
MarkChatAsChatted(DatabaseError),
#[error("could not get client user details: {0}")]
GetUserDetails(DatabaseError),
#[error("writing message to connection: {0}")]
Write(#[from] WriteError),
}
#[derive(Debug, Error, Clone)]
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)]
pub enum StatusError {
#[error("cache: {0}")]
Cache(#[from] DatabaseError),
#[error("stream write: {0}")]
Write(#[from] WriteError),
}
#[derive(Debug, Clone, Error)]
pub enum ConnectionJobError {
// #[error("connection failed: {0}")]
// ConnectionFailed(#[from] luz::Error),
#[error("failed roster retreival: {0}")]
RosterRetreival(#[from] RosterError),
#[error("failed to send available presence: {0}")]
SendPresence(#[from] WriteError),
#[error("cached status: {0}")]
StatusCacheError(#[from] DatabaseError),
}
#[derive(Debug, Error, Clone)]
pub enum RosterError {
#[error("cache: {0}")]
Cache(#[from] DatabaseError),
#[error("iq response: {0}")]
IqResponse(#[from] IqRequestError),
#[error("stream write: {0}")]
Write(#[from] WriteError),
// TODO: display for stanza, to show as xml, same for read error types.
#[error("unexpected reply: {0:?}")]
UnexpectedStanza(Stanza),
#[error("stanza error: {0}")]
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)]
pub enum DiscoError {
#[error("write error: {0}")]
Write(#[from] WriteError),
#[error("iq response: {0}")]
IqResponse(#[from] IqRequestError),
#[error("reply from incorrect entity: {0}")]
IncorrectEntity(JID),
#[error("unexpected reply: {0:?}")]
UnexpectedStanza(Stanza),
#[error("stanza error: {0}")]
StanzaError(#[from] stanza::client::error::Error),
#[error("disco result missing query item")]
MissingQuery,
#[error("disco error missing error")]
MissingError,
#[error("received mismatched query")]
MismatchedQuery(Query),
}
#[derive(Debug, Error, Clone)]
pub enum IqRequestError {
#[error("sending request: {0}")]
Write(#[from] WriteError),
#[error("receiving expected response: {0}")]
Read(#[from] ReadError),
}
#[derive(Debug, Error, Clone)]
pub enum ResponseError {
#[error("no matching id: {0}")]
NoMatchingId(String),
}
#[derive(Debug, Error, Clone)]
#[error("database error: {0}")]
pub struct DatabaseError(pub Arc<sqlx::Error>);
impl From<sqlx::Error> for DatabaseError {
fn from(e: sqlx::Error) -> Self {
Self(Arc::new(e))
}
}
impl From<sqlx::Error> for DatabaseOpenError {
fn from(e: sqlx::Error) -> Self {
Self::Error(Arc::new(e))
}
}
#[derive(Debug, Error, Clone)]
// TODO: should probably have all iq query related errors here, including read, write, stanza error, etc.
pub enum IqError {
#[error("writing response: {0}")]
WriteError(#[from] WriteError),
#[error("receiving response: `{0}`")]
ReceivedResponse(#[from] ResponseError),
#[error("incorrect addressee: {0}")]
IncorrectAddressee(jid::JID),
}
#[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 {
#[error("error: {0}")]
Error(Arc<sqlx::Error>),
#[error("migration: {0}")]
Migration(Arc<sqlx::migrate::MigrateError>),
#[error("io: {0}")]
Io(Arc<tokio::io::Error>),
#[error("invalid path")]
InvalidPath,
}
impl From<sqlx::migrate::MigrateError> for DatabaseOpenError {
fn from(e: sqlx::migrate::MigrateError) -> Self {
Self::Migration(Arc::new(e))
}
}
impl From<tokio::io::Error> for DatabaseOpenError {
fn from(e: tokio::io::Error) -> Self {
Self::Io(Arc::new(e))
}
}
#[derive(Debug, Error, Clone)]
pub enum SubscribeError {
#[error("write: {0}")]
Write(#[from] WriteError),
#[error("fetching client user details: {0}")]
Database(#[from] DatabaseError),
}
#[derive(Debug, Error, Clone)]
pub enum PresenceError {
#[error("unsupported")]
Unsupported,
#[error("missing from")]
MissingFrom,
#[error("stanza error: {0}")]
StanzaError(#[from] stanza::client::error::Error),
}
#[derive(Debug, Error, Clone)]
pub enum PEPError {
#[error("received mismatched query")]
MismatchedQuery(Query),
#[error("missing query")]
MissingQuery,
#[error("stanza errors: {0:?}")]
StanzaErrors(Vec<stanza::client::error::Error>),
#[error("reply from incorrect entity: {0}")]
IncorrectEntity(JID),
#[error("unexpected stanza: {0:?}")]
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<PEPError>),
#[error("updating database: {0}")]
Database(#[from] DatabaseError),
#[error("disconnected")]
Disconnected,
}
#[derive(Debug, Error, Clone)]
pub enum CapsDecodeError {
#[error("base64 decode: {0}")]
Base64Decode(#[from] base64::DecodeError),
#[error("utf8: {0}")]
UTF8(#[from] FromUtf8Error),
#[error("missing features")]
MissingFeatures,
#[error("missing identities")]
MissingIdentities,
#[error("missing identity category")]
MissingIdentityCategory,
#[error("missing identity type")]
MissingIdentityType,
#[error("missing identity language")]
MissingIdentityLang,
#[error("missing identity name")]
MissingIdentityName,
}
#[derive(Debug, Error, Clone)]
pub enum CapsEncodeError {
#[error("invalid data form in disco extensions")]
InvalidDataForm,
}
#[derive(Debug, Error, Clone)]
pub enum HashNodeConversionError {
#[error("no prefix")]
NoPrefix,
#[error("missing period")]
MissingPeriod,
}
#[derive(Debug, Error, Clone)]
pub enum CapsNodeConversionError {
#[error("missing hashtag")]
MissingHashtag,
}
#[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))
}
}