Solstice client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

244 lines
6.5 KiB

//! This module defines the central message dispatcher to the client process.
use std::fmt::Debug;
use log::{error, warn};
use solstice_proto::server::ServerResponse;
use crate::context::Context;
use crate::control::Request as ControlRequest;
use crate::handlers::{
LoginStatusRequestHandler, PrivilegedUsersResponseHandler,
RoomJoinRequestHandler, RoomJoinResponseHandler, RoomListRequestHandler,
RoomListResponseHandler, RoomMessageRequestHandler,
RoomMessageResponseHandler,
};
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<ServerResponse> for Message {
fn from(response: ServerResponse) -> Self {
Self::ServerResponse(response)
}
}
impl From<ControlRequest> for Message {
fn from(request: ControlRequest) -> Self {
Self::ControlRequest(request)
}
}
/// 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<Self>, context: &mut Context);
}
/// Pairs together a message and its handler as chosen by the dispatcher.
/// Implements Job so as to erase the exact types involved.
struct DispatchedMessage<H, M> {
message: M,
handler: H,
}
impl<H> Job for DispatchedMessage<H, <H as MessageHandler>::Message>
where
H: MessageHandler + Send,
<H as MessageHandler>::Message: Debug + Send,
{
fn execute(self: Box<Self>, context: &mut 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<Box<dyn Job>> {
match message {
Message::ControlRequest(ControlRequest::LoginStatusRequest) => {
Some(Box::new(DispatchedMessage {
message: (),
handler: LoginStatusRequestHandler::default(),
}))
}
Message::ServerResponse(ServerResponse::PrivilegedUsersResponse(
response,
)) => Some(Box::new(DispatchedMessage {
message: response,
handler: PrivilegedUsersResponseHandler::default(),
})),
Message::ServerResponse(ServerResponse::RoomJoinResponse(response)) => {
Some(Box::new(DispatchedMessage {
message: response,
handler: RoomJoinResponseHandler::default(),
}))
}
Message::ServerResponse(ServerResponse::RoomMessageResponse(
response,
)) => Some(Box::new(DispatchedMessage {
message: response,
handler: RoomMessageResponseHandler::default(),
})),
Message::ServerResponse(ServerResponse::RoomListResponse(response)) => {
Some(Box::new(DispatchedMessage {
message: response,
handler: RoomListResponseHandler::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_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());
}
#[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());
}
}