kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Connection pool management for Kegani
//!
//! Wraps sqlx PgPool with additional utilities.

use sqlx::{PgPool, postgres::PgPoolOptions};
use std::time::Duration;

/// Database pool configuration
#[derive(Debug, Clone)]
pub struct DbPoolConfig {
    pub url: String,
    pub max_connections: u32,
    pub min_connections: u32,
    pub idle_timeout: Duration,
    pub max_lifetime: Duration,
}

impl Default for DbPoolConfig {
    fn default() -> Self {
        Self {
            url: std::env::var("DATABASE_URL")
                .unwrap_or_else(|_| "postgres://localhost:5432/kegani".to_string()),
            max_connections: 10,
            min_connections: 2,
            idle_timeout: Duration::from_secs(600),
            max_lifetime: Duration::from_secs(3600),
        }
    }
}

impl DbPoolConfig {
    /// Create from connection string
    pub fn new(url: &str) -> Self {
        Self {
            url: url.to_string(),
            ..Default::default()
        }
    }

    /// Set max connections
    pub fn max_connections(mut self, n: u32) -> Self {
        self.max_connections = n;
        self
    }

    /// Set min connections
    pub fn min_connections(mut self, n: u32) -> Self {
        self.min_connections = n;
        self
    }

    /// Set idle timeout
    pub fn idle_timeout(mut self, d: Duration) -> Self {
        self.idle_timeout = d;
        self
    }
}

/// Database pool wrapper
#[derive(Clone)]
pub struct DbPool {
    inner: PgPool,
}

impl DbPool {
    /// Create a new pool from config
    pub async fn new(config: DbPoolConfig) -> Result<Self, sqlx::Error> {
        let pool = PgPoolOptions::new()
            .max_connections(config.max_connections)
            .min_connections(config.min_connections)
            .acquire_timeout(Duration::from_secs(5))
            .idle_timeout(config.idle_timeout)
            .max_lifetime(config.max_lifetime)
            .connect(&config.url)
            .await?;

        Ok(Self { inner: pool })
    }

    /// Create from connection string
    pub async fn connect(url: &str) -> Result<Self, sqlx::Error> {
        Self::new(DbPoolConfig::new(url)).await
    }

    /// Get the inner pool reference
    pub fn inner(&self) -> &PgPool {
        &self.inner
    }

    /// Ping the database
    pub async fn ping(&self) -> Result<(), sqlx::Error> {
        sqlx::query("SELECT 1")
            .fetch_one(&self.inner)
            .await
            .map(|_| ())
    }

    /// Start a transaction
    pub async fn begin(&self) -> Result<Transaction, sqlx::Error> {
        self.inner
            .begin()
            .await
            .map(|tx| Transaction { inner: Some(tx) })
    }
}

impl std::ops::Deref for DbPool {
    type Target = PgPool;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

/// Database transaction wrapper
pub struct Transaction {
    inner: Option<sqlx::Transaction<'static, sqlx::Postgres>>,
}

impl Transaction {
    /// Commit the transaction
    pub async fn commit(self) -> Result<(), sqlx::Error> {
        if let Some(tx) = self.inner {
            tx.commit().await
        } else {
            Ok(())
        }
    }

    /// Rollback the transaction
    pub async fn rollback(self) -> Result<(), sqlx::Error> {
        if let Some(tx) = self.inner {
            tx.rollback().await
        } else {
            Ok(())
        }
    }
}