6 Commits

8 changed files with 182 additions and 157 deletions
Unified View
  1. +18
    -18
      client/src/control/listener.rs
  2. +48
    -79
      client/src/dispatcher.rs
  3. +39
    -0
      client/src/event.rs
  4. +9
    -8
      client/src/main.rs
  5. +35
    -30
      client/src/room/event.rs
  6. +22
    -0
      client/src/user/event.rs
  7. +6
    -22
      client/src/user/map.rs
  8. +5
    -0
      client/src/user/mod.rs

+ 18
- 18
client/src/control/listener.rs View File

@ -13,11 +13,11 @@ use tokio_tungstenite::WebSocketStream;
use crate::control::request::*; use crate::control::request::*;
use crate::control::response::*; use crate::control::response::*;
use crate::dispatcher::Message;
use crate::event::Event;
async fn forward_incoming( async fn forward_incoming(
mut incoming: SplitStream<WebSocketStream<TcpStream>>, mut incoming: SplitStream<WebSocketStream<TcpStream>>,
message_tx: &mpsc::Sender<Message>,
event_tx: &mpsc::Sender<Event>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
while let Some(result) = incoming.next().await { while let Some(result) = incoming.next().await {
if let Err(WebSocketError::ConnectionClosed) = result { if let Err(WebSocketError::ConnectionClosed) = result {
@ -31,8 +31,8 @@ async fn forward_incoming(
let control_request: Request = serde_json::from_str(&text) let control_request: Request = serde_json::from_str(&text)
.with_context(|| format!("decoding JSON message {:?}", text))?; .with_context(|| format!("decoding JSON message {:?}", text))?;
message_tx
.send(Message::ControlRequest(control_request))
event_tx
.send(Event::ControlRequest(control_request))
.await .await
.context("dispatcher channel closed")?; .context("dispatcher channel closed")?;
} }
@ -60,7 +60,7 @@ async fn forward_outgoing(
async fn handle( async fn handle(
stream: TcpStream, stream: TcpStream,
remote_address: &SocketAddr, remote_address: &SocketAddr,
message_tx: &mpsc::Sender<Message>,
event_tx: &mpsc::Sender<Event>,
response_rx: &mut mpsc::Receiver<Response>, response_rx: &mut mpsc::Receiver<Response>,
) { ) {
let ws_stream = match tokio_tungstenite::accept_async(stream).await { let ws_stream = match tokio_tungstenite::accept_async(stream).await {
@ -79,7 +79,7 @@ async fn handle(
let (mut outgoing, incoming) = ws_stream.split(); let (mut outgoing, incoming) = ws_stream.split();
tokio::select! { tokio::select! {
result = forward_incoming(incoming, message_tx) => match result {
result = forward_incoming(incoming, event_tx) => match result {
Ok(()) => info!( Ok(()) => info!(
"Incoming WebSocket handler task for {} stopped", "Incoming WebSocket handler task for {} stopped",
remote_address, remote_address,
@ -132,11 +132,11 @@ impl Listener {
} }
/// Starts accepting control connections, one at a time. For each connection, /// Starts accepting control connections, one at a time. For each connection,
/// forwards incoming messages from the socket to `message_tx` and outgoing
/// forwards incoming messages from the socket to `event_tx` and outgoing
/// responses from `response_rx` to the socket. /// responses from `response_rx` to the socket.
pub async fn run( pub async fn run(
&mut self, &mut self,
message_tx: mpsc::Sender<Message>,
event_tx: mpsc::Sender<Event>,
mut response_rx: mpsc::Receiver<Response>, mut response_rx: mpsc::Receiver<Response>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
info!("Accepting control connections on {}", self.address()); info!("Accepting control connections on {}", self.address());
@ -163,7 +163,7 @@ impl Listener {
info!("Accepted control connection from {}", remote_address); info!("Accepted control connection from {}", remote_address);
handle(stream, &remote_address, &message_tx, &mut response_rx).await
handle(stream, &remote_address, &event_tx, &mut response_rx).await
} }
Ok(()) Ok(())
@ -185,19 +185,19 @@ mod tests {
use tokio_tungstenite::tungstenite::Message as WebSocketMessage; use tokio_tungstenite::tungstenite::Message as WebSocketMessage;
use crate::control::{Request, Response, RoomLeaveResponse}; use crate::control::{Request, Response, RoomLeaveResponse};
use crate::dispatcher::Message;
use crate::event::Event;
// A bound `Listener` packaged with the channels it needs to run. // A bound `Listener` packaged with the channels it needs to run.
// Convenient for tests. // Convenient for tests.
struct RunnableListener { struct RunnableListener {
inner: Listener, inner: Listener,
message_tx: mpsc::Sender<Message>,
event_tx: mpsc::Sender<Event>,
response_rx: mpsc::Receiver<Response>, response_rx: mpsc::Receiver<Response>,
} }
impl RunnableListener { impl RunnableListener {
async fn run(mut self) -> anyhow::Result<()> { async fn run(mut self) -> anyhow::Result<()> {
self.inner.run(self.message_tx, self.response_rx).await
self.inner.run(self.event_tx, self.response_rx).await
} }
} }
@ -206,7 +206,7 @@ mod tests {
pub listener: RunnableListener, pub listener: RunnableListener,
pub address: SocketAddr, pub address: SocketAddr,
pub websocket_address: String, pub websocket_address: String,
pub message_rx: mpsc::Receiver<Message>,
pub event_rx: mpsc::Receiver<Event>,
pub response_tx: mpsc::Sender<Response>, pub response_tx: mpsc::Sender<Response>,
} }
@ -217,12 +217,12 @@ mod tests {
let address = inner.address().clone(); let address = inner.address().clone();
let websocket_address = format!("ws://{}", address); let websocket_address = format!("ws://{}", address);
let (message_tx, message_rx) = mpsc::channel(100);
let (event_tx, event_rx) = mpsc::channel(100);
let (response_tx, response_rx) = mpsc::channel(100); let (response_tx, response_rx) = mpsc::channel(100);
let listener = RunnableListener { let listener = RunnableListener {
inner, inner,
message_tx,
event_tx,
response_rx, response_rx,
}; };
@ -230,7 +230,7 @@ mod tests {
listener, listener,
address, address,
websocket_address, websocket_address,
message_rx,
event_rx,
response_tx, response_tx,
}) })
} }
@ -335,8 +335,8 @@ mod tests {
.context("sending request")?; .context("sending request")?;
assert_eq!( assert_eq!(
bundle.message_rx.recv().await,
Some(Message::ControlRequest(Request::RoomListRequest))
bundle.event_rx.recv().await,
Some(Event::ControlRequest(Request::RoomListRequest))
); );
// Dropping this sender signals to the listener that it should stop. // Dropping this sender signals to the listener that it should stop.


+ 48
- 79
client/src/dispatcher.rs View File

@ -1,38 +1,18 @@
//! This module defines the central message dispatcher to the client process.
use std::fmt::Debug;
//! This module defines the central event dispatcher to the client process.
use log::{error, warn}; use log::{error, warn};
use solstice_proto::server::ServerResponse; use solstice_proto::server::ServerResponse;
use crate::context::Context; use crate::context::Context;
use crate::control::Request as ControlRequest; use crate::control::Request as ControlRequest;
use crate::event::{Event, EventHandler};
use crate::handlers::*; use crate::handlers::*;
use crate::message_handler::MessageHandler; use crate::message_handler::MessageHandler;
use crate::room::{HandleRoomEvent, RoomEvent, RoomEventHandler};
/// The type of messages dispatched by a dispatcher.
#[derive(Debug, Eq, PartialEq)]
pub enum Message {
ControlRequest(ControlRequest),
ServerResponse(ServerResponse),
}
impl From<ServerResponse> for Message {
fn from(response: ServerResponse) -> Self {
Self::ServerResponse(response)
}
}
impl From<ControlRequest> for Message {
fn from(request: ControlRequest) -> Self {
Self::ControlRequest(request)
}
}
use crate::room::{RoomEvent, RoomEventHandler};
/// Subsystem event handlers to which the `Dispatcher` dispatches events. /// Subsystem event handlers to which the `Dispatcher` dispatches events.
pub trait DispatcherHandlers { pub trait DispatcherHandlers {
type RoomEventHandler: HandleRoomEvent;
type RoomEventHandler: EventHandler<Event = RoomEvent>;
fn room_event_handler(&mut self) -> &mut Self::RoomEventHandler; fn room_event_handler(&mut self) -> &mut Self::RoomEventHandler;
} }
@ -51,14 +31,14 @@ impl DispatcherHandlers for DefaultDispatcherHandlers {
/// Parts that make up a `Dispatcher`. /// Parts that make up a `Dispatcher`.
pub struct DispatcherParts<H> { pub struct DispatcherParts<H> {
/// The global context against which messages are handled.
/// The global context against which events are handled.
pub context: Context, pub context: Context,
/// Subsystem event handlers. Injected for testability. /// Subsystem event handlers. Injected for testability.
pub handlers: H, pub handlers: H,
} }
/// The `Dispatcher` is in charge of mapping messages to their handlers.
/// The `Dispatcher` is in charge of mapping events to their handlers.
pub struct Dispatcher<H> { pub struct Dispatcher<H> {
pub context: Context, pub context: Context,
pub handlers: H, pub handlers: H,
@ -81,79 +61,68 @@ impl<H: DispatcherHandlers> Dispatcher<H> {
} }
} }
fn dispatch_internal(&mut self, message: Message) -> anyhow::Result<()> {
match message {
Message::ServerResponse(ServerResponse::PeerAddressResponse(
response,
)) => {
fn dispatch_internal(&mut self, event: Event) -> anyhow::Result<()> {
match event {
Event::ServerResponse(ServerResponse::PeerAddressResponse(response)) => {
PeerAddressResponseHandler::default().run(&mut self.context, &response) PeerAddressResponseHandler::default().run(&mut self.context, &response)
} }
Message::ServerResponse(ServerResponse::PrivilegedUsersResponse(
Event::ServerResponse(ServerResponse::PrivilegedUsersResponse(
response, response,
)) => PrivilegedUsersResponseHandler::default() )) => PrivilegedUsersResponseHandler::default()
.run(&mut self.context, &response), .run(&mut self.context, &response),
Message::ServerResponse(ServerResponse::RoomJoinResponse(response)) => {
self
.handlers
.room_event_handler()
.handle(&mut self.context, RoomEvent::JoinResponse(response))
}
Message::ServerResponse(ServerResponse::RoomMessageResponse(
response,
)) => self
Event::ServerResponse(ServerResponse::RoomJoinResponse(response)) => self
.handlers .handlers
.room_event_handler() .room_event_handler()
.handle(&mut self.context, RoomEvent::MessageResponse(response)),
Message::ServerResponse(ServerResponse::RoomListResponse(response)) => {
.handle(&mut self.context, RoomEvent::JoinResponse(response)),
Event::ServerResponse(ServerResponse::RoomMessageResponse(response)) => {
self self
.handlers .handlers
.room_event_handler() .room_event_handler()
.handle(&mut self.context, RoomEvent::ListResponse(response))
.handle(&mut self.context, RoomEvent::MessageResponse(response))
} }
Message::ServerResponse(response) => {
Event::ServerResponse(ServerResponse::RoomListResponse(response)) => self
.handlers
.room_event_handler()
.handle(&mut self.context, RoomEvent::ListResponse(response)),
Event::ServerResponse(response) => {
warn!("Unhandled server response: {:?}", response); warn!("Unhandled server response: {:?}", response);
Ok(()) Ok(())
} }
Message::ControlRequest(ControlRequest::LoginStatusRequest) => {
Event::ControlRequest(ControlRequest::LoginStatusRequest) => {
LoginStatusRequestHandler::default().run(&mut self.context, &()) LoginStatusRequestHandler::default().run(&mut self.context, &())
} }
Message::ControlRequest(ControlRequest::PeerConnectRequest(request)) => {
Event::ControlRequest(ControlRequest::PeerConnectRequest(request)) => {
PeerConnectRequestHandler::default().run(&mut self.context, &request) PeerConnectRequestHandler::default().run(&mut self.context, &request)
} }
Message::ControlRequest(ControlRequest::RoomListRequest) => self
Event::ControlRequest(ControlRequest::RoomListRequest) => self
.handlers .handlers
.room_event_handler() .room_event_handler()
.handle(&mut self.context, RoomEvent::ListRequest), .handle(&mut self.context, RoomEvent::ListRequest),
Message::ControlRequest(ControlRequest::RoomMessageRequest(request)) => {
Event::ControlRequest(ControlRequest::RoomMessageRequest(request)) => {
self self
.handlers .handlers
.room_event_handler() .room_event_handler()
.handle(&mut self.context, RoomEvent::MessageRequest(request)) .handle(&mut self.context, RoomEvent::MessageRequest(request))
} }
Message::ControlRequest(ControlRequest::RoomJoinRequest(room_name)) => {
self
.handlers
.room_event_handler()
.handle(&mut self.context, RoomEvent::JoinRequest(room_name))
}
Message::ControlRequest(ControlRequest::UserListRequest) => {
Event::ControlRequest(ControlRequest::RoomJoinRequest(room_name)) => self
.handlers
.room_event_handler()
.handle(&mut self.context, RoomEvent::JoinRequest(room_name)),
Event::ControlRequest(ControlRequest::UserListRequest) => {
UserListRequestHandler::default().run(&mut self.context, &()) UserListRequestHandler::default().run(&mut self.context, &())
} }
Message::ControlRequest(request) => {
Event::ControlRequest(request) => {
warn!("Unhandled control request: {:?}", request); warn!("Unhandled control request: {:?}", request);
Ok(()) Ok(())
} }
} }
} }
/// Dispatches the given message to the appropriate subsystem handler.
pub fn dispatch(&mut self, message: Message) {
let debug_message = format!("{:?}", &message);
if let Err(error) = self.dispatch_internal(message) {
error!(
"Error handling message: {:?}\nMessage: {}",
error, debug_message
);
/// Dispatches the given event to the appropriate subsystem handler.
pub fn dispatch(&mut self, event: Event) {
let debug_event = format!("{:?}", &event);
if let Err(error) = self.dispatch_internal(event) {
error!("Error handling event: {:?}\nEvent: {}", error, debug_event);
} }
} }
} }
@ -166,7 +135,7 @@ mod tests {
use crate::context::ContextBundle; use crate::context::ContextBundle;
use crate::control; use crate::control;
use crate::dispatcher::Message;
use crate::dispatcher::Event;
use crate::room::testing::FakeRoomEventHandler; use crate::room::testing::FakeRoomEventHandler;
use super::*; use super::*;
@ -190,9 +159,9 @@ mod tests {
} }
} }
/// Dispatches `message` to fake handlers using a new `Dispatcher` and
/// Dispatches `event` to fake handlers using a new `Dispatcher` and
/// `Context`, then returns the handlers for inspection. /// `Context`, then returns the handlers for inspection.
fn dispatch(message: Message) -> FakeDispatcherHandlers {
fn dispatch(event: Event) -> FakeDispatcherHandlers {
let bundle = ContextBundle::default(); let bundle = ContextBundle::default();
let mut dispatcher = Dispatcher::from_parts(DispatcherParts { let mut dispatcher = Dispatcher::from_parts(DispatcherParts {
@ -200,7 +169,7 @@ mod tests {
handlers: FakeDispatcherHandlers::default(), handlers: FakeDispatcherHandlers::default(),
}); });
dispatcher.dispatch(message);
dispatcher.dispatch(event);
dispatcher.into_parts().handlers dispatcher.into_parts().handlers
} }
@ -211,7 +180,7 @@ mod tests {
reason: "bleep bloop".to_string(), reason: "bleep bloop".to_string(),
}; };
let handlers = dispatch(Message::ServerResponse(response.into()));
let handlers = dispatch(Event::ServerResponse(response.into()));
assert!(!handlers.has_events()); assert!(!handlers.has_events());
} }
@ -220,7 +189,7 @@ mod tests {
fn dispatches_login_status_request() { fn dispatches_login_status_request() {
let request = control::Request::LoginStatusRequest; let request = control::Request::LoginStatusRequest;
let _handlers = dispatch(Message::ControlRequest(request));
let _handlers = dispatch(Event::ControlRequest(request));
// TODO: Check that event is dispatched to a new login event handler. // TODO: Check that event is dispatched to a new login event handler.
} }
@ -233,7 +202,7 @@ mod tests {
port: 1234, port: 1234,
}; };
let _handlers = dispatch(Message::ServerResponse(response.into()));
let _handlers = dispatch(Event::ServerResponse(response.into()));
// TODO: Check that event is dispatched to a new peer event handler. // TODO: Check that event is dispatched to a new peer event handler.
} }
@ -242,7 +211,7 @@ mod tests {
fn dispatches_peer_connect_request() { fn dispatches_peer_connect_request() {
let request = control::Request::PeerConnectRequest("shruti".to_string()); let request = control::Request::PeerConnectRequest("shruti".to_string());
let _handlers = dispatch(Message::ControlRequest(request));
let _handlers = dispatch(Event::ControlRequest(request));
// TODO: Check that event is dispatched to a new peer event handler. // TODO: Check that event is dispatched to a new peer event handler.
} }
@ -253,7 +222,7 @@ mod tests {
users: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], users: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()],
}; };
let _handlers = dispatch(Message::ServerResponse(response.into()));
let _handlers = dispatch(Event::ServerResponse(response.into()));
// TODO: Check that event is dispatched to a new user event handler. // TODO: Check that event is dispatched to a new user event handler.
} }
@ -262,7 +231,7 @@ mod tests {
fn dispatches_room_join_request() { fn dispatches_room_join_request() {
let request = control::Request::RoomJoinRequest("bleep".to_string()); let request = control::Request::RoomJoinRequest("bleep".to_string());
let handlers = dispatch(Message::ControlRequest(request.into()));
let handlers = dispatch(Event::ControlRequest(request.into()));
assert_eq!( assert_eq!(
handlers.room_event_handler.events, handlers.room_event_handler.events,
@ -279,7 +248,7 @@ mod tests {
users: Vec::new(), users: Vec::new(),
}; };
let handlers = dispatch(Message::ServerResponse(response.clone().into()));
let handlers = dispatch(Event::ServerResponse(response.clone().into()));
assert_eq!( assert_eq!(
handlers.room_event_handler.events, handlers.room_event_handler.events,
@ -306,7 +275,7 @@ mod tests {
operated_private_room_names: Vec::new(), operated_private_room_names: Vec::new(),
}; };
let handlers = dispatch(Message::ServerResponse(response.clone().into()));
let handlers = dispatch(Event::ServerResponse(response.clone().into()));
assert_eq!( assert_eq!(
handlers.room_event_handler.events, handlers.room_event_handler.events,
@ -321,7 +290,7 @@ mod tests {
message: "yo!".to_string(), message: "yo!".to_string(),
}; };
let handlers = dispatch(Message::ControlRequest(request.clone().into()));
let handlers = dispatch(Event::ControlRequest(request.clone().into()));
assert_eq!( assert_eq!(
handlers.room_event_handler.events, handlers.room_event_handler.events,
@ -337,7 +306,7 @@ mod tests {
message: "yo!".to_string(), message: "yo!".to_string(),
}; };
let handlers = dispatch(Message::ServerResponse(response.clone().into()));
let handlers = dispatch(Event::ServerResponse(response.clone().into()));
assert_eq!( assert_eq!(
handlers.room_event_handler.events, handlers.room_event_handler.events,


+ 39
- 0
client/src/event.rs View File

@ -0,0 +1,39 @@
//! Defines the root event model for the client application.
use solstice_proto::ServerResponse;
use crate::context::Context;
use crate::control;
/// The type of events affecting the client.
#[derive(Debug, Eq, PartialEq)]
pub enum Event {
ControlRequest(control::Request),
ServerResponse(ServerResponse),
}
impl From<ServerResponse> for Event {
fn from(response: ServerResponse) -> Self {
Self::ServerResponse(response)
}
}
impl From<control::Request> for Event {
fn from(request: control::Request) -> Self {
Self::ControlRequest(request)
}
}
/// An interface for event handlers.
///
/// Allows mocking of handlers in tests.
pub trait EventHandler {
type Event;
/// Handles the given `event` against the given global `context`.
fn handle(
&mut self,
context: &mut Context,
event: Self::Event,
) -> anyhow::Result<()>;
}

+ 9
- 8
client/src/main.rs View File

@ -13,6 +13,7 @@ mod config;
mod context; mod context;
mod control; mod control;
mod dispatcher; mod dispatcher;
mod event;
mod handlers; mod handlers;
mod login; mod login;
mod message_handler; mod message_handler;
@ -25,6 +26,7 @@ mod user;
use config::{Config, TomlConfig}; use config::{Config, TomlConfig};
use context::{ContextBundle, ContextOptions}; use context::{ContextBundle, ContextOptions};
use dispatcher::{DefaultDispatcherHandlers, Dispatcher, DispatcherParts}; use dispatcher::{DefaultDispatcherHandlers, Dispatcher, DispatcherParts};
use event::Event;
// Size of various channels defined below. // Size of various channels defined below.
const CHANNEL_BUFFER_SIZE: usize = 100; const CHANNEL_BUFFER_SIZE: usize = 100;
@ -43,7 +45,7 @@ fn old_main(config: Config) {
async fn run_client( async fn run_client(
config: Config, config: Config,
request_rx: mpsc::Receiver<ServerRequest>, request_rx: mpsc::Receiver<ServerRequest>,
dispatcher_tx: mpsc::Sender<dispatcher::Message>,
event_tx: mpsc::Sender<Event>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
info!("Connecting to server at {}.", config.server_address); info!("Connecting to server at {}.", config.server_address);
let stream = TcpStream::connect(&config.server_address) let stream = TcpStream::connect(&config.server_address)
@ -61,8 +63,8 @@ async fn run_client(
let (response_tx, mut response_rx) = mpsc::channel(CHANNEL_BUFFER_SIZE); let (response_tx, mut response_rx) = mpsc::channel(CHANNEL_BUFFER_SIZE);
let forwarder_task = tokio::spawn(async move { let forwarder_task = tokio::spawn(async move {
while let Some(response) = response_rx.recv().await { while let Some(response) = response_rx.recv().await {
dispatcher_tx
.send(dispatcher::Message::ServerResponse(response))
event_tx
.send(Event::ServerResponse(response))
.await .await
.expect("dispatcher channel closed"); .expect("dispatcher channel closed");
} }
@ -88,7 +90,7 @@ async fn async_main(config: Config) -> anyhow::Result<()> {
let bundle = ContextBundle::new(options); let bundle = ContextBundle::new(options);
let (dispatcher_tx, mut dispatcher_rx) = mpsc::channel(CHANNEL_BUFFER_SIZE);
let (event_tx, mut event_rx) = mpsc::channel(CHANNEL_BUFFER_SIZE);
let mut control_listener = let mut control_listener =
control::Listener::bind(&config.control_listen_address) control::Listener::bind(&config.control_listen_address)
@ -98,18 +100,17 @@ async fn async_main(config: Config) -> anyhow::Result<()> {
let client_task = tokio::spawn(run_client( let client_task = tokio::spawn(run_client(
config, config,
bundle.server_request_rx, bundle.server_request_rx,
dispatcher_tx.clone(),
event_tx.clone(),
)); ));
let control_task =
control_listener.run(dispatcher_tx, bundle.control_response_rx);
let control_task = control_listener.run(event_tx, bundle.control_response_rx);
let dispatch_task = tokio::task::spawn_blocking(move || { let dispatch_task = tokio::task::spawn_blocking(move || {
let mut dispatcher = Dispatcher::from_parts(DispatcherParts { let mut dispatcher = Dispatcher::from_parts(DispatcherParts {
context: bundle.context, context: bundle.context,
handlers: DefaultDispatcherHandlers::default(), handlers: DefaultDispatcherHandlers::default(),
}); });
while let Some(message) = dispatcher_rx.blocking_recv() {
while let Some(message) = event_rx.blocking_recv() {
dispatcher.dispatch(message); dispatcher.dispatch(message);
} }
dispatcher.into_parts().context dispatcher.into_parts().context


+ 35
- 30
client/src/room/event.rs View File

@ -7,6 +7,7 @@ use solstice_proto::ServerRequest;
use crate::context::Context; use crate::context::Context;
use crate::control; use crate::control;
use crate::event::EventHandler;
use crate::room::RoomMessage; use crate::room::RoomMessage;
/// An event affecting the chat room module. /// An event affecting the chat room module.
@ -20,19 +21,7 @@ pub enum RoomEvent {
MessageResponse(server::RoomMessageResponse), MessageResponse(server::RoomMessageResponse),
} }
/// An interface for room event handlers.
///
/// Allows mocking of handlers in tests.
pub trait HandleRoomEvent {
/// Handles the given `event` against the given global `context`.
fn handle(
&mut self,
context: &mut Context,
event: RoomEvent,
) -> anyhow::Result<()>;
}
/// The real, default implementation of `HandleRoomEvent`.
/// The real, default room event handler.
#[derive(Default)] #[derive(Default)]
pub struct RoomEventHandler; pub struct RoomEventHandler;
@ -168,7 +157,9 @@ fn handle_message_response(
.context("sending control response") .context("sending control response")
} }
impl HandleRoomEvent for RoomEventHandler {
impl EventHandler for RoomEventHandler {
type Event = RoomEvent;
fn handle( fn handle(
&mut self, &mut self,
context: &mut Context, context: &mut Context,
@ -199,19 +190,22 @@ impl HandleRoomEvent for RoomEventHandler {
#[cfg(test)] #[cfg(test)]
pub mod testing { pub mod testing {
use crate::context::Context; use crate::context::Context;
use crate::event::EventHandler;
use super::{HandleRoomEvent, RoomEvent};
use super::RoomEvent;
#[derive(Default)] #[derive(Default)]
pub struct FakeRoomEventHandler { pub struct FakeRoomEventHandler {
pub events: Vec<RoomEvent>, pub events: Vec<RoomEvent>,
} }
impl HandleRoomEvent for FakeRoomEventHandler {
impl EventHandler for FakeRoomEventHandler {
type Event = RoomEvent;
fn handle( fn handle(
&mut self, &mut self,
_context: &mut Context, _context: &mut Context,
event: RoomEvent,
event: Self::Event,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.events.push(event); self.events.push(event);
Ok(()) Ok(())
@ -224,16 +218,18 @@ mod tests {
use std::cmp::Ordering; use std::cmp::Ordering;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use solstice_proto::server;
use solstice_proto::{server, ServerRequest};
use crate::clock::SimulatedSystemClock; use crate::clock::SimulatedSystemClock;
use crate::context::{ContextBundle, ContextOptions}; use crate::context::{ContextBundle, ContextOptions};
use crate::room::{RoomMembership, RoomState, RoomVisibility};
use crate::control;
use crate::event::EventHandler;
use crate::room::{RoomMembership, RoomMessage, RoomState, RoomVisibility};
use super::*;
use super::{RoomEvent, RoomEventHandler};
#[test] #[test]
fn handle_room_join_request_failure() {
fn handle_join_request_failure() {
let mut bundle = ContextBundle::default(); let mut bundle = ContextBundle::default();
RoomEventHandler RoomEventHandler
@ -241,7 +237,7 @@ mod tests {
&mut bundle.context, &mut bundle.context,
RoomEvent::JoinRequest("bleep".to_string()), RoomEvent::JoinRequest("bleep".to_string()),
) )
.unwrap_err();
.expect_err("handling request");
// Room state has not changed. // Room state has not changed.
assert_eq!(bundle.context.state.rooms.get_room_list(), vec![]); assert_eq!(bundle.context.state.rooms.get_room_list(), vec![]);
@ -252,7 +248,7 @@ mod tests {
} }
#[test] #[test]
fn handle_room_join_request_already_joined() {
fn handle_join_request_already_joined() {
let mut room = RoomState::new(RoomVisibility::Public, 3); let mut room = RoomState::new(RoomVisibility::Public, 3);
room.membership = RoomMembership::Member; room.membership = RoomMembership::Member;
@ -268,7 +264,7 @@ mod tests {
&mut bundle.context, &mut bundle.context,
RoomEvent::JoinRequest("bleep".to_string()), RoomEvent::JoinRequest("bleep".to_string()),
) )
.unwrap_err();
.expect_err("handling request");
// Room state has not changed. // Room state has not changed.
assert_eq!( assert_eq!(
@ -288,7 +284,7 @@ mod tests {
} }
#[test] #[test]
fn handle_room_join_request_success() {
fn handle_join_request_success() {
let mut options = ContextOptions::default(); let mut options = ContextOptions::default();
options.initial_state.rooms.insert( options.initial_state.rooms.insert(
"bleep".to_string(), "bleep".to_string(),
@ -303,7 +299,10 @@ mod tests {
) )
.expect("handling request"); .expect("handling request");
let request = bundle.server_request_rx.blocking_recv().unwrap();
let request = bundle
.server_request_rx
.blocking_recv()
.expect("receiving request");
// Room state has been altered to reflect the request. // Room state has been altered to reflect the request.
assert_eq!( assert_eq!(
@ -377,9 +376,12 @@ mod tests {
RoomEventHandler RoomEventHandler
.handle(&mut bundle.context, RoomEvent::ListRequest) .handle(&mut bundle.context, RoomEvent::ListRequest)
.unwrap();
.expect("handling request");
let request = bundle.server_request_rx.blocking_recv().unwrap();
let request = bundle
.server_request_rx
.blocking_recv()
.expect("receving request");
assert_eq!(request, ServerRequest::RoomListRequest); assert_eq!(request, ServerRequest::RoomListRequest);
} }
@ -411,9 +413,12 @@ mod tests {
RoomEventHandler RoomEventHandler
.handle(&mut bundle.context, RoomEvent::ListRequest) .handle(&mut bundle.context, RoomEvent::ListRequest)
.unwrap();
.expect("handling request");
let response = bundle.control_response_rx.blocking_recv().unwrap();
let response = bundle
.control_response_rx
.blocking_recv()
.expect("receiving response");
let mut rooms = match response { let mut rooms = match response {
control::Response::RoomListResponse(control::RoomListResponse { control::Response::RoomListResponse(control::RoomListResponse {


+ 22
- 0
client/src/user/event.rs View File

@ -0,0 +1,22 @@
//! This module defines events affecting the user module and their handling.
use crate::context::Context;
use crate::event::EventHandler;
/// An event affecting the user module.
#[derive(Debug, PartialEq, Eq)]
pub enum UserEvent {}
pub struct UserEventHandler;
impl EventHandler for UserEventHandler {
type Event = UserEvent;
fn handle(
&mut self,
_context: &mut Context,
_event: Self::Event,
) -> anyhow::Result<()> {
Ok(())
}
}

client/src/user.rs → client/src/user/map.rs View File

@ -1,27 +1,13 @@
use std::collections; use std::collections;
use std::error;
use std::fmt;
use thiserror::Error;
use solstice_proto::{User, UserStatus}; use solstice_proto::{User, UserStatus};
/// The error returned when a user name was not found in the user map. /// The error returned when a user name was not found in the user map.
#[derive(Debug)]
pub struct UserNotFoundError {
/// The name of the user that wasn't found.
user_name: String,
}
impl fmt::Display for UserNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "user \"{}\" not found", self.user_name)
}
}
impl error::Error for UserNotFoundError {
fn description(&self) -> &str {
"user not found"
}
}
#[derive(Debug, Error)]
#[error("user {0:?} not found")]
pub struct UserNotFoundError(String);
/// Contains the mapping from user names to user data and provides a clean /// Contains the mapping from user names to user data and provides a clean
/// interface to interact with it. /// interface to interact with it.
@ -54,9 +40,7 @@ impl UserMap {
) -> Result<&mut User, UserNotFoundError> { ) -> Result<&mut User, UserNotFoundError> {
match self.map.get_mut(user_name) { match self.map.get_mut(user_name) {
Some(user) => Ok(user), Some(user) => Ok(user),
None => Err(UserNotFoundError {
user_name: user_name.to_string(),
}),
None => Err(UserNotFoundError(user_name.to_string())),
} }
} }

+ 5
- 0
client/src/user/mod.rs View File

@ -0,0 +1,5 @@
mod event;
mod map;
pub use event::{UserEvent, UserEventHandler};
pub use map::UserMap;

Loading…
Cancel
Save