diff --git a/client/src/control/request.rs b/client/src/control/request.rs index f0799b8..7af27e8 100644 --- a/client/src/control/request.rs +++ b/client/src/control/request.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; /// This enumeration is the list of possible control requests made by the /// controller client to the client. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Request { /// The controller wants to connect to a peer. Contains the peer name. PeerConnectRequest(String), @@ -27,7 +27,7 @@ impl From for Request { } /// This structure contains the chat room message request from the controller. -#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct RoomMessageRequest { /// The name of the chat room in which to send the message. pub room_name: String, diff --git a/client/src/dispatcher.rs b/client/src/dispatcher.rs index f19bee5..1fb2d3a 100644 --- a/client/src/dispatcher.rs +++ b/client/src/dispatcher.rs @@ -9,6 +9,7 @@ use crate::context::Context; use crate::control::Request as ControlRequest; use crate::handlers::*; use crate::message_handler::MessageHandler; +use crate::room_controller::{HandleRoomEvent, RoomEvent, RoomEventHandler}; /// The type of messages dispatched by a dispatcher. #[derive(Debug, Eq, PartialEq)] @@ -17,6 +18,7 @@ pub enum Message { ServerResponse(ServerResponse), } +// TODO: Remove? impl From for Message { fn from(response: ServerResponse) -> Self { Self::ServerResponse(response) @@ -29,229 +31,351 @@ impl From for Message { } } -/// Represents a synchronous task that can be run against a context. -pub trait Job: Send { - /// Runs this job against the given context. - fn execute(self: Box, context: &mut Context); +pub struct DispatcherHandlersView<'a> { + room_event_handler: &'a mut dyn HandleRoomEvent, } -/// Pairs together a message and its handler as chosen by the dispatcher. -/// Implements Job so as to erase the exact types involved. -struct DispatchedMessage { - message: M, - handler: H, +#[derive(Default)] +pub struct DefaultDispatcherHandlers { + room_event_handler: RoomEventHandler, } -impl Job for DispatchedMessage::Message> -where - H: MessageHandler + Send, - ::Message: Debug + Send, -{ - fn execute(self: Box, context: &mut Context) { - if let Err(error) = self.handler.run(context, &self.message) { - error!( - "Error in handler {}: {:?}\nMessage: {:?}", - H::name(), - error, - &self.message - ); +impl DefaultDispatcherHandlers { + pub fn view<'a>(&'a mut self) -> DispatcherHandlersView<'a> { + DispatcherHandlersView { + room_event_handler: &mut self.room_event_handler, } } } +pub struct DispatcherParts<'a> { + pub context: Context, + pub handlers: DispatcherHandlersView<'a>, +} + /// The Dispatcher is in charge of mapping messages to their handlers. -pub struct Dispatcher; +pub struct Dispatcher<'a> { + pub context: Context, + pub handlers: DispatcherHandlersView<'a>, +} -impl Dispatcher { +impl<'a> Dispatcher<'a> { /// Returns a new dispatcher. - pub fn new() -> Self { - Self {} + pub fn from_parts(parts: DispatcherParts<'a>) -> Self { + Self { + context: parts.context, + handlers: parts.handlers, + } } - /// Dispatches the given message by wrapping it with a handler. - pub fn dispatch(&self, message: Message) -> Option> { + pub fn into_parts(self) -> DispatcherParts<'a> { + DispatcherParts { + context: self.context, + handlers: self.handlers, + } + } + + fn dispatch_internal(&mut self, message: Message) -> anyhow::Result<()> { match message { - Message::ControlRequest(ControlRequest::LoginStatusRequest) => { - Some(Box::new(DispatchedMessage { - message: (), - handler: LoginStatusRequestHandler::default(), - })) - } Message::ServerResponse(ServerResponse::PeerAddressResponse( response, - )) => Some(Box::new(DispatchedMessage { - message: response, - handler: PeerAddressResponseHandler::default(), - })), + )) => { + PeerAddressResponseHandler::default().run(&mut self.context, &response) + } Message::ServerResponse(ServerResponse::PrivilegedUsersResponse( response, - )) => Some(Box::new(DispatchedMessage { - message: response, - handler: PrivilegedUsersResponseHandler::default(), - })), + )) => PrivilegedUsersResponseHandler::default() + .run(&mut self.context, &response), Message::ServerResponse(ServerResponse::RoomJoinResponse(response)) => { - Some(Box::new(DispatchedMessage { - message: response, - handler: RoomJoinResponseHandler::default(), - })) + self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::JoinResponse(response)) } Message::ServerResponse(ServerResponse::RoomMessageResponse( response, - )) => Some(Box::new(DispatchedMessage { - message: response, - handler: RoomMessageResponseHandler::default(), - })), + )) => self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::MessageResponse(response)), Message::ServerResponse(ServerResponse::RoomListResponse(response)) => { - Some(Box::new(DispatchedMessage { - message: response, - handler: RoomListResponseHandler::default(), - })) + self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::ListResponse(response)) } Message::ServerResponse(response) => { warn!("Unhandled server response: {:?}", response); - None + Ok(()) } - Message::ControlRequest(ControlRequest::PeerConnectRequest(request)) => { - Some(Box::new(DispatchedMessage { - message: request, - handler: PeerConnectRequestHandler::default(), - })) + Message::ControlRequest(ControlRequest::LoginStatusRequest) => { + LoginStatusRequestHandler::default().run(&mut self.context, &()) } - Message::ControlRequest(ControlRequest::RoomListRequest) => { - Some(Box::new(DispatchedMessage { - message: (), - handler: RoomListRequestHandler::default(), - })) + Message::ControlRequest(ControlRequest::PeerConnectRequest(request)) => { + PeerConnectRequestHandler::default().run(&mut self.context, &request) } + Message::ControlRequest(ControlRequest::RoomListRequest) => self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::ListRequest), Message::ControlRequest(ControlRequest::RoomMessageRequest(request)) => { - Some(Box::new(DispatchedMessage { - message: request, - handler: RoomMessageRequestHandler::default(), - })) + self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::MessageRequest(request)) } Message::ControlRequest(ControlRequest::RoomJoinRequest(room_name)) => { - Some(Box::new(DispatchedMessage { - message: room_name, - handler: RoomJoinRequestHandler::default(), - })) + self + .handlers + .room_event_handler + .handle(&mut self.context, RoomEvent::JoinRequest(room_name)) } Message::ControlRequest(ControlRequest::UserListRequest) => { - Some(Box::new(DispatchedMessage { - message: (), - handler: UserListRequestHandler::default(), - })) + UserListRequestHandler::default().run(&mut self.context, &()) } Message::ControlRequest(request) => { warn!("Unhandled control request: {:?}", request); - None + Ok(()) } } } + + /// Dispatches the given message by wrapping it with a 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 + ); + } + } } #[cfg(test)] mod tests { - use crate::control; - use crate::dispatcher::Message; + use std::net::Ipv4Addr; use solstice_proto::server; + use crate::context::ContextBundle; + use crate::control; + use crate::dispatcher::Message; + use crate::room_controller::testing::FakeRoomEventHandler; + use super::*; + #[derive(Default)] + struct FakeDispatcherHandlers { + room_event_handler: FakeRoomEventHandler, + } + + impl FakeDispatcherHandlers { + fn view<'a>(&'a mut self) -> DispatcherHandlersView<'a> { + DispatcherHandlersView { + room_event_handler: &mut self.room_event_handler, + } + } + + fn has_events(&self) -> bool { + !self.room_event_handler.events.is_empty() + } + } + + /// Dispatches `message` to `handlers` using a new `Dispatcher` and `Context`. + fn dispatch(message: Message, handlers: &mut FakeDispatcherHandlers) { + let bundle = ContextBundle::default(); + + let mut dispatcher = Dispatcher::from_parts(DispatcherParts { + context: bundle.context, + handlers: handlers.view(), + }); + + dispatcher.dispatch(message); + } + #[test] fn does_not_dispatch_unhandled_response() { - assert!(Dispatcher::new() - .dispatch(Message::ServerResponse( - server::LoginResponse::LoginFail { - reason: "bleep bloop".to_string(), - } - .into() - )) - .is_none()); + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::LoginResponse::LoginFail { + reason: "bleep bloop".to_string(), + }; + + dispatch(Message::ServerResponse(response.into()), &mut handlers); + + assert!(!handlers.has_events()); + } + + #[test] + fn dispatches_login_status_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + let request = control::Request::LoginStatusRequest; + + dispatch(request.into(), &mut handlers); + + // TODO: Check that event is dispatched to a new login event handler. + } + + #[test] + fn dispatches_peer_address_response() { + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::PeerAddressResponse { + user_name: "shruti".to_string(), + ip: Ipv4Addr::new(1, 2, 3, 4), + port: 1234, + }; + + dispatch(Message::ServerResponse(response.into()), &mut handlers); + + // TODO: Check that event is dispatched to a new peer event handler. + } + + #[test] + fn dispatches_peer_connect_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + let request = control::Request::PeerConnectRequest("shruti".to_string()); + + dispatch(Message::ControlRequest(request), &mut handlers); + + // TODO: Check that event is dispatched to a new peer event handler. } #[test] fn dispatches_privileged_users_response() { - assert!(Dispatcher::new() - .dispatch(Message::ServerResponse( - server::PrivilegedUsersResponse { - users: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], - } - .into() - )) - .is_some()); + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::PrivilegedUsersResponse { + users: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], + }; + + dispatch(Message::ServerResponse(response.into()), &mut handlers); + + // TODO: Check that event is dispatched to a new user event handler. + } + + #[test] + fn dispatches_room_join_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + let request = control::Request::RoomJoinRequest("bleep".to_string()); + + dispatch(Message::ControlRequest(request.into()), &mut handlers); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::JoinRequest("bleep".to_string())], + ); } #[test] fn dispatches_room_join_response() { - assert!(Dispatcher::new() - .dispatch(Message::ServerResponse( - server::RoomJoinResponse { - room_name: "bleep".to_string(), - owner: None, - operators: Vec::new(), - users: Vec::new(), - } - .into() - )) - .is_some()); + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::RoomJoinResponse { + room_name: "bleep".to_string(), + owner: None, + operators: Vec::new(), + users: Vec::new(), + }; + + dispatch( + Message::ServerResponse(response.clone().into()), + &mut handlers, + ); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::JoinResponse(response)] + ); } #[test] - fn dispatches_room_message_response() { - assert!(Dispatcher::new() - .dispatch(Message::ServerResponse( - server::RoomMessageResponse { - room_name: "bleep".to_string(), - user_name: "shruti".to_string(), - message: "yo!".to_string(), - } - .into() - )) - .is_some()); + fn dispatches_room_list_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + dispatch(control::Request::RoomListRequest.into(), &mut handlers); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::ListRequest] + ); } #[test] fn dispatches_room_list_response() { - assert!(Dispatcher::new() - .dispatch(Message::ServerResponse( - server::RoomListResponse { - rooms: Vec::new(), - owned_private_rooms: Vec::new(), - other_private_rooms: Vec::new(), - operated_private_room_names: Vec::new(), - } - .into() - )) - .is_some()); + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::RoomListResponse { + rooms: Vec::new(), + owned_private_rooms: Vec::new(), + other_private_rooms: Vec::new(), + operated_private_room_names: Vec::new(), + }; + + dispatch( + Message::ServerResponse(response.clone().into()), + &mut handlers, + ); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::ListResponse(response)] + ); } #[test] - fn dispatches_room_join_request() { - assert!(Dispatcher::new() - .dispatch(Message::ControlRequest( - control::Request::RoomJoinRequest("bleep".to_string()).into() - )) - .is_some()); + fn dispatches_room_message_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + let request = control::RoomMessageRequest { + room_name: "bleep".to_string(), + message: "yo!".to_string(), + }; + + dispatch( + Message::ControlRequest(request.clone().into()), + &mut handlers, + ); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::MessageRequest(request)], + ); } #[test] - fn dispatches_room_message_request() { - assert!(Dispatcher::new() - .dispatch(Message::ControlRequest( - control::RoomMessageRequest { - room_name: "bleep".to_string(), - message: "yo!".to_string(), - } - .into() - )) - .is_some()); + fn dispatches_room_message_response() { + let mut handlers = FakeDispatcherHandlers::default(); + + let response = server::RoomMessageResponse { + room_name: "bleep".to_string(), + user_name: "shruti".to_string(), + message: "yo!".to_string(), + }; + + dispatch( + Message::ServerResponse(response.clone().into()), + &mut handlers, + ); + + assert_eq!( + handlers.room_event_handler.events, + &[RoomEvent::MessageResponse(response)] + ); } #[test] - fn dispatches_room_list_request() { - assert!(Dispatcher::new() - .dispatch(Message::ControlRequest(control::Request::RoomListRequest)) - .is_some()); + fn dispatches_user_list_request() { + let mut handlers = FakeDispatcherHandlers::default(); + + let request = control::Request::UserListRequest; + + dispatch(request.into(), &mut handlers); + + // TODO: Check that event is dispatched to a new user event handler. } } diff --git a/client/src/main.rs b/client/src/main.rs index 3aae563..a7c2a0f 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -18,13 +18,14 @@ mod login; mod message_handler; mod peer; mod room; +mod room_controller; #[cfg(test)] mod testing; mod user; use config::{Config, TomlConfig}; use context::{ContextBundle, ContextOptions}; -use dispatcher::Dispatcher; +use dispatcher::{DefaultDispatcherHandlers, Dispatcher, DispatcherParts}; // Size of various channels defined below. const CHANNEL_BUFFER_SIZE: usize = 100; @@ -101,18 +102,19 @@ async fn async_main(config: Config) -> anyhow::Result<()> { dispatcher_tx.clone(), )); - let dispatcher = Dispatcher::new(); let control_task = control_listener.run(dispatcher_tx, bundle.control_response_rx); let dispatch_task = tokio::task::spawn_blocking(move || { - let mut context = bundle.context; + let mut handlers = DefaultDispatcherHandlers::default(); + let mut dispatcher = Dispatcher::from_parts(DispatcherParts { + context: bundle.context, + handlers: handlers.view(), + }); while let Some(message) = dispatcher_rx.blocking_recv() { - if let Some(job) = dispatcher.dispatch(message) { - job.execute(&mut context); - } + dispatcher.dispatch(message); } - context + dispatcher.into_parts().context }); tokio::select! { diff --git a/client/src/room_controller.rs b/client/src/room_controller.rs new file mode 100644 index 0000000..c71280b --- /dev/null +++ b/client/src/room_controller.rs @@ -0,0 +1,91 @@ +use solstice_proto::server::{ + RoomJoinResponse, RoomListResponse, RoomMessageResponse, +}; + +use crate::context::Context; +use crate::control::RoomMessageRequest; +use crate::handlers::{ + RoomJoinRequestHandler, RoomJoinResponseHandler, RoomListRequestHandler, + RoomListResponseHandler, RoomMessageRequestHandler, + RoomMessageResponseHandler, +}; +use crate::message_handler::MessageHandler; + +#[derive(Debug, PartialEq, Eq)] +pub enum RoomEvent { + JoinRequest(String), + JoinResponse(RoomJoinResponse), + ListRequest, + ListResponse(RoomListResponse), + MessageRequest(RoomMessageRequest), + MessageResponse(RoomMessageResponse), +} + +pub trait HandleRoomEvent { + fn handle( + &mut self, + context: &mut Context, + event: RoomEvent, + ) -> anyhow::Result<()>; +} + +#[derive(Default)] +pub struct RoomEventHandler; + +impl HandleRoomEvent for RoomEventHandler { + fn handle( + &mut self, + context: &mut Context, + event: RoomEvent, + ) -> anyhow::Result<()> { + // TODO: Remove individual handlers, move code into RoomEventHandler. + match event { + RoomEvent::JoinRequest(room_name) => { + RoomJoinRequestHandler::default().run(context, &room_name) + } + RoomEvent::JoinResponse(response) => { + RoomJoinResponseHandler::default().run(context, &response) + } + RoomEvent::ListRequest => { + RoomListRequestHandler::default().run(context, &()) + } + RoomEvent::ListResponse(response) => { + RoomListResponseHandler::default().run(context, &response) + } + RoomEvent::MessageRequest(request) => { + RoomMessageRequestHandler::default().run(context, &request) + } + RoomEvent::MessageResponse(response) => { + RoomMessageResponseHandler::default().run(context, &response) + } + } + } +} + +#[cfg(test)] +pub mod testing { + use crate::context::Context; + + use super::{HandleRoomEvent, RoomEvent}; + + #[derive(Default)] + pub struct FakeRoomEventHandler { + pub events: Vec, + } + + impl HandleRoomEvent for FakeRoomEventHandler { + fn handle( + &mut self, + context: &mut Context, + event: RoomEvent, + ) -> anyhow::Result<()> { + self.events.push(event); + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + // TODO +}