tank-core 0.44.0

Core of Tank: the Rust data layer. This is intended to be used by drivers to implement a backend.
Documentation
use crate::{Driver, ErrorContext, Executor, Result, truncate_long};
use anyhow::anyhow;
use std::{
    borrow::Cow,
    future::{self, Future},
};
use url::Url;

/// A live database handle capable of executing queries and spawning transactions.
///
/// Extends [`Executor`] with connection and transaction management.
///
/// # Lifecycle
/// - `connect` creates (or fetches) an underlying connection. It may eagerly
///   establish network I/O; always await it.
/// - `begin` starts a transaction scope. Commit/rollback MUST be awaited to
///   guarantee resource release.
pub trait Connection: Executor {
    /// Validates and normalizes the connection URL, handling special cases like in-memory databases.
    fn sanitize_url(_driver: &Self::Driver, mut url: Cow<'static, str>) -> Result<Url>
    where
        Self: Sized,
    {
        let mut in_memory = false;
        if let Some((scheme, host)) = url.split_once("://")
            && host.starts_with(":memory:")
        {
            url = format!("{scheme}://localhost{}", &host[8..]).into();
            in_memory = true;
        }
        let make_context = || format!("While parsing the connection URL:\n{}", truncate_long!(url));
        let mut result = Url::parse(&url).with_context(make_context)?;
        if in_memory {
            result.query_pairs_mut().append_pair("mode", "memory");
        }
        let names = <Self::Driver as Driver>::NAME;
        if !names
            .iter()
            .any(|name| url.starts_with(name) && url[name.len()..].starts_with("://"))
        {
            let error = anyhow!("Connection URL must start with: {}", names.join(", "))
                .context(make_context());
            log::error!("{error:#}");
            return Err(error);
        }
        Ok(result)
    }

    /// Establishes a connection (or pool) to the database specified by the URL.
    ///
    /// Implementations may perform I/O or validation during `connect`.
    /// Callers should treat this as a potentially expensive operation.
    fn connect(
        driver: &Self::Driver,
        url: Cow<'static, str>,
    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>> + Send
    where
        Self: Sized;

    /// Starts a new transaction on this connection.
    ///
    /// Must await `commit` or `rollback` to finalize the scope and release resources.
    fn begin(
        &mut self,
    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send;

    /// Closes the connection and releases any session resources.
    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
    where
        Self: Sized,
    {
        future::ready(Ok(()))
    }
}

impl<S: Connection> Connection for &mut S {
    fn connect(
        driver: &Self::Driver,
        url: Cow<'static, str>,
    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>> + Send
    where
        Self: Sized,
    {
        S::connect(driver, url)
    }

    fn begin(
        &mut self,
    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send {
        (**self).begin()
    }

    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
    where
        Self: Sized,
    {
        future::ready(Err(anyhow!(
            "Cannot disconnect using `&mut Connection`, use the actual object.",
        )))
    }
}