reduce usage of unwraps (#70)

Reviewed-on: lavina/lavina#70
This commit is contained in:
Nikita Vilunov 2024-06-04 21:54:57 +00:00
parent d0420ec834
commit 4e8eb09184
18 changed files with 235 additions and 171 deletions

View File

@ -79,10 +79,10 @@ impl LavinaCore {
message: Str, message: Str,
created_at: chrono::DateTime<chrono::Utc>, created_at: chrono::DateTime<chrono::Utc>,
) -> Result<Option<()>> { ) -> Result<Option<()>> {
let Some(room_handle) = self.services.rooms.get_room(&self.services, &room_id).await else { let Some(room_handle) = self.services.rooms.get_room(&self.services, &room_id).await? else {
return Ok(None); return Ok(None);
}; };
room_handle.send_message(&self.services, &player_id, message, created_at).await; room_handle.send_message(&self.services, &player_id, message, created_at).await?;
Ok(Some(())) Ok(Some(()))
} }
} }

View File

@ -137,14 +137,15 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_dialog_id_new() { fn test_dialog_id_new() -> Result<()> {
let a = PlayerId::from("a").unwrap(); let a = PlayerId::from("a")?;
let b = PlayerId::from("b").unwrap(); let b = PlayerId::from("b")?;
let id1 = DialogId::new(a.clone(), b.clone()); let id1 = DialogId::new(a.clone(), b.clone());
let id2 = DialogId::new(a.clone(), b.clone()); let id2 = DialogId::new(a.clone(), b.clone());
// Dialog ids are invariant with respect to the order of participants // Dialog ids are invariant with respect to the order of participants
assert_eq!(id1, id2); assert_eq!(id1, id2);
assert_eq!(id1.as_inner(), (&a, &b)); assert_eq!(id1.as_inner(), (&a, &b));
assert_eq!(id2.as_inner(), (&a, &b)); assert_eq!(id2.as_inner(), (&a, &b));
Ok(())
} }
} }

View File

