use crate::server_generator::{ContinueRunning, ServerGenerator};
use crate::server_status::ServerStatus;
#[derive(Debug)]
pub(crate) struct SendError;
pub(crate) trait StatusSender: Send {
fn send_status(&self, status: ServerStatus) -> Result<(), SendError>;
fn send_exit(&self) -> Result<(), SendError>;
}
pub(crate) async fn run_server_loop<S: StatusSender>(sg: ServerGenerator, sender: S) {
loop {
let next_run = sg();
if sender.send_status(ServerStatus::Running).is_err() {
break;
}
match next_run.await {
ContinueRunning::Continue => {
if sender
.send_status(ServerStatus::Stopped(
"Server Exited, will start again".to_string(),
))
.is_err()
{
break;
}
continue;
}
ContinueRunning::Exit => {
let _ = sender.send_exit();
break;
}
ContinueRunning::ExitWithError(e) => {
let _ = sender.send_status(ServerStatus::Error(e));
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[derive(Debug, PartialEq, Eq)]
enum MockEvent {
Status(ServerStatus),
Exit,
}
#[derive(Clone, Default)]
struct MockSender {
events: Arc<Mutex<Vec<MockEvent>>>,
}
impl MockSender {
fn drain(&self) -> Vec<MockEvent> {
self.events.lock().unwrap().drain(..).collect()
}
}
impl StatusSender for MockSender {
fn send_status(&self, status: ServerStatus) -> Result<(), SendError> {
self.events.lock().unwrap().push(MockEvent::Status(status));
Ok(())
}
fn send_exit(&self) -> Result<(), SendError> {
self.events.lock().unwrap().push(MockEvent::Exit);
Ok(())
}
}
fn block_on<F: Future<Output = ()>>(f: F) {
tokio::runtime::Builder::new_current_thread()
.build()
.unwrap()
.block_on(f);
}
fn stopped_msg() -> ServerStatus {
ServerStatus::Stopped("Server Exited, will start again".to_string())
}
#[test]
fn immediate_exit_emits_running_then_exit() {
let sender = MockSender::default();
let sg: ServerGenerator = Arc::new(|| Box::pin(async { ContinueRunning::Exit }));
block_on(run_server_loop(sg, sender.clone()));
assert_eq!(
sender.drain(),
vec![MockEvent::Status(ServerStatus::Running), MockEvent::Exit]
);
}
#[test]
fn immediate_exit_with_error_emits_running_then_error_no_exit() {
let sender = MockSender::default();
let sg: ServerGenerator =
Arc::new(|| Box::pin(async { ContinueRunning::ExitWithError("boom".to_string()) }));
block_on(run_server_loop(sg, sender.clone()));
assert_eq!(
sender.drain(),
vec![
MockEvent::Status(ServerStatus::Running),
MockEvent::Status(ServerStatus::Error("boom".to_string())),
]
);
}
#[test]
fn continue_then_exit_runs_generator_twice() {
let sender = MockSender::default();
let count = Arc::new(Mutex::new(0u32));
let sg: ServerGenerator = {
let count = count.clone();
Arc::new(move || {
let count = count.clone();
Box::pin(async move {
let mut c = count.lock().unwrap();
*c += 1;
if *c == 1 {
ContinueRunning::Continue
} else {
ContinueRunning::Exit
}
})
})
};
block_on(run_server_loop(sg, sender.clone()));
assert_eq!(*count.lock().unwrap(), 2);
assert_eq!(
sender.drain(),
vec![
MockEvent::Status(ServerStatus::Running),
MockEvent::Status(stopped_msg()),
MockEvent::Status(ServerStatus::Running),
MockEvent::Exit,
]
);
}
#[test]
fn continue_then_exit_with_error_runs_generator_twice() {
let sender = MockSender::default();
let count = Arc::new(Mutex::new(0u32));
let sg: ServerGenerator = {
let count = count.clone();
Arc::new(move || {
let count = count.clone();
Box::pin(async move {
let mut c = count.lock().unwrap();
*c += 1;
if *c == 1 {
ContinueRunning::Continue
} else {
ContinueRunning::ExitWithError("boom".to_string())
}
})
})
};
block_on(run_server_loop(sg, sender.clone()));
assert_eq!(*count.lock().unwrap(), 2);
assert_eq!(
sender.drain(),
vec![
MockEvent::Status(ServerStatus::Running),
MockEvent::Status(stopped_msg()),
MockEvent::Status(ServerStatus::Running),
MockEvent::Status(ServerStatus::Error("boom".to_string())),
]
);
}
}