//! 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<Event=RoomEvent>;
|
|
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<H> {
|
|
/// 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<H> {
|
|
pub context: Context,
|
|
pub handlers: H,
|
|
}
|
|
|
|
impl<H: DispatcherHandlers> Dispatcher<H> {
|
|
/// Returns a new `Dispatcher` from the given parts.
|
|
pub fn from_parts(parts: DispatcherParts<H>) -> Self {
|
|
Self {
|
|
context: parts.context,
|
|
handlers: parts.handlers,
|
|
}
|
|
}
|
|
|
|
/// Breaks down a `Dispatcher` into its constituent parts.
|
|
pub fn into_parts(self) -> DispatcherParts<H> {
|
|
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.
|
|
}
|
|
}
|