rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
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:");
    }
}