use std::collections::BTreeMap;
#[derive(Debug, Clone, Default)]
pub struct ConnectionHandler {
default_alias: Option<String>,
connections: BTreeMap<String, String>,
}
impl ConnectionHandler {
pub fn new() -> Self {
<Self as Default>::default()
}
pub fn with_default(alias: impl Into<String>, url: impl Into<String>) -> Self {
let alias = alias.into();
let mut handler = <Self as Default>::default();
handler.register(alias.clone(), url);
handler.default_alias = Some(alias);
handler
}
pub fn register(&mut self, alias: impl Into<String>, url: impl Into<String>) -> Option<String> {
let alias = alias.into();
let existing = self.connections.insert(alias.clone(), url.into());
if self.default_alias.is_none() {
self.default_alias = Some(alias);
}
existing
}
#[must_use]
pub fn contains(&self, alias: &str) -> bool {
self.connections.contains_key(alias)
}
#[must_use]
pub fn len(&self) -> usize {
self.connections.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.connections.is_empty()
}
#[must_use]
pub fn aliases(&self) -> Vec<&str> {
self.connections.keys().map(String::as_str).collect()
}
pub fn set_default(&mut self, alias: &str) -> Result<(), String> {
if self.contains(alias) {
self.default_alias = Some(alias.to_string());
Ok(())
} else {
Err(format!("unknown connection alias: {alias}"))
}
}
pub fn get(&self, alias: &str) -> Option<ConnectionProxy<'_>> {
self.connections
.get_key_value(alias)
.map(|(alias, url)| ConnectionProxy::new(alias.as_str(), url.as_str()))
}
pub fn default_connection(&self) -> Option<ConnectionProxy<'_>> {
self.default_alias
.as_deref()
.and_then(|alias| self.get(alias))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConnectionProxy<'a> {
alias: &'a str,
url: &'a str,
}
impl<'a> ConnectionProxy<'a> {
pub fn new(alias: &'a str, url: &'a str) -> Self {
Self { alias, url }
}
#[must_use]
pub fn alias(&self) -> &'a str {
self.alias
}
#[must_use]
pub fn url(&self) -> &'a str {
self.url
}
}
#[cfg(test)]
mod tests {
use super::{ConnectionHandler, ConnectionProxy};
#[test]
fn handler_uses_first_registration_as_default() {
let mut handler = ConnectionHandler::new();
handler.register("default", "sqlite::memory:");
handler.register("replica", "sqlite:///tmp/replica.db");
let default = handler
.default_connection()
.expect("default connection should exist");
assert_eq!(default.alias(), "default");
assert_eq!(default.url(), "sqlite::memory:");
}
#[test]
fn handler_can_switch_default_connection() {
let mut handler = ConnectionHandler::with_default("default", "sqlite::memory:");
handler.register("analytics", "sqlite:///tmp/analytics.db");
handler
.set_default("analytics")
.expect("registered alias should be accepted as the default");
assert_eq!(
handler
.default_connection()
.map(|connection| connection.alias()),
Some("analytics")
);
}
#[test]
fn handler_rejects_unknown_default_alias() {
let mut handler = ConnectionHandler::with_default("default", "sqlite::memory:");
let error = handler
.set_default("missing")
.expect_err("unknown aliases should be rejected");
assert_eq!(error, "unknown connection alias: missing");
}
#[test]
fn handler_lists_sorted_aliases_and_fetches_proxies() {
let mut handler = ConnectionHandler::new();
handler.register("replica", "sqlite:///tmp/replica.db");
handler.register("default", "sqlite::memory:");
assert_eq!(handler.aliases(), vec!["default", "replica"]);
assert_eq!(handler.len(), 2);
assert!(handler.contains("default"));
assert_eq!(
handler.get("replica").map(|proxy| proxy.url()),
Some("sqlite:///tmp/replica.db")
);
}
#[test]
fn proxy_exposes_alias_and_url() {
let proxy = ConnectionProxy::new("default", "sqlite::memory:");
assert_eq!(proxy.alias(), "default");
assert_eq!(proxy.url(), "sqlite::memory:");
}
}