//! This module defines the central message dispatcher to the client process. use log::{error, warn}; use solstice_proto::server::ServerResponse; use crate::context::Context; use crate::control::Request as ControlRequest; use crate::event::{Message, EventHandler}; use crate::handlers::*; use crate::message_handler::MessageHandler; use crate::room::{RoomEvent, RoomEventHandler}; /// Subsystem event handlers to which the `Dispatcher` dispatches events. pub trait DispatcherHandlers { type RoomEventHandler: EventHandler; fn room_event_handler(&mut self) -> &mut Self::RoomEventHandler; } /// Default event handlers implementing real behavior. #[derive(Default)] pub struct DefaultDispatcherHandlers { room_event_handler: RoomEventHandler, } impl DispatcherHandlers for DefaultDispatcherHandlers { type RoomEventHandler = RoomEventHandler; fn room_event_handler(&mut self) -> &mut Self::RoomEventHandler { &mut self.room_event_handler } } /// Parts that make up a `Dispatcher`. pub struct DispatcherParts { /// The global context against which messages are handled. pub context: Context, /// Subsystem event handlers. Injected for testability. pub handlers: H, } /// The `Dispatcher` is in charge of mapping messages to their handlers. pub struct Dispatcher { pub context: Context, pub handlers: H, } impl Dispatcher { /// Returns a new `Dispatcher` from the given parts. pub fn from_parts(parts: DispatcherParts) -> Self { Self { context: parts.context, handlers: parts.handlers, } } /// Breaks down a `Dispatcher` into its constituent parts. pub fn into_parts(self) -> DispatcherParts { DispatcherParts { context: self.context, handlers: self.handlers, } } fn dispatch_internal(&mut self, message: Message) -> anyhow::Result<()> { match message { Message::ServerResponse(ServerResponse::PeerAddressResponse( response, )) => { PeerAddressResponseHandler::default().run(&mut self.context, &response) } Message::ServerResponse(ServerResponse::PrivilegedUsersResponse( response, )) => PrivilegedUsersResponseHandler::default() .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 .handlers .room_event_handler() .handle(&mut self.context, RoomEvent::MessageResponse(response)), Message::ServerResponse(ServerResponse::RoomListResponse(response)) => { self .handlers .room_event_handler() .handle(&mut self.context, RoomEvent::ListResponse(response)) } Message::ServerResponse(response) => { warn!("Unhandled server response: {:?}", response); Ok(()) } Message::ControlRequest(ControlRequest::LoginStatusRequest) => { LoginStatusRequestHandler::default().run(&mut self.context, &()) } 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)) => { self .handlers .room_event_handler() .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) => { UserListRequestHandler::default().run(&mut self.context, &()) } Message::ControlRequest(request) => { warn!("Unhandled control request: {:?}", request); 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 ); } } } #[cfg(test)] mod tests { use std::net::Ipv4Addr; use solstice_proto::server; use crate::context::ContextBundle; use crate::control; use crate::dispatcher::Message; use crate::room::testing::FakeRoomEventHandler; use super::*; #[derive(Default)] struct FakeDispatcherHandlers { room_event_handler: FakeRoomEventHandler, } impl FakeDispatcherHandlers { // Defined here for better maintainability as new handlers are added. fn has_events(&self) -> bool { !self.room_event_handler.events.is_empty() } } impl DispatcherHandlers for FakeDispatcherHandlers { type RoomEventHandler = FakeRoomEventHandler; fn room_event_handler(&mut self) -> &mut Self::RoomEventHandler { &mut self.room_event_handler } } /// Dispatches `message` to fake handlers using a new `Dispatcher` and /// `Context`, then returns the handlers for inspection. fn dispatch(message: Message) -> FakeDispatcherHandlers { let bundle = ContextBundle::default(); let mut dispatcher = Dispatcher::from_parts(DispatcherParts { context: bundle.context, handlers: FakeDispatcherHandlers::default(), }); dispatcher.dispatch(message); dispatcher.into_parts().handlers } #[test] fn does_not_dispatch_unhandled_response() { let response = server::LoginResponse::LoginFail { reason: "bleep bloop".to_string(), }; let handlers = dispatch(Message::ServerResponse(response.into())); assert!(!handlers.has_events()); } #[test] fn dispatches_login_status_request() { let request = control::Request::LoginStatusRequest; let _handlers = dispatch(Message::ControlRequest(request)); // TODO: Check that event is dispatched to a new login event handler. } #[test] fn dispatches_peer_address_response() { let response = server::PeerAddressResponse { user_name: "shruti".to_string(), ip: Ipv4Addr::new(1, 2, 3, 4), port: 1234, }; let _handlers = dispatch(Message::ServerResponse(response.into())); // TODO: Check that event is dispatched to a new peer event handler. } #[test] fn dispatches_peer_connect_request() { let request = control::Request::PeerConnectRequest("shruti".to_string()); let _handlers = dispatch(Message::ControlRequest(request)); // TODO: Check that event is dispatched to a new peer event handler. } #[test] fn dispatches_privileged_users_response() { let response = server::PrivilegedUsersResponse { users: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], }; let _handlers = dispatch(Message::ServerResponse(response.into())); // TODO: Check that event is dispatched to a new user event handler. } #[test] fn dispatches_room_join_request() { let request = control::Request::RoomJoinRequest("bleep".to_string()); let handlers = dispatch(Message::ControlRequest(request.into())); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::JoinRequest("bleep".to_string())], ); } #[test] fn dispatches_room_join_response() { let response = server::RoomJoinResponse { room_name: "bleep".to_string(), owner: None, operators: Vec::new(), users: Vec::new(), }; let handlers = dispatch(Message::ServerResponse(response.clone().into())); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::JoinResponse(response)] ); } #[test] fn dispatches_room_list_request() { let handlers = dispatch(control::Request::RoomListRequest.into()); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::ListRequest] ); } #[test] fn dispatches_room_list_response() { let response = server::RoomListResponse { rooms: Vec::new(), owned_private_rooms: Vec::new(), other_private_rooms: Vec::new(), operated_private_room_names: Vec::new(), }; let handlers = dispatch(Message::ServerResponse(response.clone().into())); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::ListResponse(response)] ); } #[test] fn dispatches_room_message_request() { let request = control::RoomMessageRequest { room_name: "bleep".to_string(), message: "yo!".to_string(), }; let handlers = dispatch(Message::ControlRequest(request.clone().into())); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::MessageRequest(request)], ); } #[test] fn dispatches_room_message_response() { let response = server::RoomMessageResponse { room_name: "bleep".to_string(), user_name: "shruti".to_string(), message: "yo!".to_string(), }; let handlers = dispatch(Message::ServerResponse(response.clone().into())); assert_eq!( handlers.room_event_handler.events, &[RoomEvent::MessageResponse(response)] ); } #[test] fn dispatches_user_list_request() { let request = control::Request::UserListRequest; let _handlers = dispatch(request.into()); // TODO: Check that event is dispatched to a new user event handler. } }