refactor(luz): error types

This commit is contained in:
cel 🌸 2025-02-25 23:29:44 +00:00
parent d797061786
commit 4dac2dbe1d
7 changed files with 375 additions and 237 deletions

View File

@ -16,3 +16,4 @@ tokio-util = "0.7.13"
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.19"
uuid = { version = "1.13.1", features = ["v4"] } uuid = { version = "1.13.1", features = ["v4"] }
thiserror = "2.0.11"

View File

@ -20,7 +20,7 @@ use write::{WriteControl, WriteControlHandle, WriteHandle, WriteMessage};
use crate::{ use crate::{
db::Db, db::Db,
error::{Error, Reason}, error::{Error, ReadError, WriteError},
UpdateMessage, UpdateMessage,
}; };
@ -36,7 +36,7 @@ pub struct Supervisor {
tokio::task::JoinSet<()>, tokio::task::JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
sender: mpsc::Sender<UpdateMessage>, sender: mpsc::Sender<UpdateMessage>,
writer_handle: WriteControlHandle, writer_handle: WriteControlHandle,
@ -62,7 +62,7 @@ pub enum State {
tokio::task::JoinSet<()>, tokio::task::JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
), ),
), ),
} }
@ -77,7 +77,7 @@ impl Supervisor {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
sender: mpsc::Sender<UpdateMessage>, sender: mpsc::Sender<UpdateMessage>,
writer_handle: WriteControlHandle, writer_handle: WriteControlHandle,
@ -180,7 +180,7 @@ impl Supervisor {
// if reconnection failure, respond to all current write messages with lost connection error. the received processes should complete themselves. // if reconnection failure, respond to all current write messages with lost connection error. the received processes should complete themselves.
write_state.close(); write_state.close();
while let Some(msg) = write_state.recv().await { while let Some(msg) = write_state.recv().await {
let _ = msg.respond_to.send(Err(Reason::LostConnection)); let _ = msg.respond_to.send(Err(WriteError::LostConnection));
} }
// TODO: is this the correct error? // TODO: is this the correct error?
let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await; let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await;
@ -227,9 +227,9 @@ impl Supervisor {
Err(e) => { Err(e) => {
// if reconnection failure, respond to all current write messages with lost connection error. the received processes should complete themselves. // if reconnection failure, respond to all current write messages with lost connection error. the received processes should complete themselves.
write_recv.close(); write_recv.close();
let _ = write_msg.respond_to.send(Err(Reason::LostConnection)); let _ = write_msg.respond_to.send(Err(WriteError::LostConnection));
while let Some(msg) = write_recv.recv().await { while let Some(msg) = write_recv.recv().await {
let _ = msg.respond_to.send(Err(Reason::LostConnection)); let _ = msg.respond_to.send(Err(WriteError::LostConnection));
} }
// TODO: is this the correct error to send? // TODO: is this the correct error to send?
let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await; let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await;
@ -278,10 +278,10 @@ impl Supervisor {
// if reconnection failure, respond to all current messages with lost connection error. // if reconnection failure, respond to all current messages with lost connection error.
write_receiver.close(); write_receiver.close();
if let Some(msg) = retry_msg { if let Some(msg) = retry_msg {
msg.respond_to.send(Err(Reason::LostConnection)); msg.respond_to.send(Err(WriteError::LostConnection));
} }
while let Some(msg) = write_receiver.recv().await { while let Some(msg) = write_receiver.recv().await {
msg.respond_to.send(Err(Reason::LostConnection)); msg.respond_to.send(Err(WriteError::LostConnection));
} }
// TODO: is this the correct error? // TODO: is this the correct error?
let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await; let _ = self.sender.send(UpdateMessage::Error(Error::LostConnection)).await;
@ -342,7 +342,7 @@ impl SupervisorHandle {
on_shutdown: oneshot::Sender<()>, on_shutdown: oneshot::Sender<()>,
jid: Arc<Mutex<JID>>, jid: Arc<Mutex<JID>>,
password: Arc<String>, password: Arc<String>,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) -> (WriteHandle, Self) { ) -> (WriteHandle, Self) {
let (command_sender, command_receiver) = mpsc::channel(20); let (command_sender, command_receiver) = mpsc::channel(20);
let (writer_error_sender, writer_error_receiver) = oneshot::channel(); let (writer_error_sender, writer_error_receiver) = oneshot::channel();

View File

@ -18,16 +18,13 @@ use uuid::Uuid;
use crate::{ use crate::{
chat::{Body, Message}, chat::{Body, Message},
db::Db, db::Db,
error::{Error, IqError, PresenceError, Reason, RecvMessageError}, error::{Error, IqError, MessageRecvError, PresenceError, ReadError, RosterError},
presence::{Offline, Online, Presence, Show}, presence::{Offline, Online, Presence, Show},
roster::Contact, roster::Contact,
UpdateMessage, UpdateMessage,
}; };
use super::{ use super::{write::WriteHandle, SupervisorCommand};
write::{WriteHandle, WriteMessage},
SupervisorCommand,
};
pub struct Read { pub struct Read {
control_receiver: mpsc::Receiver<ReadControl>, control_receiver: mpsc::Receiver<ReadControl>,
@ -38,7 +35,7 @@ pub struct Read {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
db: Db, db: Db,
update_sender: mpsc::Sender<UpdateMessage>, update_sender: mpsc::Sender<UpdateMessage>,
@ -48,7 +45,7 @@ pub struct Read {
disconnecting: bool, disconnecting: bool,
disconnect_timedout: oneshot::Receiver<()>, disconnect_timedout: oneshot::Receiver<()>,
// TODO: use proper stanza ids // TODO: use proper stanza ids
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
} }
impl Read { impl Read {
@ -61,7 +58,7 @@ impl Read {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
db: Db, db: Db,
update_sender: mpsc::Sender<UpdateMessage>, update_sender: mpsc::Sender<UpdateMessage>,
@ -69,9 +66,9 @@ impl Read {
supervisor_control: mpsc::Sender<SupervisorCommand>, supervisor_control: mpsc::Sender<SupervisorCommand>,
write_handle: WriteHandle, write_handle: WriteHandle,
tasks: JoinSet<()>, tasks: JoinSet<()>,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) -> Self { ) -> Self {
let (send, recv) = oneshot::channel(); let (_send, recv) = oneshot::channel();
Self { Self {
control_receiver, control_receiver,
stream, stream,
@ -162,7 +159,7 @@ impl Read {
// when it aborts, must clear iq map no matter what // when it aborts, must clear iq map no matter what
let mut iqs = self.pending_iqs.lock().await; let mut iqs = self.pending_iqs.lock().await;
for (_id, sender) in iqs.drain() { for (_id, sender) in iqs.drain() {
let _ = sender.send(Err(Reason::LostConnection)); let _ = sender.send(Err(ReadError::LostConnection));
} }
} }
} }
@ -178,7 +175,7 @@ async fn handle_stanza(
db: Db, db: Db,
supervisor_control: mpsc::Sender<SupervisorCommand>, supervisor_control: mpsc::Sender<SupervisorCommand>,
write_handle: WriteHandle, write_handle: WriteHandle,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) { ) {
match stanza { match stanza {
Stanza::Message(stanza_message) => { Stanza::Message(stanza_message) => {
@ -207,7 +204,9 @@ async fn handle_stanza(
if let Err(e) = result { if let Err(e) = result {
tracing::error!("messagecreate"); tracing::error!("messagecreate");
let _ = update_sender let _ = update_sender
.send(UpdateMessage::Error(Error::CacheUpdate(e.into()))) .send(UpdateMessage::Error(Error::MessageRecv(
MessageRecvError::MessageHistory(e.into()),
)))
.await; .await;
} }
let _ = update_sender let _ = update_sender
@ -215,8 +214,8 @@ async fn handle_stanza(
.await; .await;
} else { } else {
let _ = update_sender let _ = update_sender
.send(UpdateMessage::Error(Error::RecvMessage( .send(UpdateMessage::Error(Error::MessageRecv(
RecvMessageError::MissingFrom, MessageRecvError::MissingFrom,
))) )))
.await; .await;
} }
@ -229,9 +228,16 @@ async fn handle_stanza(
stanza::client::presence::PresenceType::Error => { stanza::client::presence::PresenceType::Error => {
// TODO: is there any other information that should go with the error? also MUST have an error, otherwise it's a different error. maybe it shoulnd't be an option. // TODO: is there any other information that should go with the error? also MUST have an error, otherwise it's a different error. maybe it shoulnd't be an option.
let _ = update_sender let _ = update_sender
.send(UpdateMessage::Error(Error::Presence(PresenceError::Error( .send(UpdateMessage::Error(Error::Presence(
Reason::Stanza(presence.errors.first().cloned()), // TODO: ughhhhhhhhhhhhh these stanza errors should probably just have an option, and custom display
)))) PresenceError::StanzaError(
presence
.errors
.first()
.cloned()
.expect("error MUST have error"),
),
)))
.await; .await;
} }
// should not happen (error to server) // should not happen (error to server)
@ -329,8 +335,8 @@ async fn handle_stanza(
let contact: Contact = item.into(); let contact: Contact = item.into();
if let Err(e) = db.upsert_contact(contact.clone()).await { if let Err(e) = db.upsert_contact(contact.clone()).await {
let _ = update_sender let _ = update_sender
.send(UpdateMessage::Error(Error::CacheUpdate( .send(UpdateMessage::Error(Error::Roster(
e.into(), RosterError::Cache(e.into()),
))) )))
.await; .await;
} }
@ -381,7 +387,7 @@ pub enum ReadControl {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
), ),
} }
@ -414,13 +420,13 @@ impl ReadControlHandle {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
db: Db, db: Db,
sender: mpsc::Sender<UpdateMessage>, sender: mpsc::Sender<UpdateMessage>,
supervisor_control: mpsc::Sender<SupervisorCommand>, supervisor_control: mpsc::Sender<SupervisorCommand>,
jabber_write: WriteHandle, jabber_write: WriteHandle,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) -> Self { ) -> Self {
let (control_sender, control_receiver) = mpsc::channel(20); let (control_sender, control_receiver) = mpsc::channel(20);
@ -451,14 +457,14 @@ impl ReadControlHandle {
JoinSet<()>, JoinSet<()>,
mpsc::Sender<SupervisorCommand>, mpsc::Sender<SupervisorCommand>,
WriteHandle, WriteHandle,
Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
)>, )>,
db: Db, db: Db,
sender: mpsc::Sender<UpdateMessage>, sender: mpsc::Sender<UpdateMessage>,
supervisor_control: mpsc::Sender<SupervisorCommand>, supervisor_control: mpsc::Sender<SupervisorCommand>,
jabber_write: WriteHandle, jabber_write: WriteHandle,
tasks: JoinSet<()>, tasks: JoinSet<()>,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) -> Self { ) -> Self {
let (control_sender, control_receiver) = mpsc::channel(20); let (control_sender, control_receiver) = mpsc::channel(20);

View File

@ -7,7 +7,7 @@ use tokio::{
task::JoinHandle, task::JoinHandle,
}; };
use crate::error::{Error, Reason}; use crate::error::WriteError;
// actor that receives jabber stanzas to write, and if there is an error, sends a message back to the supervisor then aborts, so the supervisor can spawn a new stream. // actor that receives jabber stanzas to write, and if there is an error, sends a message back to the supervisor then aborts, so the supervisor can spawn a new stream.
pub struct Write { pub struct Write {
@ -17,9 +17,10 @@ pub struct Write {
on_crash: oneshot::Sender<(WriteMessage, mpsc::Receiver<WriteMessage>)>, on_crash: oneshot::Sender<(WriteMessage, mpsc::Receiver<WriteMessage>)>,
} }
#[derive(Debug)]
pub struct WriteMessage { pub struct WriteMessage {
pub stanza: Stanza, pub stanza: Stanza,
pub respond_to: oneshot::Sender<Result<(), Reason>>, pub respond_to: oneshot::Sender<Result<(), WriteError>>,
} }
pub enum WriteControl { pub enum WriteControl {
@ -84,9 +85,9 @@ impl Write {
Err(e) => match &e { Err(e) => match &e {
peanuts::Error::ReadError(_error) => { peanuts::Error::ReadError(_error) => {
// if connection lost during disconnection, just send lost connection error to the write requests // if connection lost during disconnection, just send lost connection error to the write requests
let _ = msg.respond_to.send(Err(Reason::LostConnection)); let _ = msg.respond_to.send(Err(WriteError::LostConnection));
while let Some(msg) = self.stanza_receiver.recv().await { while let Some(msg) = self.stanza_receiver.recv().await {
let _ = msg.respond_to.send(Err(Reason::LostConnection)); let _ = msg.respond_to.send(Err(WriteError::LostConnection));
} }
break; break;
} }
@ -140,16 +141,16 @@ pub struct WriteHandle {
} }
impl WriteHandle { impl WriteHandle {
pub async fn write(&self, stanza: Stanza) -> Result<(), Reason> { pub async fn write(&self, stanza: Stanza) -> Result<(), WriteError> {
let (send, recv) = oneshot::channel(); let (send, recv) = oneshot::channel();
self.send(WriteMessage { self.send(WriteMessage {
stanza, stanza,
respond_to: send, respond_to: send,
}) })
.await .await
.map_err(|_| Reason::ChannelSend)?; .map_err(|e| WriteError::Actor(e.into()))?;
// TODO: timeout // TODO: timeout
recv.await? recv.await.map_err(|e| WriteError::Actor(e.into()))?
} }
} }

View File

@ -1,138 +1,158 @@
use stanza::client::Stanza; use std::sync::Arc;
use tokio::sync::oneshot::{self};
#[derive(Debug)] use stanza::client::Stanza;
use thiserror::Error;
use tokio::sync::{mpsc::error::SendError, oneshot::error::RecvError};
#[derive(Debug, Error, Clone)]
pub enum Error { pub enum Error {
#[error("already connected")]
AlreadyConnected, AlreadyConnected,
// TODO: change to Connecting(ConnectingError) // TODO: change to Connecting(ConnectingError)
Connection(ConnectionError), #[error("connecting: {0}")]
Presence(PresenceError), Connecting(#[from] ConnectionError),
SetStatus(Reason), #[error("presence: {0}")]
Roster(Reason), Presence(#[from] PresenceError),
Stream(stanza::stream::Error), #[error("set status: {0}")]
SendMessage(Reason), SetStatus(#[from] StatusError),
RecvMessage(RecvMessageError), // TODO: have different ones for get/update/set
#[error("roster: {0}")]
Roster(RosterError),
#[error("stream error: {0}")]
Stream(#[from] stanza::stream::Error),
#[error("message send error: {0}")]
MessageSend(MessageSendError),
#[error("message receive error: {0}")]
MessageRecv(MessageRecvError),
#[error("already disconnected")]
AlreadyDisconnected, AlreadyDisconnected,
#[error("lost connection")]
LostConnection, LostConnection,
// TODO: should all cache update errors include the context? // TODO: Display for Content
CacheUpdate(Reason), #[error("received unrecognized/unsupported content: {0:?}")]
UnrecognizedContent(peanuts::element::Content), UnrecognizedContent(peanuts::element::Content),
#[error("iq receive error: {0}")]
Iq(IqError), Iq(IqError),
Cloned, #[error("disconnected")]
Disconnected,
} }
// TODO: this is horrifying, maybe just use tracing to forward error events??? #[derive(Debug, Error, Clone)]
impl Clone for Error { pub enum MessageSendError {
fn clone(&self) -> Self { #[error("could not add to message history: {0}")]
Error::Cloned MessageHistory(#[from] DatabaseError),
}
} }
#[derive(Debug)] #[derive(Debug, Error, Clone)]
pub enum PresenceError { pub enum PresenceError {
Error(Reason), #[error("unsupported")]
Unsupported, Unsupported,
#[error("missing from")]
MissingFrom, MissingFrom,
#[error("stanza error: {0}")]
StanzaError(#[from] stanza::client::error::Error),
} }
#[derive(Debug)] #[derive(Debug, Error, Clone)]
// TODO: should probably have all iq query related errors here, including read, write, stanza error, etc.
pub enum IqError { pub enum IqError {
#[error("no iq with id matching `{0}`")]
NoMatchingId(String), NoMatchingId(String),
} }
#[derive(Debug)] #[derive(Debug, Error, Clone)]
pub enum RecvMessageError { pub enum MessageRecvError {
#[error("could not add to message history: {0}")]
MessageHistory(#[from] DatabaseError),
#[error("missing from")]
MissingFrom, MissingFrom,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Error)]
pub enum ConnectionError { pub enum ConnectionError {
ConnectionFailed(Reason), #[error("connection failed: {0}")]
RosterRetreival(Reason), ConnectionFailed(#[from] jabber::Error),
SendPresence(Reason), #[error("failed roster retreival: {0}")]
NoCachedStatus(Reason), RosterRetreival(#[from] RosterError),
#[error("failed to send available presence: {0}")]
SendPresence(#[from] WriteError),
#[error("cached status: {0}")]
StatusCacheError(#[from] DatabaseError),
} }
#[derive(Debug)] #[derive(Debug, Error, Clone)]
pub struct RosterError(pub Reason); pub enum RosterError {
#[error("cache: {0}")]
impl From<RosterError> for Error { Cache(#[from] DatabaseError),
fn from(e: RosterError) -> Self { #[error("stream write: {0}")]
Self::Roster(e.0) Write(#[from] WriteError),
} // TODO: display for stanza, to show as xml, same for read error types.
} #[error("unexpected reply: {0:?}")]
impl From<RosterError> for ConnectionError {
fn from(e: RosterError) -> Self {
Self::RosterRetreival(e.0)
}
}
pub struct StatusError(pub Reason);
impl From<StatusError> for Error {
fn from(e: StatusError) -> Self {
Error::SetStatus(e.0)
}
}
impl From<StatusError> for ConnectionError {
fn from(e: StatusError) -> Self {
Self::SendPresence(e.0)
}
}
#[derive(Debug)]
pub enum Reason {
// TODO: organisastion of error into internal error thing
Timeout,
Stream(stanza::stream_error::Error),
Stanza(Option<stanza::client::error::Error>),
Jabber(jabber::Error),
XML(peanuts::Error),
SQL(sqlx::Error),
// JID(jid::ParseError),
LostConnection,
OneshotRecv(oneshot::error::RecvError),
UnexpectedStanza(Stanza), UnexpectedStanza(Stanza),
Disconnected, #[error("stream read: {0}")]
ChannelSend, Read(#[from] ReadError),
Cloned, #[error("stanza error: {0}")]
StanzaError(#[from] stanza::client::error::Error),
} }
// TODO: same here #[derive(Debug, Error, Clone)]
impl Clone for Reason { #[error("database error: {0}")]
fn clone(&self) -> Self { pub struct DatabaseError(Arc<sqlx::Error>);
Reason::Cloned
}
}
impl From<oneshot::error::RecvError> for Reason { impl From<sqlx::Error> for DatabaseError {
fn from(e: oneshot::error::RecvError) -> Reason {
Self::OneshotRecv(e)
}
}
impl From<peanuts::Error> for Reason {
fn from(e: peanuts::Error) -> Self {
Self::XML(e)
}
}
// impl From<jid::ParseError> for Reason {
// fn from(e: jid::ParseError) -> Self {
// Self::JID(e)
// }
// }
impl From<sqlx::Error> for Reason {
fn from(e: sqlx::Error) -> Self { fn from(e: sqlx::Error) -> Self {
Self::SQL(e) Self(Arc::new(e))
} }
} }
impl From<jabber::Error> for Reason { #[derive(Debug, Error, Clone)]
fn from(e: jabber::Error) -> Self { pub enum StatusError {
Self::Jabber(e) #[error("cache: {0}")]
Cache(#[from] DatabaseError),
#[error("stream write: {0}")]
Write(#[from] WriteError),
}
#[derive(Debug, Error, Clone)]
pub enum WriteError {
#[error("xml: {0}")]
XML(#[from] peanuts::Error),
#[error("lost connection")]
LostConnection,
// TODO: should this be in writeerror or separate?
#[error("actor: {0}")]
Actor(#[from] ActorError),
#[error("disconnected")]
Disconnected,
}
// TODO: separate peanuts read and write error?
#[derive(Debug, Error, Clone)]
pub enum ReadError {
#[error("xml: {0}")]
XML(#[from] peanuts::Error),
#[error("lost connection")]
LostConnection,
}
#[derive(Debug, Error, Clone)]
pub enum ActorError {
#[error("receive timed out")]
Timeout,
#[error("could not send message to actor, channel closed")]
Send,
#[error("could not receive message from actor, channel closed")]
Receive,
}
impl<T> From<SendError<T>> for ActorError {
fn from(_e: SendError<T>) -> Self {
Self::Send
}
}
impl From<RecvError> for ActorError {
fn from(_e: RecvError) -> Self {
Self::Receive
} }
} }

View File

@ -7,7 +7,7 @@ use std::{
use chat::{Body, Chat, Message}; use chat::{Body, Chat, Message};
use connection::{write::WriteMessage, SupervisorSender}; use connection::{write::WriteMessage, SupervisorSender};
use db::Db; use db::Db;
use error::{ConnectionError, Reason, RosterError, StatusError}; use error::{ConnectionError, DatabaseError, ReadError, RosterError, StatusError, WriteError};
use futures::{future::Fuse, FutureExt}; use futures::{future::Fuse, FutureExt};
use jabber::JID; use jabber::JID;
use presence::{Offline, Online, Presence}; use presence::{Offline, Online, Presence};
@ -21,7 +21,7 @@ use tokio::{
sync::{mpsc, oneshot, Mutex}, sync::{mpsc, oneshot, Mutex},
task::JoinSet, task::JoinSet,
}; };
use tracing::{debug, info, Instrument}; use tracing::{debug, info};
use user::User; use user::User;
use uuid::Uuid; use uuid::Uuid;
@ -44,7 +44,7 @@ pub struct Luz {
// TODO: use a dyn passwordprovider trait to avoid storing password in memory // TODO: use a dyn passwordprovider trait to avoid storing password in memory
password: Arc<String>, password: Arc<String>,
connected: Arc<Mutex<Option<(WriteHandle, SupervisorHandle)>>>, connected: Arc<Mutex<Option<(WriteHandle, SupervisorHandle)>>>,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
db: Db, db: Db,
sender: mpsc::Sender<UpdateMessage>, sender: mpsc::Sender<UpdateMessage>,
/// if connection was shut down due to e.g. server shutdown, supervisor must be able to mark client as disconnected /// if connection was shut down due to e.g. server shutdown, supervisor must be able to mark client as disconnected
@ -159,8 +159,8 @@ impl Luz {
let _ = self let _ = self
.sender .sender
.send(UpdateMessage::Error( .send(UpdateMessage::Error(
Error::Connection( Error::Connecting(
ConnectionError::NoCachedStatus( ConnectionError::StatusCacheError(
e.into(), e.into(),
), ),
), ),
@ -170,16 +170,20 @@ impl Luz {
} }
}; };
let (send, recv) = oneshot::channel(); let (send, recv) = oneshot::channel();
CommandMessage::SetStatus(online.clone(), send) CommandMessage::SendPresence(
.handle_online( None,
writer.clone(), Presence::Online(online.clone()),
supervisor.sender(), send,
self.jid.clone(), )
self.db.clone(), .handle_online(
self.sender.clone(), writer.clone(),
self.pending_iqs.clone(), supervisor.sender(),
) self.jid.clone(),
.await; self.db.clone(),
self.sender.clone(),
self.pending_iqs.clone(),
)
.await;
let set_status = recv.await; let set_status = recv.await;
match set_status { match set_status {
Ok(s) => match s { Ok(s) => match s {
@ -198,13 +202,13 @@ impl Luz {
let _ = self let _ = self
.sender .sender
.send(UpdateMessage::Error( .send(UpdateMessage::Error(
Error::Connection(e.into()), Error::Connecting(e.into()),
)) ))
.await; .await;
} }
}, },
Err(e) => { Err(e) => {
let _ = self.sender.send(UpdateMessage::Error(Error::Connection(ConnectionError::SendPresence(e.into())))).await; let _ = self.sender.send(UpdateMessage::Error(Error::Connecting(ConnectionError::SendPresence(WriteError::Actor(e.into()))))).await;
} }
} }
} }
@ -212,7 +216,7 @@ impl Luz {
let _ = self let _ = self
.sender .sender
.send(UpdateMessage::Error( .send(UpdateMessage::Error(
Error::Connection(e.into()), Error::Connecting(e.into()),
)) ))
.await; .await;
} }
@ -221,8 +225,12 @@ impl Luz {
Err(e) => { Err(e) => {
let _ = self let _ = self
.sender .sender
.send(UpdateMessage::Error(Error::Connection( .send(UpdateMessage::Error(Error::Connecting(
ConnectionError::RosterRetreival(e.into()), ConnectionError::RosterRetreival(
RosterError::Write(WriteError::Actor(
e.into(),
)),
),
))) )))
.await; .await;
} }
@ -230,7 +238,7 @@ impl Luz {
} }
Err(e) => { Err(e) => {
let _ = let _ =
self.sender.send(UpdateMessage::Error(Error::Connection( self.sender.send(UpdateMessage::Error(Error::Connecting(
ConnectionError::ConnectionFailed(e.into()), ConnectionError::ConnectionFailed(e.into()),
))); )));
} }
@ -286,7 +294,7 @@ impl Luz {
impl CommandMessage { impl CommandMessage {
pub async fn handle_offline( pub async fn handle_offline(
mut self, self,
jid: Arc<Mutex<JID>>, jid: Arc<Mutex<JID>>,
db: Db, db: Db,
update_sender: mpsc::Sender<UpdateMessage>, update_sender: mpsc::Sender<UpdateMessage>,
@ -301,7 +309,7 @@ impl CommandMessage {
let _ = sender.send(Ok(roster)); let _ = sender.send(Ok(roster));
} }
Err(e) => { Err(e) => {
let _ = sender.send(Err(RosterError(e.into()))); let _ = sender.send(Err(RosterError::Cache(e.into())));
} }
} }
} }
@ -331,45 +339,48 @@ impl CommandMessage {
} }
// TODO: offline queue to modify roster // TODO: offline queue to modify roster
CommandMessage::AddContact(jid, sender) => { CommandMessage::AddContact(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(RosterError::Write(WriteError::Disconnected)));
} }
CommandMessage::BuddyRequest(jid, sender) => { CommandMessage::BuddyRequest(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::SubscriptionRequest(jid, sender) => { CommandMessage::SubscriptionRequest(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::AcceptBuddyRequest(jid, sender) => { CommandMessage::AcceptBuddyRequest(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::AcceptSubscriptionRequest(jid, sender) => { CommandMessage::AcceptSubscriptionRequest(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::UnsubscribeFromContact(jid, sender) => { CommandMessage::UnsubscribeFromContact(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::UnsubscribeContact(jid, sender) => { CommandMessage::UnsubscribeContact(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::UnfriendContact(jid, sender) => { CommandMessage::UnfriendContact(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
} }
CommandMessage::DeleteContact(jid, sender) => { CommandMessage::DeleteContact(jid, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(RosterError::Write(WriteError::Disconnected)));
} }
CommandMessage::UpdateContact(jid, contact_update, sender) => { CommandMessage::UpdateContact(jid, contact_update, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(RosterError::Write(WriteError::Disconnected)));
} }
CommandMessage::SetStatus(online, sender) => { CommandMessage::SetStatus(online, sender) => {
let result = db let result = db
.upsert_cached_status(online) .upsert_cached_status(online)
.await .await
.map_err(|e| StatusError(e.into())); .map_err(|e| StatusError::Cache(e.into()));
sender.send(result); sender.send(result);
} }
// TODO: offline message queue // TODO: offline message queue
CommandMessage::SendMessage(jid, body, sender) => { CommandMessage::SendMessage(jid, body, sender) => {
sender.send(Err(Reason::Disconnected)); sender.send(Err(WriteError::Disconnected));
}
CommandMessage::SendPresence(jid, presence, sender) => {
sender.send(Err(WriteError::Disconnected));
} }
} }
} }
@ -382,7 +393,7 @@ impl CommandMessage {
client_jid: Arc<Mutex<JID>>, client_jid: Arc<Mutex<JID>>,
db: Db, db: Db,
update_sender: mpsc::Sender<UpdateMessage>, update_sender: mpsc::Sender<UpdateMessage>,
pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, Reason>>>>>, pending_iqs: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Stanza, ReadError>>>>>,
) { ) {
match self { match self {
CommandMessage::Connect => unreachable!(), CommandMessage::Connect => unreachable!(),
@ -424,11 +435,12 @@ impl CommandMessage {
Ok(Ok(())) => info!("roster request sent"), Ok(Ok(())) => info!("roster request sent"),
Ok(Err(e)) => { Ok(Err(e)) => {
// TODO: log errors if fail to send // TODO: log errors if fail to send
let _ = result_sender.send(Err(RosterError(e.into()))); let _ = result_sender.send(Err(RosterError::Write(e.into())));
return; return;
} }
Err(e) => { Err(e) => {
let _ = result_sender.send(Err(RosterError(e.into()))); let _ = result_sender
.send(Err(RosterError::Write(WriteError::Actor(e.into()))));
return; return;
} }
}; };
@ -448,23 +460,41 @@ impl CommandMessage {
items.into_iter().map(|item| item.into()).collect(); items.into_iter().map(|item| item.into()).collect();
if let Err(e) = db.replace_cached_roster(contacts.clone()).await { if let Err(e) = db.replace_cached_roster(contacts.clone()).await {
update_sender update_sender
.send(UpdateMessage::Error(Error::CacheUpdate(e.into()))) .send(UpdateMessage::Error(Error::Roster(RosterError::Cache(
e.into(),
))))
.await; .await;
}; };
result_sender.send(Ok(contacts)); result_sender.send(Ok(contacts));
return; return;
} }
ref s @ Stanza::Iq(Iq {
from: _,
ref id,
to: _,
r#type,
lang: _,
query: _,
ref errors,
}) if *id == iq_id && r#type == IqType::Error => {
if let Some(error) = errors.first() {
result_sender.send(Err(RosterError::StanzaError(error.clone())));
} else {
result_sender.send(Err(RosterError::UnexpectedStanza(s.clone())));
}
return;
}
s => { s => {
result_sender.send(Err(RosterError(Reason::UnexpectedStanza(s)))); result_sender.send(Err(RosterError::UnexpectedStanza(s)));
return; return;
} }
}, },
Ok(Err(e)) => { Ok(Err(e)) => {
result_sender.send(Err(RosterError(e.into()))); result_sender.send(Err(RosterError::Read(e)));
return; return;
} }
Err(e) => { Err(e) => {
result_sender.send(Err(RosterError(e.into()))); result_sender.send(Err(RosterError::Write(WriteError::Actor(e.into()))));
return; return;
} }
} }
@ -525,8 +555,8 @@ impl CommandMessage {
} }
// TODO: write_handle send helper function // TODO: write_handle send helper function
let result = write_handle.write(set_stanza).await; let result = write_handle.write(set_stanza).await;
if let Err(_) = result { if let Err(e) = result {
sender.send(result); sender.send(Err(RosterError::Write(e)));
return; return;
} }
let iq_result = recv.await; let iq_result = recv.await;
@ -545,24 +575,24 @@ impl CommandMessage {
sender.send(Ok(())); sender.send(Ok(()));
return; return;
} }
Stanza::Iq(Iq { ref s @ Stanza::Iq(Iq {
from: _, from: _,
id, ref id,
to: _, to: _,
r#type, r#type,
lang: _, lang: _,
query: _, query: _,
errors, ref errors,
}) if id == iq_id && r#type == IqType::Error => { }) if *id == iq_id && r#type == IqType::Error => {
if let Some(error) = errors.first() { if let Some(error) = errors.first() {
sender.send(Err(Reason::Stanza(Some(error.clone())))); sender.send(Err(RosterError::StanzaError(error.clone())));
} else { } else {
sender.send(Err(Reason::Stanza(None))); sender.send(Err(RosterError::UnexpectedStanza(s.clone())));
} }
return; return;
} }
s => { s => {
sender.send(Err(Reason::UnexpectedStanza(s))); sender.send(Err(RosterError::UnexpectedStanza(s)));
return; return;
} }
}, },
@ -572,7 +602,7 @@ impl CommandMessage {
} }
}, },
Err(e) => { Err(e) => {
sender.send(Err(e.into())); sender.send(Err(RosterError::Write(WriteError::Actor(e.into()))));
return; return;
} }
} }
@ -770,8 +800,8 @@ impl CommandMessage {
pending_iqs.lock().await.insert(iq_id.clone(), send); pending_iqs.lock().await.insert(iq_id.clone(), send);
} }
let result = write_handle.write(set_stanza).await; let result = write_handle.write(set_stanza).await;
if let Err(_) = result { if let Err(e) = result {
sender.send(result); sender.send(Err(RosterError::Write(e)));
return; return;
} }
let iq_result = recv.await; let iq_result = recv.await;
@ -790,24 +820,24 @@ impl CommandMessage {
sender.send(Ok(())); sender.send(Ok(()));
return; return;
} }
Stanza::Iq(Iq { ref s @ Stanza::Iq(Iq {
from: _, from: _,
id, ref id,
to: _, to: _,
r#type, r#type,
lang: _, lang: _,
query: _, query: _,
errors, ref errors,
}) if id == iq_id && r#type == IqType::Error => { }) if *id == iq_id && r#type == IqType::Error => {
if let Some(error) = errors.first() { if let Some(error) = errors.first() {
sender.send(Err(Reason::Stanza(Some(error.clone())))); sender.send(Err(RosterError::StanzaError(error.clone())));
} else { } else {
sender.send(Err(Reason::Stanza(None))); sender.send(Err(RosterError::UnexpectedStanza(s.clone())));
} }
return; return;
} }
s => { s => {
sender.send(Err(Reason::UnexpectedStanza(s))); sender.send(Err(RosterError::UnexpectedStanza(s)));
return; return;
} }
}, },
@ -817,7 +847,7 @@ impl CommandMessage {
} }
}, },
Err(e) => { Err(e) => {
sender.send(Err(e.into())); sender.send(Err(RosterError::Write(WriteError::Actor(e.into()))));
return; return;
} }
} }
@ -858,8 +888,8 @@ impl CommandMessage {
pending_iqs.lock().await.insert(iq_id.clone(), send); pending_iqs.lock().await.insert(iq_id.clone(), send);
} }
let result = write_handle.write(set_stanza).await; let result = write_handle.write(set_stanza).await;
if let Err(_) = result { if let Err(e) = result {
sender.send(result); sender.send(Err(RosterError::Write(e)));
return; return;
} }
let iq_result = recv.await; let iq_result = recv.await;
@ -878,24 +908,24 @@ impl CommandMessage {
sender.send(Ok(())); sender.send(Ok(()));
return; return;
} }
Stanza::Iq(Iq { ref s @ Stanza::Iq(Iq {
from: _, from: _,
id, ref id,
to: _, to: _,
r#type, r#type,
lang: _, lang: _,
query: _, query: _,
errors, ref errors,
}) if id == iq_id && r#type == IqType::Error => { }) if *id == iq_id && r#type == IqType::Error => {
if let Some(error) = errors.first() { if let Some(error) = errors.first() {
sender.send(Err(Reason::Stanza(Some(error.clone())))); sender.send(Err(RosterError::StanzaError(error.clone())));
} else { } else {
sender.send(Err(Reason::Stanza(None))); sender.send(Err(RosterError::UnexpectedStanza(s.clone())));
} }
return; return;
} }
s => { s => {
sender.send(Err(Reason::UnexpectedStanza(s))); sender.send(Err(RosterError::UnexpectedStanza(s)));
return; return;
} }
}, },
@ -905,7 +935,7 @@ impl CommandMessage {
} }
}, },
Err(e) => { Err(e) => {
sender.send(Err(e.into())); sender.send(Err(RosterError::Write(WriteError::Actor(e.into()))));
return; return;
} }
} }
@ -914,13 +944,16 @@ impl CommandMessage {
let result = db.upsert_cached_status(online.clone()).await; let result = db.upsert_cached_status(online.clone()).await;
if let Err(e) = result { if let Err(e) = result {
let _ = update_sender let _ = update_sender
.send(UpdateMessage::Error(Error::CacheUpdate(e.into()))) .send(UpdateMessage::Error(Error::SetStatus(StatusError::Cache(
e.into(),
))))
.await; .await;
} }
let result = write_handle let result = write_handle
.write(Stanza::Presence(online.into())) .write(Stanza::Presence(online.into()))
.await .await
.map_err(|e| StatusError(e)); .map_err(|e| StatusError::Write(e));
// .map_err(|e| StatusError::Write(e));
let _ = sender.send(result); let _ = sender.send(result);
} }
// TODO: offline message queue // TODO: offline message queue
@ -956,7 +989,9 @@ impl CommandMessage {
}; };
if let Err(e) = db.create_message(message, jid).await.map_err(|e| e.into()) if let Err(e) = db.create_message(message, jid).await.map_err(|e| e.into())
{ {
let _ = update_sender.send(UpdateMessage::Error(Error::CacheUpdate(e))); let _ = update_sender.send(UpdateMessage::Error(Error::MessageSend(
error::MessageSendError::MessageHistory(e),
)));
} }
let _ = sender.send(Ok(())); let _ = sender.send(Ok(()));
} }
@ -965,6 +1000,15 @@ impl CommandMessage {
} }
} }
} }
CommandMessage::SendPresence(jid, presence, sender) => {
let mut presence: stanza::client::presence::Presence = presence.into();
if let Some(jid) = jid {
presence.to = Some(jid);
};
let result = write_handle.write(Stanza::Presence(presence)).await;
// .map_err(|e| StatusError::Write(e));
let _ = sender.send(result);
}
} }
} }
} }
@ -999,11 +1043,12 @@ impl DerefMut for LuzHandle {
} }
impl LuzHandle { impl LuzHandle {
// TODO: database creation separate
pub async fn new( pub async fn new(
jid: JID, jid: JID,
password: String, password: String,
db: &str, db: &str,
) -> Result<(Self, mpsc::Receiver<UpdateMessage>), Reason> { ) -> Result<(Self, mpsc::Receiver<UpdateMessage>), DatabaseError> {
let db = SqlitePool::connect(db).await?; let db = SqlitePool::connect(db).await?;
let (command_sender, command_receiver) = mpsc::channel(20); let (command_sender, command_receiver) = mpsc::channel(20);
let (update_sender, update_receiver) = mpsc::channel(20); let (update_sender, update_receiver) = mpsc::channel(20);
@ -1030,8 +1075,59 @@ impl LuzHandle {
update_receiver, update_receiver,
)) ))
} }
pub async fn connect(&self) {
self.send(CommandMessage::Connect).await;
}
pub async fn disconnect(&self, offline: Offline) {
self.send(CommandMessage::Disconnect(offline)).await;
}
// pub async fn get_roster(&self) -> Result<Vec<Contact>, RosterError> {
// let (send, recv) = oneshot::channel();
// self.send(CommandMessage::GetRoster(send)).await.map_err(|e| RosterError::)?;
// Ok(recv.await?)
// }
// pub async fn get_chats(&self) -> Result<Vec<Chat>, Error> {}
// pub async fn get_chat(&self, jid: JID) -> Result<Chat, Error> {}
// pub async fn get_messages(&self, jid: JID) -> Result<Vec<Message>, Error> {}
// pub async fn delete_chat(&self, jid: JID) -> Result<(), Error> {}
// pub async fn delete_message(&self, id: Uuid) -> Result<(), Error> {}
// pub async fn get_user(&self, jid: JID) -> Result<User, Error> {}
// pub async fn add_contact(&self, jid: JID) -> Result<(), Error> {}
// pub async fn buddy_request(&self, jid: JID) -> Result<(), Error> {}
// pub async fn subscription_request(&self, jid: JID) -> Result<(), Error> {}
// pub async fn accept_buddy_request(&self, jid: JID) -> Result<(), Error> {}
// pub async fn accept_subscription_request(&self, jid: JID) -> Result<(), Error> {}
// pub async fn unsubscribe_from_contact(&self, jid: JID) -> Result<(), Error> {}
// pub async fn unsubscribe_contact(&self, jid: JID) -> Result<(), Error> {}
// pub async fn unfriend_contact(&self, jid: JID) -> Result<(), Error> {}
// pub async fn delete_contact(&self, jid: JID) -> Result<(), Error> {}
// pub async fn update_contact(&self, jid: JID, update: ContactUpdate) -> Result<(), Error> {}
// pub async fn set_status(&self, online: Online) -> Result<(), Error> {}
// pub async fn send_message(&self, jid: JID, body: Body) -> Result<(), Error> {}
} }
// TODO: generate methods for each with a macro
pub enum CommandMessage { pub enum CommandMessage {
// TODO: login invisible xep-0186 // TODO: login invisible xep-0186
/// connect to XMPP chat server. gets roster and publishes initial presence. /// connect to XMPP chat server. gets roster and publishes initial presence.
@ -1042,46 +1138,51 @@ pub enum CommandMessage {
GetRoster(oneshot::Sender<Result<Vec<Contact>, RosterError>>), GetRoster(oneshot::Sender<Result<Vec<Contact>, RosterError>>),
/// get all chats. chat will include 10 messages in their message Vec (enough for chat previews) /// get all chats. chat will include 10 messages in their message Vec (enough for chat previews)
// TODO: paging and filtering // TODO: paging and filtering
GetChats(oneshot::Sender<Result<Vec<Chat>, Reason>>), GetChats(oneshot::Sender<Result<Vec<Chat>, DatabaseError>>),
/// get a specific chat by jid /// get a specific chat by jid
GetChat(JID, oneshot::Sender<Result<Chat, Reason>>), GetChat(JID, oneshot::Sender<Result<Chat, DatabaseError>>),
/// get message history for chat (does appropriate mam things) /// get message history for chat (does appropriate mam things)
// TODO: paging and filtering // TODO: paging and filtering
GetMessages(JID, oneshot::Sender<Result<Vec<Message>, Reason>>), GetMessages(JID, oneshot::Sender<Result<Vec<Message>, DatabaseError>>),
/// delete a chat from your chat history, along with all the corresponding messages /// delete a chat from your chat history, along with all the corresponding messages
DeleteChat(JID, oneshot::Sender<Result<(), Reason>>), DeleteChat(JID, oneshot::Sender<Result<(), DatabaseError>>),
/// delete a message from your chat history /// delete a message from your chat history
DeleteMessage(Uuid, oneshot::Sender<Result<(), Reason>>), DeleteMessage(Uuid, oneshot::Sender<Result<(), DatabaseError>>),
/// get a user from your users database /// get a user from your users database
GetUser(JID, oneshot::Sender<Result<User, Reason>>), GetUser(JID, oneshot::Sender<Result<User, DatabaseError>>),
/// add a contact to your roster, with a status of none, no subscriptions. /// add a contact to your roster, with a status of none, no subscriptions.
// TODO: for all these, consider returning with oneshot::Sender<Result<(), Error>> AddContact(JID, oneshot::Sender<Result<(), RosterError>>),
AddContact(JID, oneshot::Sender<Result<(), Reason>>),
/// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster. /// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster.
BuddyRequest(JID, oneshot::Sender<Result<(), Reason>>), BuddyRequest(JID, oneshot::Sender<Result<(), WriteError>>),
/// send a subscription request, without pre-approval. if not already added to roster server adds to roster. /// send a subscription request, without pre-approval. if not already added to roster server adds to roster.
SubscriptionRequest(JID, oneshot::Sender<Result<(), Reason>>), SubscriptionRequest(JID, oneshot::Sender<Result<(), WriteError>>),
/// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster. /// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster.
AcceptBuddyRequest(JID, oneshot::Sender<Result<(), Reason>>), AcceptBuddyRequest(JID, oneshot::Sender<Result<(), WriteError>>),
/// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster. /// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster.
AcceptSubscriptionRequest(JID, oneshot::Sender<Result<(), Reason>>), AcceptSubscriptionRequest(JID, oneshot::Sender<Result<(), WriteError>>),
/// unsubscribe to a contact, but don't remove their subscription. /// unsubscribe to a contact, but don't remove their subscription.
UnsubscribeFromContact(JID, oneshot::Sender<Result<(), Reason>>), UnsubscribeFromContact(JID, oneshot::Sender<Result<(), WriteError>>),
/// stop a contact from being subscribed, but stay subscribed to the contact. /// stop a contact from being subscribed, but stay subscribed to the contact.
UnsubscribeContact(JID, oneshot::Sender<Result<(), Reason>>), UnsubscribeContact(JID, oneshot::Sender<Result<(), WriteError>>),
/// remove subscriptions to and from contact, but keep in roster. /// remove subscriptions to and from contact, but keep in roster.
UnfriendContact(JID, oneshot::Sender<Result<(), Reason>>), UnfriendContact(JID, oneshot::Sender<Result<(), WriteError>>),
/// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster. /// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster.
DeleteContact(JID, oneshot::Sender<Result<(), Reason>>), DeleteContact(JID, oneshot::Sender<Result<(), RosterError>>),
/// update contact. contact details will be overwritten with the contents of the contactupdate struct. /// update contact. contact details will be overwritten with the contents of the contactupdate struct.
UpdateContact(JID, ContactUpdate, oneshot::Sender<Result<(), Reason>>), UpdateContact(JID, ContactUpdate, oneshot::Sender<Result<(), RosterError>>),
/// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence. /// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence.
SetStatus(Online, oneshot::Sender<Result<(), StatusError>>), SetStatus(Online, oneshot::Sender<Result<(), StatusError>>),
/// send presence stanza
SendPresence(
Option<JID>,
Presence,
oneshot::Sender<Result<(), WriteError>>,
),
/// send a directed presence (usually to a non-contact). /// send a directed presence (usually to a non-contact).
// TODO: should probably make it so people can add non-contact auto presence sharing in the client (most likely through setting an internal setting) // TODO: should probably make it so people can add non-contact auto presence sharing in the client (most likely through setting an internal setting)
/// send a message to a jid (any kind of jid that can receive a message, e.g. a user or a /// send a message to a jid (any kind of jid that can receive a message, e.g. a user or a
/// chatroom). if disconnected, will be cached so when client connects, message will be sent. /// chatroom). if disconnected, will be cached so when client connects, message will be sent.
SendMessage(JID, Body, oneshot::Sender<Result<(), Reason>>), SendMessage(JID, Body, oneshot::Sender<Result<(), WriteError>>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -110,3 +110,12 @@ impl From<Offline> for stanza::client::presence::Presence {
} }
} }
} }
impl From<Presence> for stanza::client::presence::Presence {
fn from(value: Presence) -> Self {
match value {
Presence::Online(online) => online.into(),
Presence::Offline(offline) => offline.into(),
}
}
}