@ -8,7 +8,7 @@ use prometheus::Registry as MetricsRegistry;
use crate::clustering::broadcast::Broadcasting; use crate::clustering::broadcast::Broadcasting;
use crate::clustering::{ClusterConfig, ClusterMetadata, LavinaClient}; use crate::clustering::{ClusterConfig, ClusterMetadata, LavinaClient};
use crate::dialog::DialogRegistry; use crate::dialog::DialogRegistry;
use crate::player::{PlayerConnection, PlayerId, PlayerRegistry}; use crate::player::{PlayerConnectionResult, PlayerId, PlayerRegistry};
use crate::repo::Storage; use crate::repo::Storage;
use crate::room::{RoomHandle, RoomId, RoomInfo, RoomRegistry}; use crate::room::{RoomHandle, RoomId, RoomInfo, RoomRegistry};
@ -37,11 +37,11 @@ impl Deref for LavinaCore {
} }
impl LavinaCore { impl LavinaCore {
pub async fn connect_to_player(&self, player_id: &PlayerId) -> PlayerConnection { pub async fn connect_to_player(&self, player_id: &PlayerId) -> Result<PlayerConnectionResult> {
self.services.players.connect_to_player(&self, player_id).await self.services.players.connect_to_player(&self, player_id).await
} }
pub async fn get_room(&self, room_id: &RoomId) -> Option<RoomHandle> { pub async fn get_room(&self, room_id: &RoomId) -> Result<Option<RoomHandle>> {
self.services.rooms.get_room(&self.services, room_id).await self.services.rooms.get_room(&self.services, room_id).await
} }
@ -97,7 +97,7 @@ impl LavinaCore {
} }
pub async fn shutdown(self) -> Storage { pub async fn shutdown(self) -> Storage {
let _ = self.players.shutdown_all().await; self.players.shutdown_all().await;
let services = match Arc::try_unwrap(self.services) { let services = match Arc::try_unwrap(self.services) {
Ok(e) => e, Ok(e) => e,
Err(_) => { Err(_) => {

View File

@ -64,7 +64,7 @@ impl PlayerConnection {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = ClientCommand::SendMessage { room_id, body, promise }; let cmd = ClientCommand::SendMessage { room_id, body, promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
/// Handled in [Player::join_room]. /// Handled in [Player::join_room].
@ -73,7 +73,7 @@ impl PlayerConnection {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = ClientCommand::JoinRoom { room_id, promise }; let cmd = ClientCommand::JoinRoom { room_id, promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
/// Handled in [Player::change_room_topic]. /// Handled in [Player::change_room_topic].
@ -86,7 +86,7 @@ impl PlayerConnection {
promise, promise,
}; };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
/// Handled in [Player::leave_room]. /// Handled in [Player::leave_room].
@ -95,7 +95,7 @@ impl PlayerConnection {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = ClientCommand::LeaveRoom { room_id, promise }; let cmd = ClientCommand::LeaveRoom { room_id, promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
pub async fn terminate(self) { pub async fn terminate(self) {
@ -108,7 +108,7 @@ impl PlayerConnection {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = ClientCommand::GetRooms { promise }; let cmd = ClientCommand::GetRooms { promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
#[tracing::instrument(skip(self), name = "PlayerConnection::get_room_message_history")] #[tracing::instrument(skip(self), name = "PlayerConnection::get_room_message_history")]
@ -120,7 +120,7 @@ impl PlayerConnection {
limit, limit,
}; };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
/// Handler in [Player::send_dialog_message]. /// Handler in [Player::send_dialog_message].
@ -133,7 +133,7 @@ impl PlayerConnection {
promise, promise,
}; };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
/// Handler in [Player::check_user_existence]. /// Handler in [Player::check_user_existence].
@ -142,7 +142,7 @@ impl PlayerConnection {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = ClientCommand::GetInfo { recipient, promise }; let cmd = ClientCommand::GetInfo { recipient, promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) deferred.await?
} }
} }
@ -196,37 +196,37 @@ enum ActorCommand {
pub enum ClientCommand { pub enum ClientCommand {
JoinRoom { JoinRoom {
room_id: RoomId, room_id: RoomId,
promise: Promise<JoinResult>, promise: Promise<Result<JoinResult>>,
}, },
LeaveRoom { LeaveRoom {
room_id: RoomId, room_id: RoomId,
promise: Promise<()>, promise: Promise<Result<()>>,
}, },
SendMessage { SendMessage {
room_id: RoomId, room_id: RoomId,
body: Str, body: Str,
promise: Promise<SendMessageResult>, promise: Promise<Result<SendMessageResult>>,
}, },
ChangeTopic { ChangeTopic {
room_id: RoomId, room_id: RoomId,
new_topic: Str, new_topic: Str,
promise: Promise<()>, promise: Promise<Result<()>>,
}, },
GetRooms { GetRooms {
promise: Promise<Vec<RoomInfo>>, promise: Promise<Result<Vec<RoomInfo>>>,
}, },
SendDialogMessage { SendDialogMessage {
recipient: PlayerId, recipient: PlayerId,
body: Str, body: Str,
promise: Promise<()>, promise: Promise<Result<()>>,
}, },
GetInfo { GetInfo {
recipient: PlayerId, recipient: PlayerId,
promise: Promise<GetInfoResult>, promise: Promise<Result<GetInfoResult>>,
}, },
GetRoomHistory { GetRoomHistory {
room_id: RoomId, room_id: RoomId,
promise: Promise<Vec<StoredMessage>>, promise: Promise<Result<Vec<StoredMessage>>>,
limit: u32, limit: u32,
}, },
} }
@ -317,40 +317,45 @@ impl PlayerRegistry {
} }
#[tracing::instrument(skip(self, core), name = "PlayerRegistry::get_or_launch_player")] #[tracing::instrument(skip(self, core), name = "PlayerRegistry::get_or_launch_player")]
pub async fn get_or_launch_player(&self, core: &LavinaCore, id: &PlayerId) -> PlayerHandle { async fn get_or_launch_player(&self, core: &LavinaCore, id: &PlayerId) -> Result<Option<PlayerHandle>> {
let inner = self.0.read().await; let inner = self.0.read().await;
if let Some((handle, _)) = inner.players.get(id) { if let Some((handle, _)) = inner.players.get(id) {
handle.clone() Ok(Some(handle.clone()))
} else { } else {
drop(inner); drop(inner);
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
if let Some((handle, _)) = inner.players.get(id) { if let Some((handle, _)) = inner.players.get(id) {
handle.clone() Ok(Some(handle.clone()))
} else { } else {
let (handle, fiber) = Player::launch(id.clone(), core.clone()).await; let Some((handle, fiber)) = Player::launch(id.clone(), core.clone()).await? else {
return Ok(None);
};
inner.players.insert(id.clone(), (handle.clone(), fiber)); inner.players.insert(id.clone(), (handle.clone(), fiber));
inner.metric_active_players.inc(); inner.metric_active_players.inc();
handle Ok(Some(handle))
} }
} }
} }
#[tracing::instrument(skip(self, core), name = "PlayerRegistry::connect_to_player")] #[tracing::instrument(skip(self, core), name = "PlayerRegistry::connect_to_player")]
pub async fn connect_to_player(&self, core: &LavinaCore, id: &PlayerId) -> PlayerConnection { pub async fn connect_to_player(&self, core: &LavinaCore, id: &PlayerId) -> Result<PlayerConnectionResult> {
let player_handle = self.get_or_launch_player(core, id).await; let Some(player_handle) = self.get_or_launch_player(core, id).await? else {
player_handle.subscribe().await return Ok(PlayerConnectionResult::PlayerNotFound);
};
Ok(PlayerConnectionResult::Success(player_handle.subscribe().await))
} }
pub async fn shutdown_all(&self) -> Result<()> { pub async fn shutdown_all(&self) {
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
for (i, (k, j)) in inner.players.drain() { for (id, (handle, task)) in inner.players.drain() {
k.send(ActorCommand::Stop).await; handle.send(ActorCommand::Stop).await;
drop(k); drop(handle);
j.await?; match task.await {
log::debug!("Player stopped #{i:?}") Ok(_) => log::debug!("Player stopped #{id:?}"),
Err(e) => log::error!("Player #{id:?} failed to stop: {e}"),
}
} }
log::debug!("All players stopped"); log::debug!("All players stopped");
Ok(())
} }
} }
@ -378,11 +383,13 @@ struct Player {
services: LavinaCore, services: LavinaCore,
} }
impl Player { impl Player {
async fn launch(player_id: PlayerId, core: LavinaCore) -> (PlayerHandle, JoinHandle<Player>) { async fn launch(player_id: PlayerId, core: LavinaCore) -> Result<Option<(PlayerHandle, JoinHandle<Player>)>> {
let (tx, rx) = channel(32); let (tx, rx) = channel(32);
let handle = PlayerHandle { tx }; let handle = PlayerHandle { tx };
let handle_clone = handle.clone(); let handle_clone = handle.clone();
let storage_id = core.services.storage.retrieve_user_id_by_name(player_id.as_inner()).await.unwrap().unwrap(); let Some(storage_id) = core.services.storage.retrieve_user_id_by_name(player_id.as_inner()).await? else {
return Ok(None);
};
let player = Player { let player = Player {
player_id, player_id,
storage_id, storage_id,
@ -397,7 +404,7 @@ impl Player {
services: core, services: core,
}; };
let fiber = tokio::task::spawn(player.main_loop()); let fiber = tokio::task::spawn(player.main_loop());
(handle_clone, fiber) Ok(Some((handle_clone, fiber)))
} }
fn room_location(&self, room_id: &RoomId) -> Option<u32> { fn room_location(&self, room_id: &RoomId) -> Option<u32> {
@ -417,7 +424,7 @@ impl Player {
self.my_rooms.insert(room_id.clone(), RoomRef::Remote { node_id: remote_node }); self.my_rooms.insert(room_id.clone(), RoomRef::Remote { node_id: remote_node });
self.services.subscribe(self.player_id.clone(), room_id).await; self.services.subscribe(self.player_id.clone(), room_id).await;
} else { } else {
let room = self.services.rooms.get_room(&self.services, &room_id).await; let room = self.services.rooms.get_room(&self.services, &room_id).await.unwrap();
if let Some(room) = room { if let Some(room) = room {
room.subscribe(&self.player_id, self.handle.clone()).await; room.subscribe(&self.player_id, self.handle.clone()).await;
self.my_rooms.insert(room_id, RoomRef::Local(room)); self.my_rooms.insert(room_id, RoomRef::Local(room));
@ -496,8 +503,8 @@ impl Player {
let _ = promise.send(result); let _ = promise.send(result);
} }
ClientCommand::LeaveRoom { room_id, promise } => { ClientCommand::LeaveRoom { room_id, promise } => {
self.leave_room(connection_id, room_id).await; let result = self.leave_room(connection_id, room_id).await;
let _ = promise.send(()); let _ = promise.send(result);
} }
ClientCommand::SendMessage { room_id, body, promise } => { ClientCommand::SendMessage { room_id, body, promise } => {
let result = self.send_room_message(connection_id, room_id, body).await; let result = self.send_room_message(connection_id, room_id, body).await;
@ -508,20 +515,20 @@ impl Player {
new_topic, new_topic,
promise, promise,
} => { } => {
self.change_room_topic(connection_id, room_id, new_topic).await; let result = self.change_room_topic(connection_id, room_id, new_topic).await;
let _ = promise.send(()); let _ = promise.send(result);
} }
ClientCommand::GetRooms { promise } => { ClientCommand::GetRooms { promise } => {
let result = self.get_rooms().await; let result = self.get_rooms().await;
let _ = promise.send(result); let _ = promise.send(Ok(result));
} }
ClientCommand::SendDialogMessage { ClientCommand::SendDialogMessage {
recipient, recipient,
body, body,
promise, promise,
} => { } => {
self.send_dialog_message(connection_id, recipient, body).await; let result = self.send_dialog_message(connection_id, recipient, body).await;
let _ = promise.send(()); let _ = promise.send(result);
} }
ClientCommand::GetInfo { recipient, promise } => { ClientCommand::GetInfo { recipient, promise } => {
let result = self.check_user_existence(recipient).await; let result = self.check_user_existence(recipient).await;
@ -539,12 +546,12 @@ impl Player {
} }
#[tracing::instrument(skip(self), name = "Player::join_room")] #[tracing::instrument(skip(self), name = "Player::join_room")]
async fn join_room(&mut self, connection_id: ConnectionId, room_id: RoomId) -> JoinResult { async fn join_room(&mut self, connection_id: ConnectionId, room_id: RoomId) -> Result<JoinResult> {
if self.banned_from.contains(&room_id) { if self.banned_from.contains(&room_id) {
return JoinResult::Banned; return Ok(JoinResult::Banned);
} }
if self.my_rooms.contains_key(&room_id) { if self.my_rooms.contains_key(&room_id) {
return JoinResult::AlreadyJoined; return Ok(JoinResult::AlreadyJoined);
} }
if let Some(remote_node) = self.room_location(&room_id) { if let Some(remote_node) = self.room_location(&room_id) {
@ -552,16 +559,15 @@ impl Player {
room_id: room_id.as_inner(), room_id: room_id.as_inner(),
player_id: self.player_id.as_inner(), player_id: self.player_id.as_inner(),
}; };
self.services.client.join_room(remote_node, req).await.unwrap(); self.services.client.join_room(remote_node, req).await?;
let room_storage_id = let room_storage_id = self.services.storage.create_or_retrieve_room_id_by_name(room_id.as_inner()).await?;
self.services.storage.create_or_retrieve_room_id_by_name(room_id.as_inner()).await.unwrap(); self.services.storage.add_room_member(room_storage_id, self.storage_id).await?;
self.services.storage.add_room_member(room_storage_id, self.storage_id).await.unwrap();
self.my_rooms.insert(room_id.clone(), RoomRef::Remote { node_id: remote_node }); self.my_rooms.insert(room_id.clone(), RoomRef::Remote { node_id: remote_node });
JoinResult::Success(RoomInfo { Ok(JoinResult::Success(RoomInfo {
id: room_id, id: room_id,
topic: "unknown".into(), topic: "unknown".into(),
members: vec![], members: vec![],
}) }))
} else { } else {
let room = match self.services.rooms.get_or_create_room(&self.services, room_id.clone()).await { let room = match self.services.rooms.get_or_create_room(&self.services, room_id.clone()).await {
Ok(room) => room, Ok(room) => room,
@ -579,12 +585,12 @@ impl Player {
new_member_id: self.player_id.clone(), new_member_id: self.player_id.clone(),
}; };
self.broadcast_update(update, connection_id).await; self.broadcast_update(update, connection_id).await;
JoinResult::Success(room_info) Ok(JoinResult::Success(room_info))
} }
} }
#[tracing::instrument(skip(self), name = "Player::retrieve_room_history")] #[tracing::instrument(skip(self), name = "Player::retrieve_room_history")]
async fn get_room_history(&mut self, room_id: RoomId, limit: u32) -> Vec<StoredMessage> { async fn get_room_history(&mut self, room_id: RoomId, limit: u32) -> Result<Vec<StoredMessage>> {
let room = self.my_rooms.get(&room_id); let room = self.my_rooms.get(&room_id);
if let Some(room) = room { if let Some(room) = room {
match room { match room {
@ -601,7 +607,7 @@ impl Player {
} }
#[tracing::instrument(skip(self), name = "Player::leave_room")] #[tracing::instrument(skip(self), name = "Player::leave_room")]
async fn leave_room(&mut self, connection_id: ConnectionId, room_id: RoomId) { async fn leave_room(&mut self, connection_id: ConnectionId, room_id: RoomId) -> Result<()> {
let room = self.my_rooms.remove(&room_id); let room = self.my_rooms.remove(&room_id);
if let Some(room) = room { if let Some(room) = room {
match room { match room {
@ -614,10 +620,10 @@ impl Player {
room_id: room_id.as_inner(), room_id: room_id.as_inner(),
player_id: self.player_id.as_inner(), player_id: self.player_id.as_inner(),
}; };
self.services.client.leave_room(node_id, req).await.unwrap(); self.services.client.leave_room(node_id, req).await?;
let room_storage_id = let room_storage_id =
self.services.storage.create_or_retrieve_room_id_by_name(room_id.as_inner()).await.unwrap(); self.services.storage.create_or_retrieve_room_id_by_name(room_id.as_inner()).await?;
self.services.storage.remove_room_member(room_storage_id, self.storage_id).await.unwrap(); self.services.storage.remove_room_member(room_storage_id, self.storage_id).await?;
} }
} }
} }
@ -626,6 +632,7 @@ impl Player {
former_member_id: self.player_id.clone(), former_member_id: self.player_id.clone(),
}; };
self.broadcast_update(update, connection_id).await; self.broadcast_update(update, connection_id).await;
Ok(())
} }
#[tracing::instrument(skip(self, body), name = "Player::send_room_message")] #[tracing::instrument(skip(self, body), name = "Player::send_room_message")]
@ -634,15 +641,15 @@ impl Player {
connection_id: ConnectionId, connection_id: ConnectionId,
room_id: RoomId, room_id: RoomId,
body: Str, body: Str,
) -> SendMessageResult { ) -> Result<SendMessageResult> {
let Some(room) = self.my_rooms.get(&room_id) else { let Some(room) = self.my_rooms.get(&room_id) else {
tracing::info!("Room with ID {room_id:?} not found"); tracing::info!("Room with ID {room_id:?} not found");
return SendMessageResult::NoSuchRoom; return Ok(SendMessageResult::NoSuchRoom);
}; };
let created_at = Utc::now(); let created_at = Utc::now();
match room { match room {
RoomRef::Local(room) => { RoomRef::Local(room) => {
room.send_message(&self.services, &self.player_id, body.clone(), created_at.clone()).await; room.send_message(&self.services, &self.player_id, body.clone(), created_at.clone()).await?;
} }
RoomRef::Remote { node_id } => { RoomRef::Remote { node_id } => {
let req = SendMessageReq { let req = SendMessageReq {
@ -651,7 +658,7 @@ impl Player {
message: &*body, message: &*body,
created_at: &*created_at.to_rfc3339(), created_at: &*created_at.to_rfc3339(),
}; };
self.services.client.send_room_message(*node_id, req).await.unwrap(); self.services.client.send_room_message(*node_id, req).await?;
self.services self.services
.broadcast( .broadcast(
room_id.clone(), room_id.clone(),
@ -669,18 +676,19 @@ impl Player {
created_at, created_at,
}; };
self.broadcast_update(update, connection_id).await; self.broadcast_update(update, connection_id).await;
SendMessageResult::Success(created_at) Ok(SendMessageResult::Success(created_at))
} }
#[tracing::instrument(skip(self, new_topic), name = "Player::change_room_topic")] #[tracing::instrument(skip(self, new_topic), name = "Player::change_room_topic")]
async fn change_room_topic(&mut self, connection_id: ConnectionId, room_id: RoomId, new_topic: Str) { async fn change_room_topic(&mut self, connection_id: ConnectionId, room_id: RoomId, new_topic: Str) -> Result<()> {
let Some(room) = self.my_rooms.get(&room_id) else { let Some(room) = self.my_rooms.get(&room_id) else {
tracing::info!("Room with ID {room_id:?} not found"); tracing::info!("Room with ID {room_id:?} not found");
return; // TODO
return Ok(());
}; };
match room { match room {
RoomRef::Local(room) => { RoomRef::Local(room) => {
room.set_topic(&self.services, &self.player_id, new_topic.clone()).await; room.set_topic(&self.services, &self.player_id, new_topic.clone()).await?;
} }
RoomRef::Remote { node_id } => { RoomRef::Remote { node_id } => {
let req = SetRoomTopicReq { let req = SetRoomTopicReq {
@ -688,11 +696,12 @@ impl Player {
player_id: self.player_id.as_inner(), player_id: self.player_id.as_inner(),
topic: &*new_topic, topic: &*new_topic,
}; };
self.services.client.set_room_topic(*node_id, req).await.unwrap(); self.services.client.set_room_topic(*node_id, req).await?;
} }
} }
let update = Updates::RoomTopicChanged { room_id, new_topic }; let update = Updates::RoomTopicChanged { room_id, new_topic };
self.broadcast_update(update, connection_id).await; self.broadcast_update(update, connection_id).await;
Ok(())
} }
#[tracing::instrument(skip(self), name = "Player::get_rooms")] #[tracing::instrument(skip(self), name = "Player::get_rooms")]
@ -717,12 +726,9 @@ impl Player {
} }
#[tracing::instrument(skip(self, body), name = "Player::send_dialog_message")] #[tracing::instrument(skip(self, body), name = "Player::send_dialog_message")]
async fn send_dialog_message(&self, connection_id: ConnectionId, recipient: PlayerId, body: Str) { async fn send_dialog_message(&self, connection_id: ConnectionId, recipient: PlayerId, body: Str) -> Result<()> {
let created_at = Utc::now(); let created_at = Utc::now();
self.services self.services.send_dialog_message(self.player_id.clone(), recipient.clone(), body.clone(), &created_at).await?;
.send_dialog_message(self.player_id.clone(), recipient.clone(), body.clone(), &created_at)
.await
.unwrap();
let update = Updates::NewDialogMessage { let update = Updates::NewDialogMessage {
sender: self.player_id.clone(), sender: self.player_id.clone(),
receiver: recipient.clone(), receiver: recipient.clone(),
@ -730,14 +736,15 @@ impl Player {
created_at, created_at,
}; };
self.broadcast_update(update, connection_id).await; self.broadcast_update(update, connection_id).await;
Ok(())
} }
#[tracing::instrument(skip(self), name = "Player::check_user_existence")] #[tracing::instrument(skip(self), name = "Player::check_user_existence")]
async fn check_user_existence(&self, recipient: PlayerId) -> GetInfoResult { async fn check_user_existence(&self, recipient: PlayerId) -> Result<GetInfoResult> {
if self.services.storage.check_user_existence(recipient.as_inner().as_ref()).await.unwrap() { if self.services.storage.check_user_existence(recipient.as_inner().as_ref()).await? {
GetInfoResult::UserExists Ok(GetInfoResult::UserExists)
} else { } else {
GetInfoResult::UserDoesntExist Ok(GetInfoResult::UserDoesntExist)
} }
} }
@ -766,3 +773,8 @@ pub enum StopReason {
ServerShutdown, ServerShutdown,
InternalError, InternalError,
} }
pub enum PlayerConnectionResult {
Success(PlayerConnection),
PlayerNotFound,
}

View File

@ -79,9 +79,9 @@ impl RoomRegistry {
} }
#[tracing::instrument(skip(self, services), name = "RoomRegistry::get_room")] #[tracing::instrument(skip(self, services), name = "RoomRegistry::get_room")]
pub async fn get_room(&self, services: &Services, room_id: &RoomId) -> Option<RoomHandle> { pub async fn get_room(&self, services: &Services, room_id: &RoomId) -> Result<Option<RoomHandle>> {
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
inner.get_or_load_room(services, room_id).await.unwrap() inner.get_or_load_room(services, room_id).await
} }
#[tracing::instrument(skip(self), name = "RoomRegistry::get_all_rooms")] #[tracing::instrument(skip(self), name = "RoomRegistry::get_all_rooms")]
@ -161,8 +161,8 @@ impl RoomHandle {
lock.broadcast_update(update, player_id).await; lock.broadcast_update(update, player_id).await;
} }
pub async fn get_message_history(&self, services: &Services, limit: u32) -> Vec<StoredMessage> { pub async fn get_message_history(&self, services: &Services, limit: u32) -> Result<Vec<StoredMessage>> {
return services.storage.get_room_message_history(self.0.read().await.storage_id, limit).await.unwrap(); services.storage.get_room_message_history(self.0.read().await.storage_id, limit).await
} }
#[tracing::instrument(skip(self), name = "RoomHandle::unsubscribe")] #[tracing::instrument(skip(self), name = "RoomHandle::unsubscribe")]
@ -186,12 +186,19 @@ impl RoomHandle {
} }
#[tracing::instrument(skip(self, services, body, created_at), name = "RoomHandle::send_message")] #[tracing::instrument(skip(self, services, body, created_at), name = "RoomHandle::send_message")]
pub async fn send_message(&self, services: &Services, player_id: &PlayerId, body: Str, created_at: DateTime<Utc>) { pub async fn send_message(
&self,
services: &Services,
player_id: &PlayerId,
body: Str,
created_at: DateTime<Utc>,
) -> Result<()> {
let mut lock = self.0.write().await; let mut lock = self.0.write().await;
let res = lock.send_message(services, player_id, body, created_at).await; let res = lock.send_message(services, player_id, body, created_at).await;
if let Err(err) = res { if let Err(err) = &res {
log::warn!("Failed to send message: {err:?}"); tracing::error!("Failed to send message: {err:?}");
} }
res
} }
#[tracing::instrument(skip(self), name = "RoomHandle::get_room_info")] #[tracing::instrument(skip(self), name = "RoomHandle::get_room_info")]
@ -205,16 +212,17 @@ impl RoomHandle {
} }
#[tracing::instrument(skip(self, services, new_topic), name = "RoomHandle::set_topic")] #[tracing::instrument(skip(self, services, new_topic), name = "RoomHandle::set_topic")]
pub async fn set_topic(&self, services: &Services, changer_id: &PlayerId, new_topic: Str) { pub async fn set_topic(&self, services: &Services, changer_id: &PlayerId, new_topic: Str) -> Result<()> {
let mut lock = self.0.write().await; let mut lock = self.0.write().await;
let storage_id = lock.storage_id; let storage_id = lock.storage_id;
lock.topic = new_topic.clone(); lock.topic = new_topic.clone();
services.storage.set_room_topic(storage_id, &new_topic).await.unwrap(); services.storage.set_room_topic(storage_id, &new_topic).await?;
let update = Updates::RoomTopicChanged { let update = Updates::RoomTopicChanged {
room_id: lock.room_id.clone(), room_id: lock.room_id.clone(),
new_topic: new_topic.clone(), new_topic: new_topic.clone(),
}; };
lock.broadcast_update(update, changer_id).await; lock.broadcast_update(update, changer_id).await;
Ok(())
} }
} }

View File

@ -437,7 +437,14 @@ async fn handle_registered_socket<'a>(
log::info!("Handling registered user: {user:?}"); log::info!("Handling registered user: {user:?}");
let player_id = PlayerId::from(user.nickname.clone())?; let player_id = PlayerId::from(user.nickname.clone())?;
let mut connection = core.connect_to_player(&player_id).await; let mut connection = match core.connect_to_player(&player_id).await? {
PlayerConnectionResult::Success(connection) => connection,
PlayerConnectionResult::PlayerNotFound => {
tracing::error!("Authorized user unexpectedly not found in the database");
return Err(anyhow!("no such user"));
}
};
let text: Str = format!("Welcome to {} Server", &config.server_name).into(); let text: Str = format!("Welcome to {} Server", &config.server_name).into();
ServerMessage { ServerMessage {
@ -577,7 +584,7 @@ async fn handle_update(
match update { match update {
Updates::RoomJoined { new_member_id, room_id } => { Updates::RoomJoined { new_member_id, room_id } => {
if player_id == &new_member_id { if player_id == &new_member_id {
if let Some(room) = core.get_room(&room_id).await { if let Some(room) = core.get_room(&room_id).await? {
let room_info = room.get_room_info().await; let room_info = room.get_room_info().await;
let chan = Chan::Global(room_id.as_inner().clone()); let chan = Chan::Global(room_id.as_inner().clone());
produce_on_join_cmd_messages(&config, &user, &chan, &room_info, writer).await?; produce_on_join_cmd_messages(&config, &user, &chan, &room_info, writer).await?;
@ -784,7 +791,7 @@ async fn handle_incoming_message(
writer.flush().await?; writer.flush().await?;
} }
Recipient::Chan(Chan::Global(chan)) => { Recipient::Chan(Chan::Global(chan)) => {
let room = core.get_room(&RoomId::try_from(chan.clone())?).await; let room = core.get_room(&RoomId::try_from(chan.clone())?).await?;
if let Some(room) = room { if let Some(room) = room {
let room_info = room.get_room_info().await; let room_info = room.get_room_info().await;
for member in room_info.members { for member in room_info.members {
@ -870,7 +877,7 @@ async fn handle_incoming_message(
// TODO Respond with an error when a local channel is requested // TODO Respond with an error when a local channel is requested
Chan::Local(chan) => chan, Chan::Local(chan) => chan,
}; };
let room = core.get_room(&RoomId::try_from(channel_name.clone())?).await; let room = core.get_room(&RoomId::try_from(channel_name.clone())?).await?;
// TODO Handle non-existent room // TODO Handle non-existent room
if let Some(room) = room { if let Some(room) = room {
let room_id = &RoomId::try_from(channel_name.clone())?; let room_id = &RoomId::try_from(channel_name.clone())?;

View File

@ -9,7 +9,7 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use lavina_core::clustering::{ClusterConfig, ClusterMetadata}; use lavina_core::clustering::{ClusterConfig, ClusterMetadata};
use lavina_core::player::{JoinResult, PlayerId, SendMessageResult}; use lavina_core::player::{JoinResult, PlayerConnectionResult, PlayerId, SendMessageResult};
use lavina_core::repo::{Storage, StorageConfig}; use lavina_core::repo::{Storage, StorageConfig};
use lavina_core::room::RoomId; use lavina_core::room::RoomId;
use lavina_core::LavinaCore; use lavina_core::LavinaCore;
@ -109,7 +109,7 @@ impl TestServer {
async fn start() -> Result<TestServer> { async fn start() -> Result<TestServer> {
let _ = tracing_subscriber::fmt::try_init(); let _ = tracing_subscriber::fmt::try_init();
let config = ServerConfig { let config = ServerConfig {
listen_on: "127.0.0.1:0".parse().unwrap(), listen_on: "127.0.0.1:0".parse()?,
server_name: "testserver".into(), server_name: "testserver".into(),
}; };
let mut metrics = MetricsRegistry::new(); let mut metrics = MetricsRegistry::new();
@ -126,13 +126,13 @@ impl TestServer {
}, },
}; };
let core = LavinaCore::new(&mut metrics, cluster_config, storage).await?; let core = LavinaCore::new(&mut metrics, cluster_config, storage).await?;
let server = launch(config, core.clone(), metrics.clone()).await.unwrap(); let server = launch(config, core.clone(), metrics.clone()).await?;
Ok(TestServer { core, server }) Ok(TestServer { core, server })
} }
async fn reboot(self) -> Result<TestServer> { async fn reboot(self) -> Result<TestServer> {
let config = ServerConfig { let config = ServerConfig {
listen_on: "127.0.0.1:0".parse().unwrap(), listen_on: "127.0.0.1:0".parse()?,
server_name: "testserver".into(), server_name: "testserver".into(),
}; };
let cluster_config = ClusterConfig { let cluster_config = ClusterConfig {
@ -148,7 +148,7 @@ impl TestServer {
let storage = core.shutdown().await; let storage = core.shutdown().await;
let mut metrics = MetricsRegistry::new(); let mut metrics = MetricsRegistry::new();
let core = LavinaCore::new(&mut metrics, cluster_config, storage).await?; let core = LavinaCore::new(&mut metrics, cluster_config, storage).await?;
let server = launch(config, core.clone(), metrics.clone()).await.unwrap(); let server = launch(config, core.clone(), metrics.clone()).await?;
Ok(TestServer { core, server }) Ok(TestServer { core, server })
} }
@ -766,16 +766,18 @@ async fn server_time_capability() -> Result<()> {
s.expect(":testserver 366 tester #test :End of /NAMES list").await?; s.expect(":testserver 366 tester #test :End of /NAMES list").await?;
server.core.create_player(&PlayerId::from("some_guy")?).await?; server.core.create_player(&PlayerId::from("some_guy")?).await?;
let mut conn = server.core.connect_to_player(&PlayerId::from("some_guy").unwrap()).await; let mut conn = match server.core.connect_to_player(&PlayerId::from("some_guy")?).await? {
let res = conn.join_room(RoomId::try_from("test").unwrap()).await?; PlayerConnectionResult::Success(conn) => conn,
PlayerConnectionResult::PlayerNotFound => panic!("user was created, but not returned"),
};
let res = conn.join_room(RoomId::try_from("test")?).await?;
let JoinResult::Success(_) = res else { let JoinResult::Success(_) = res else {
panic!("Failed to join room"); panic!("Failed to join room");
}; };
s.expect(":some_guy JOIN #test").await?; s.expect(":some_guy JOIN #test").await?;
let SendMessageResult::Success(res) = conn.send_message(RoomId::try_from("test").unwrap(), "Hello".into()).await? let SendMessageResult::Success(res) = conn.send_message(RoomId::try_from("test")?, "Hello".into()).await? else {
else {
panic!("Failed to send message"); panic!("Failed to send message");
}; };
s.expect(&format!( s.expect(&format!(
@ -786,7 +788,7 @@ async fn server_time_capability() -> Result<()> {
// formatting check // formatting check
assert_eq!( assert_eq!(
DateTime::parse_from_rfc3339(&"2024-01-01T10:00:32.123Z").unwrap().to_rfc3339_opts(SecondsFormat::Millis, true), DateTime::parse_from_rfc3339(&"2024-01-01T10:00:32.123Z")?.to_rfc3339_opts(SecondsFormat::Millis, true),
"2024-01-01T10:00:32.123Z" "2024-01-01T10:00:32.123Z"
); );

View File

@ -167,7 +167,7 @@ impl<'a> XmppConnection<'a> {
resource: None, resource: None,
}) if server.0 == self.hostname_rooms => { }) if server.0 == self.hostname_rooms => {
let room_id = RoomId::try_from(room_name.0.clone()).unwrap(); let room_id = RoomId::try_from(room_name.0.clone()).unwrap();
let Some(_) = self.core.get_room(&room_id).await else { let Some(_) = self.core.get_room(&room_id).await.unwrap() else {
// TODO should return item-not-found // TODO should return item-not-found
// example: // example:
// <error type="cancel"> // <error type="cancel">

View File

@ -23,7 +23,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor; use tokio_rustls::TlsAcceptor;
use lavina_core::auth::Verdict; use lavina_core::auth::Verdict;
use lavina_core::player::{ConnectionMessage, PlayerConnection, PlayerId, StopReason}; use lavina_core::player::{ConnectionMessage, PlayerConnection, PlayerConnectionResult, PlayerId, StopReason};
use lavina_core::prelude::*; use lavina_core::prelude::*;
use lavina_core::terminator::Terminator; use lavina_core::terminator::Terminator;
use lavina_core::LavinaCore; use lavina_core::LavinaCore;
@ -202,7 +202,13 @@ async fn handle_socket(
authenticated = socket_auth(&mut xml_reader, &mut xml_writer, &mut reader_buf, &core, &hostname) => { authenticated = socket_auth(&mut xml_reader, &mut xml_writer, &mut reader_buf, &core, &hostname) => {
match authenticated { match authenticated {
Ok(authenticated) => { Ok(authenticated) => {
let mut connection = core.connect_to_player(&authenticated.player_id).await; let mut connection = match core.connect_to_player(&authenticated.player_id).await? {
PlayerConnectionResult::Success(connection) => connection,
PlayerConnectionResult::PlayerNotFound => {
tracing::error!("Authorized user unexpectedly not found in the database");
return Err(anyhow!("no such user"));
}
};
socket_final( socket_final(
&mut xml_reader, &mut xml_reader,
&mut xml_writer, &mut xml_writer,

View File

@ -220,7 +220,7 @@ impl<'a> XmppConnection<'a> {
mod tests { mod tests {
use anyhow::Result; use anyhow::Result;
use lavina_core::player::PlayerId; use lavina_core::player::{PlayerConnectionResult, PlayerId};
use proto_xmpp::bind::{Jid, Name, Resource, Server}; use proto_xmpp::bind::{Jid, Name, Resource, Server};
use proto_xmpp::client::Presence; use proto_xmpp::client::Presence;
use proto_xmpp::muc::{Affiliation, Role, XUser, XUserItem}; use proto_xmpp::muc::{Affiliation, Role, XUser, XUserItem};
@ -230,11 +230,11 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_muc_joining() -> Result<()> { async fn test_muc_joining() -> Result<()> {
let server = TestServer::start().await.unwrap(); let server = TestServer::start().await?;
server.core.create_player(&PlayerId::from("tester")?).await?; server.core.create_player(&PlayerId::from("tester")?).await?;
let player_id = PlayerId::from("tester").unwrap(); let player_id = PlayerId::from("tester")?;
let user = Authenticated { let user = Authenticated {
player_id, player_id,
xmpp_name: Name("tester".into()), xmpp_name: Name("tester".into()),
@ -242,10 +242,13 @@ mod tests {
xmpp_muc_name: Resource("tester".into()), xmpp_muc_name: Resource("tester".into()),
}; };
let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut player_conn = match server.core.connect_to_player(&user.player_id).await? {
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); PlayerConnectionResult::Success(conn) => conn,
PlayerConnectionResult::PlayerNotFound => panic!("user was created, but not returned"),
};
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await?;
let muc_presence = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); let muc_presence = conn.retrieve_muc_presence(&user.xmpp_name).await?;
let expected = Presence { let expected = Presence {
to: Some(Jid { to: Some(Jid {
name: Some(conn.user.xmpp_name.clone()), name: Some(conn.user.xmpp_name.clone()),
@ -274,7 +277,7 @@ mod tests {
}; };
assert_eq!(expected, muc_presence); assert_eq!(expected, muc_presence);
server.shutdown().await.unwrap(); server.shutdown().await;
Ok(()) Ok(())
} }
@ -282,11 +285,11 @@ mod tests {
// i.e. in-memory cache of memberships is cleaned, does not cause any issues. // i.e. in-memory cache of memberships is cleaned, does not cause any issues.
#[tokio::test] #[tokio::test]
async fn test_muc_joining_twice() -> Result<()> { async fn test_muc_joining_twice() -> Result<()> {
let server = TestServer::start().await.unwrap(); let server = TestServer::start().await?;
server.core.create_player(&PlayerId::from("tester")?).await?; server.core.create_player(&PlayerId::from("tester")?).await?;
let player_id = PlayerId::from("tester").unwrap(); let player_id = PlayerId::from("tester")?;
let user = Authenticated { let user = Authenticated {
player_id, player_id,
xmpp_name: Name("tester".into()), xmpp_name: Name("tester".into()),
@ -294,10 +297,13 @@ mod tests {
xmpp_muc_name: Resource("tester".into()), xmpp_muc_name: Resource("tester".into()),
}; };
let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut player_conn = match server.core.connect_to_player(&user.player_id).await? {
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); PlayerConnectionResult::Success(conn) => conn,
PlayerConnectionResult::PlayerNotFound => panic!("user was created, but not returned"),
};
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await?;
let response = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); let response = conn.retrieve_muc_presence(&user.xmpp_name).await?;
let expected = Presence { let expected = Presence {
to: Some(Jid { to: Some(Jid {
name: Some(conn.user.xmpp_name.clone()), name: Some(conn.user.xmpp_name.clone()),
@ -329,13 +335,16 @@ mod tests {
drop(conn); drop(conn);
let server = server.reboot().await.unwrap(); let server = server.reboot().await.unwrap();
let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut player_conn = match server.core.connect_to_player(&user.player_id).await? {
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); PlayerConnectionResult::Success(conn) => conn,
PlayerConnectionResult::PlayerNotFound => panic!("user was created, but not returned"),
};
let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await?;
let response = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); let response = conn.retrieve_muc_presence(&user.xmpp_name).await?;
assert_eq!(expected, response); assert_eq!(expected, response);
server.shutdown().await.unwrap(); server.shutdown().await;
Ok(()) Ok(())
} }
} }

View File

@ -48,10 +48,9 @@ impl TestServer {
Ok(TestServer { core }) Ok(TestServer { core })
} }
pub async fn shutdown(self) -> anyhow::Result<()> { pub async fn shutdown(self) {
let storage = self.core.shutdown().await; let storage = self.core.shutdown().await;
storage.close().await; storage.close().await;
Ok(())
} }
} }

View File

@ -149,9 +149,9 @@ impl TestServer {
async fn start() -> Result<TestServer> { async fn start() -> Result<TestServer> {
let _ = tracing_subscriber::fmt::try_init(); let _ = tracing_subscriber::fmt::try_init();
let config = ServerConfig { let config = ServerConfig {
listen_on: "127.0.0.1:0".parse().unwrap(), listen_on: "127.0.0.1:0".parse()?,
cert: "tests/certs/xmpp.pem".parse().unwrap(), cert: "tests/certs/xmpp.pem".parse()?,
key: "tests/certs/xmpp.key".parse().unwrap(), key: "tests/certs/xmpp.key".parse()?,
hostname: "localhost".into(), hostname: "localhost".into(),
}; };
let mut metrics = MetricsRegistry::new(); let mut metrics = MetricsRegistry::new();

View File

@ -48,7 +48,8 @@ impl Jid {
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
lazy_static! { lazy_static! {
static ref RE: Regex = Regex::new(r"^(([a-zA-Z0-9]+)@)?([^@/]+)(/([a-zA-Z0-9\-]+))?$").unwrap(); static ref RE: Regex =
Regex::new(r"^(([a-zA-Z0-9]+)@)?([^@/]+)(/([a-zA-Z0-9\-]+))?$").expect("this is a correct regex");
} }
let m = RE.captures(i).ok_or(anyhow!("Incorrectly format jid: {i}"))?; let m = RE.captures(i).ok_or(anyhow!("Incorrectly format jid: {i}"))?;
@ -152,70 +153,74 @@ mod tests {
use super::*; use super::*;
#[tokio::test] #[tokio::test]
async fn parse_message() { async fn parse_message() -> Result<()> {
let input = r#"<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>mobile</resource></bind>"#; let input = r#"<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>mobile</resource></bind>"#;
let mut reader = NsReader::from_reader(input.as_bytes()); let mut reader = NsReader::from_reader(input.as_bytes());
let mut buf = vec![]; let mut buf = vec![];
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap(); let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await?;
let mut parser = BindRequest::parse().consume(ns, &event); let mut parser = BindRequest::parse().consume(ns, &event);
let result = loop { let result = loop {
match parser { match parser {
Continuation::Final(res) => break res, Continuation::Final(res) => break res,
Continuation::Continue(next) => { Continuation::Continue(next) => {
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap(); let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await?;
parser = next.consume(ns, &event); parser = next.consume(ns, &event);
} }
} }
} }?;
.unwrap(); assert_eq!(result, BindRequest(Resource("mobile".into())));
assert_eq!(result, BindRequest(Resource("mobile".into())),) Ok(())
} }
#[test] #[test]
fn jid_parse_full() { fn jid_parse_full() -> Result<()> {
let input = "chelik@server.example/kek"; let input = "chelik@server.example/kek";
let expected = Jid { let expected = Jid {
name: Some(Name("chelik".into())), name: Some(Name("chelik".into())),
server: Server("server.example".into()), server: Server("server.example".into()),
resource: Some(Resource("kek".into())), resource: Some(Resource("kek".into())),
}; };
let res = Jid::from_string(input).unwrap(); let res = Jid::from_string(input)?;
assert_eq!(res, expected); assert_eq!(res, expected);
Ok(())
} }
#[test] #[test]
fn jid_parse_user() { fn jid_parse_user() -> Result<()> {
let input = "chelik@server.example"; let input = "chelik@server.example";
let expected = Jid { let expected = Jid {
name: Some(Name("chelik".into())), name: Some(Name("chelik".into())),
server: Server("server.example".into()), server: Server("server.example".into()),
resource: None, resource: None,
}; };
let res = Jid::from_string(input).unwrap(); let res = Jid::from_string(input)?;
assert_eq!(res, expected); assert_eq!(res, expected);
Ok(())
} }
#[test] #[test]
fn jid_parse_server() { fn jid_parse_server() -> Result<()> {
let input = "server.example"; let input = "server.example";
let expected = Jid { let expected = Jid {
name: None, name: None,
server: Server("server.example".into()), server: Server("server.example".into()),
resource: None, resource: None,
}; };
let res = Jid::from_string(input).unwrap(); let res = Jid::from_string(input)?;
assert_eq!(res, expected); assert_eq!(res, expected);
Ok(())
} }
#[test] #[test]
fn jid_parse_server_resource() { fn jid_parse_server_resource() -> Result<()> {
let input = "server.example/kek"; let input = "server.example/kek";
let expected = Jid { let expected = Jid {
name: None, name: None,
server: Server("server.example".into()), server: Server("server.example".into()),
resource: Some(Resource("kek".into())), resource: Some(Resource("kek".into())),
}; };
let res = Jid::from_string(input).unwrap(); let res = Jid::from_string(input)?;
assert_eq!(res, expected); assert_eq!(res, expected);
Ok(())
} }
} }

View File

@ -709,7 +709,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn parse_message() { async fn parse_message() {
let input = r#"<message id="aacea" type="chat" to="chelik@xmpp.ru"><subject>daa</subject><body>bbb</body><unknown-stuff></unknown-stuff></message>"#; let input = r#"<message id="aacea" type="chat" to="chelik@example.com"><subject>daa</subject><body>bbb</body><unknown-stuff></unknown-stuff></message>"#;
let result: Message<Ignore> = crate::xml::parse(input).unwrap(); let result: Message<Ignore> = crate::xml::parse(input).unwrap();
assert_eq!( assert_eq!(
result, result,
@ -718,7 +718,7 @@ mod tests {
id: Some("aacea".to_string()), id: Some("aacea".to_string()),
to: Some(Jid { to: Some(Jid {
name: Some(Name("chelik".into())), name: Some(Name("chelik".into())),
server: Server("xmpp.ru".into()), server: Server("example.com".into()),
resource: None resource: None
}), }),
r#type: MessageType::Chat, r#type: MessageType::Chat,
@ -732,7 +732,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn parse_message_empty_custom() { async fn parse_message_empty_custom() {
let input = r#"<message id="aacea" type="chat" to="chelik@xmpp.ru"><subject>daa</subject><body>bbb</body><unknown-stuff/></message>"#; let input = r#"<message id="aacea" type="chat" to="chelik@example.com"><subject>daa</subject><body>bbb</body><unknown-stuff/></message>"#;
let result: Message<Ignore> = crate::xml::parse(input).unwrap(); let result: Message<Ignore> = crate::xml::parse(input).unwrap();
assert_eq!( assert_eq!(
result, result,
@ -741,7 +741,7 @@ mod tests {
id: Some("aacea".to_string()), id: Some("aacea".to_string()),
to: Some(Jid { to: Some(Jid {
name: Some(Name("chelik".into())), name: Some(Name("chelik".into())),
server: Server("xmpp.ru".into()), server: Server("example.com".into()),
resource: None resource: None
}), }),
r#type: MessageType::Chat, r#type: MessageType::Chat,

View File

@ -49,11 +49,11 @@ mod tests {
use crate::client::{Iq, IqType}; use crate::client::{Iq, IqType};
#[test] #[test]
fn test_parse() { fn test_parse() -> Result<()> {
let input = let input =
r#"<iq from='juliet@example.com/balcony' id='bv1bs71f' type='get'><query xmlns='jabber:iq:roster'/></iq>"#; r#"<iq from='juliet@example.com/balcony' id='bv1bs71f' type='get'><query xmlns='jabber:iq:roster'/></iq>"#;
let result: Iq<RosterQuery> = parse(input).unwrap(); let result: Iq<RosterQuery> = parse(input)?;
assert_eq!( assert_eq!(
result, result,
Iq { Iq {
@ -67,7 +67,8 @@ mod tests {
r#type: IqType::Get, r#type: IqType::Get,
body: RosterQuery, body: RosterQuery,
} }
) );
Ok(())
} }
#[test] #[test]

View File

@ -169,30 +169,31 @@ mod test {
use super::*; use super::*;
#[tokio::test] #[tokio::test]
async fn client_stream_start_correct_parse() { async fn client_stream_start_correct_parse() -> Result<()> {
let input = r###"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="xmpp.ru" version="1.0" xmlns="jabber:client" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">"###; let input = r###"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="example.com" version="1.0" xmlns="jabber:client" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">"###;
let mut reader = NsReader::from_reader(input.as_bytes()); let mut reader = NsReader::from_reader(input.as_bytes());
let mut buf = vec![]; let mut buf = vec![];
let res = ClientStreamStart::parse(&mut reader, &mut buf).await.unwrap(); let res = ClientStreamStart::parse(&mut reader, &mut buf).await?;
assert_eq!( assert_eq!(
res, res,
ClientStreamStart { ClientStreamStart {
to: "xmpp.ru".to_owned(), to: "example.com".to_owned(),
lang: Some("en".to_owned()), lang: Some("en".to_owned()),
version: "1.0".to_owned() version: "1.0".to_owned()
} }
) );
Ok(())
} }
#[tokio::test] #[tokio::test]
async fn server_stream_start_write() { async fn server_stream_start_write() {
let input = ServerStreamStart { let input = ServerStreamStart {
from: "xmpp.ru".to_owned(), from: "example.com".to_owned(),
lang: "en".to_owned(), lang: "en".to_owned(),
id: "stream_id".to_owned(), id: "stream_id".to_owned(),
version: "1.0".to_owned(), version: "1.0".to_owned(),
}; };
let expected = r###"<stream:stream from="xmpp.ru" version="1.0" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" xml:lang="en" id="stream_id">"###; let expected = r###"<stream:stream from="example.com" version="1.0" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" xml:lang="en" id="stream_id">"###;
let mut output: Vec<u8> = vec![]; let mut output: Vec<u8> = vec![];
let mut writer = Writer::new(&mut output); let mut writer = Writer::new(&mut output);
input.write_xml(&mut writer).await.unwrap(); input.write_xml(&mut writer).await.unwrap();

View File

@ -37,42 +37,45 @@ impl AuthBody {
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_returning_auth_body() { fn test_returning_auth_body() -> Result<()> {
let orig = b"\x00login\x00pass"; let orig = b"\x00login\x00pass";
let encoded = general_purpose::STANDARD.encode(orig); let encoded = general_purpose::STANDARD.encode(orig);
let expected = AuthBody { let expected = AuthBody {
login: "login".to_string(), login: "login".to_string(),
password: "pass".to_string(), password: "pass".to_string(),
}; };
let result = AuthBody::from_str(encoded.as_bytes()).unwrap(); let result = AuthBody::from_str(encoded.as_bytes())?;
assert_eq!(expected, result); assert_eq!(expected, result);
Ok(())
} }
#[test] #[test]
fn test_ignoring_first_segment() { fn test_ignoring_first_segment() -> Result<()> {
let orig = b"ignored\x00login\x00pass"; let orig = b"ignored\x00login\x00pass";
let encoded = general_purpose::STANDARD.encode(orig); let encoded = general_purpose::STANDARD.encode(orig);
let expected = AuthBody { let expected = AuthBody {
login: "login".to_string(), login: "login".to_string(),
password: "pass".to_string(), password: "pass".to_string(),
}; };
let result = AuthBody::from_str(encoded.as_bytes()).unwrap(); let result = AuthBody::from_str(encoded.as_bytes())?;
assert_eq!(expected, result); assert_eq!(expected, result);
Ok(())
} }
#[test] #[test]
fn test_returning_auth_body_with_empty_strings() { fn test_returning_auth_body_with_empty_strings() -> Result<()> {
let orig = b"\x00\x00"; let orig = b"\x00\x00";
let encoded = general_purpose::STANDARD.encode(orig); let encoded = general_purpose::STANDARD.encode(orig);
let expected = AuthBody { let expected = AuthBody {
login: "".to_string(), login: "".to_string(),
password: "".to_string(), password: "".to_string(),
}; };
let result = AuthBody::from_str(encoded.as_bytes()).unwrap(); let result = AuthBody::from_str(encoded.as_bytes())?;
assert_eq!(expected, result); assert_eq!(expected, result);
Ok(())
} }
#[test] #[test]

View File

@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use lavina_core::auth::UpdatePasswordResult; use lavina_core::auth::UpdatePasswordResult;
use lavina_core::player::{PlayerId, SendMessageResult}; use lavina_core::player::{PlayerConnectionResult, PlayerId, SendMessageResult};
use lavina_core::prelude::*; use lavina_core::prelude::*;
use lavina_core::room::RoomId; use lavina_core::room::RoomId;
use lavina_core::terminator::Terminator; use lavina_core::terminator::Terminator;
@ -170,8 +170,13 @@ async fn endpoint_send_room_message(
let Ok(player_id) = PlayerId::from(req.author_id) else { let Ok(player_id) = PlayerId::from(req.author_id) else {
return Ok(player_not_found()); return Ok(player_not_found());
}; };
let mut player = core.connect_to_player(&player_id).await; let mut connection = match core.connect_to_player(&player_id).await? {
let res = player.send_message(room_id, req.message.into()).await?; PlayerConnectionResult::Success(connection) => connection,
PlayerConnectionResult::PlayerNotFound => {
return Ok(player_not_found());
}
};
let res = connection.send_message(room_id, req.message.into()).await?;
match res { match res {
SendMessageResult::NoSuchRoom => Ok(room_not_found()), SendMessageResult::NoSuchRoom => Ok(room_not_found()),
SendMessageResult::Success(_) => Ok(empty_204_request()), SendMessageResult::Success(_) => Ok(empty_204_request()),
@ -193,8 +198,13 @@ async fn endpoint_set_room_topic(
let Ok(player_id) = PlayerId::from(req.author_id) else { let Ok(player_id) = PlayerId::from(req.author_id) else {
return Ok(player_not_found()); return Ok(player_not_found());
}; };
let mut player = core.connect_to_player(&player_id).await; let mut connection = match core.connect_to_player(&player_id).await? {
player.change_topic(room_id, req.topic.into()).await?; PlayerConnectionResult::Success(connection) => connection,
PlayerConnectionResult::PlayerNotFound => {
return Ok(player_not_found());
}
};
connection.change_topic(room_id, req.topic.into()).await?;
Ok(empty_204_request()) Ok(empty_204_request())
} }