use std::sync::RwLock;
use tracing::info;
#[derive(Debug, Clone)]
pub struct Server {
pub id: String,
pub addr: String,
}
impl Server {
#[must_use]
pub fn new(id: impl Into<String>, addr: impl Into<String>) -> Self {
Self {
id: id.into(),
addr: addr.into(),
}
}
}
pub struct ServerRegistry {
servers: RwLock<Vec<Server>>,
try_order: RwLock<Vec<String>>,
}
impl ServerRegistry {
#[must_use]
pub const fn new() -> Self {
Self {
servers: RwLock::new(Vec::new()),
try_order: RwLock::new(Vec::new()),
}
}
pub fn register(&self, server: &Server) -> bool {
let mut servers = self.servers.write().unwrap();
if servers.iter().any(|s| s.id == server.id) {
return false;
}
servers.push(server.clone());
drop(servers);
info!(id = server.id, addr = server.addr, "registered server");
true
}
pub fn deregister(&self, id: &str) -> Option<Server> {
let mut servers = self.servers.write().unwrap();
servers
.iter()
.position(|s| s.id == id)
.map(|pos| servers.remove(pos))
}
pub fn get(&self, id: &str) -> Option<Server> {
let servers = self.servers.read().unwrap();
servers.iter().find(|s| s.id == id).cloned()
}
pub fn list(&self) -> Vec<Server> {
self.servers.read().unwrap().clone()
}
pub fn set_try_order(&self, ids: Vec<String>) {
*self.try_order.write().unwrap() = ids;
}
pub fn try_order(&self) -> Vec<String> {
self.try_order.read().unwrap().clone()
}
pub fn select_initial(&self) -> Option<Server> {
let try_order = self.try_order.read().unwrap().clone();
let servers = self.servers.read().unwrap();
for id in &try_order {
if let Some(server) = servers.iter().find(|s| s.id == *id) {
return Some(server.clone());
}
}
servers.first().cloned()
}
}
impl Default for ServerRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_and_list() {
let reg = ServerRegistry::new();
let server = Server::new("lobby", "127.0.0.1:25565");
assert!(reg.register(&server));
assert!(!reg.register(&server));
let servers = reg.list();
assert_eq!(servers.len(), 1);
assert_eq!(servers[0].id, "lobby");
}
#[test]
fn test_deregister() {
let reg = ServerRegistry::new();
reg.register(&Server::new("lobby", "127.0.0.1:25565"));
let removed = reg.deregister("lobby");
assert!(removed.is_some());
assert_eq!(removed.unwrap().id, "lobby");
assert!(reg.list().is_empty());
assert!(reg.deregister("nonexistent").is_none());
}
#[test]
fn test_get() {
let reg = ServerRegistry::new();
reg.register(&Server::new("lobby", "127.0.0.1:25565"));
reg.register(&Server::new("survival", "127.0.0.1:25566"));
let server = reg.get("survival").unwrap();
assert_eq!(server.addr, "127.0.0.1:25566");
assert!(reg.get("nonexistent").is_none());
}
#[test]
fn test_try_order_selection() {
let reg = ServerRegistry::new();
reg.register(&Server::new("survival", "127.0.0.1:25566"));
reg.register(&Server::new("lobby", "127.0.0.1:25565"));
assert_eq!(reg.select_initial().unwrap().id, "survival");
reg.set_try_order(vec!["lobby".to_string(), "survival".to_string()]);
assert_eq!(reg.select_initial().unwrap().id, "lobby");
}
#[test]
fn test_try_order_skips_missing() {
let reg = ServerRegistry::new();
reg.register(&Server::new("survival", "127.0.0.1:25566"));
reg.set_try_order(vec!["lobby".to_string(), "survival".to_string()]);
assert_eq!(reg.select_initial().unwrap().id, "survival");
}
#[test]
fn test_select_initial_empty() {
let reg = ServerRegistry::new();
assert!(reg.select_initial().is_none());
}
#[test]
fn test_try_order_roundtrip() {
let reg = ServerRegistry::new();
let order = vec!["a".to_string(), "b".to_string(), "c".to_string()];
reg.set_try_order(order.clone());
assert_eq!(reg.try_order(), order);
}
}