//! This module defines the central message dispatcher to the client process. use std::fmt::Debug; use log::warn; use solstice_proto::server::ServerResponse; use crate::context::Context; use crate::control::Request as ControlRequest; use crate::executor::Job; use crate::handlers::{ RoomJoinRequestHandler, RoomJoinResponseHandler, RoomListRequestHandler, RoomMessageRequestHandler, SetPrivilegedUsersHandler, SetRoomListHandler, }; use crate::message_handler::MessageHandler; /// The type of messages dispatched by a dispatcher. #[derive(Debug, Eq, PartialEq)] pub enum Message { ControlRequest(ControlRequest), ServerResponse(ServerResponse), } impl From for Message { fn from(response: ServerResponse) -> Self { Self::ServerResponse(response) } } impl From for Message { fn from(request: ControlRequest) -> Self { Self::ControlRequest(request) } } /// Pairs together a message and its handler as chosen by the dispatcher. /// Implements Job so as to be scheduled on an executor. struct DispatchedMessage { message: M, handler: H, } impl Job for DispatchedMessage::Message> where H: MessageHandler + Send, ::Message: Debug + Send, { fn execute(self: Box, context: &Context) { if let Err(error) = self.handler.run(context, &self.message) { error!( "Error in handler {}: {:?}\nMessage: {:?}", H::name(), error, &self.message ); } } } /// The Dispatcher is in charge of mapping messages to their handlers. pub struct Dispatcher; impl Dispatcher { /// Returns a new dispatcher. pub fn new() -> Self { Self {} } /// Dispatches the given message by wrapping it with a handler. pub fn dispatch(&self, message: Message) -> Option> { match message { Message::ServerResponse(ServerResponse::PrivilegedUsersResponse( response, )) => Some(Box::new(DispatchedMessage { message: response, handler: SetPrivilegedUsersHandler::default(), })), Message::ServerResponse(ServerResponse::RoomJoinResponse(response)) => { Some(Box::new(DispatchedMessage { message: response, handler: RoomJoinResponseHandler::default(), })) } Message::ServerResponse(ServerResponse::RoomListResponse(response)) => { Some(Box::new(DispatchedMessage { message: response, handler: SetRoomListHandler::default(), })) } Message::ServerResponse(response) => { warn!("Unhandled server response: {:?}", response); None } Message::ControlRequest(ControlRequest::RoomListRequest) => { Some(Box::new(DispatchedMessage { message: (), handler: RoomListRequestHandler::default(), })) } Message::ControlRequest(ControlRequest::RoomMessageRequest(request)) => { Some(Box::new(DispatchedMessage { message: request, handler: RoomMessageRequestHandler::default(), })) } Message::ControlRequest(ControlRequest::RoomJoinRequest(room_name)) => { Some(Box::new(DispatchedMessage { message: room_name, handler: RoomJoinRequestHandler::default(), })) } Message::ControlRequest(request) => { warn!("Unhandled control request: {:?}", request); None } } } } #[cfg(test)] mod tests { use crate::control; use crate::dispatcher::Message; use solstice_proto::server; use super::*; #[test] fn does_not_dispatch_unhandled_response() { assert!(Dispatcher::new() .dispatch(Message::ServerResponse( server::LoginResponse::LoginFail { reason: "bleep bloop".to_string(), } .into() )) .is_none()); } #[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()); } #[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()); } #[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()); } #[test] fn dispatches_room_join_request() { assert!(Dispatcher::new() .dispatch(Message::ControlRequest( control::Request::RoomJoinRequest("bleep".to_string()).into() )) .is_some()); } #[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()); } #[test] fn dispatches_room_list_request() { assert!(Dispatcher::new() .dispatch(Message::ControlRequest(control::Request::RoomListRequest)) .is_some()); } }