Skip to main content

tank_core/
connection.rs

1use crate::{Driver, Error, Executor, Result};
2use anyhow::Context;
3use convert_case::{Case, Casing};
4use std::{
5    borrow::Cow,
6    future::{self, Future},
7};
8use url::Url;
9
10/// A live database handle capable of executing queries and spawning transactions.
11///
12/// Extends [`Executor`] with connection and transaction management.
13///
14/// # Lifecycle
15/// - `connect` creates (or fetches) an underlying connection. It may eagerly
16///   establish network I/O; always await it.
17/// - `begin` starts a transaction scope. Commit/rollback MUST be awaited to
18///   guarantee resource release.
19pub trait Connection: Executor {
20    /// Validates and normalizes the connection URL, handling special cases like in-memory databases.
21    fn sanitize_url(driver: &Self::Driver, mut url: Cow<'static, str>) -> Result<Url>
22    where
23        Self: Sized,
24    {
25        let mut in_memory = false;
26        if let Some((scheme, host)) = url.split_once("://")
27            && host.starts_with(":memory:")
28        {
29            url = format!("{scheme}://localhost{}", &host[8..]).into();
30            in_memory = true;
31        }
32        let context = || {
33            format!(
34                "While trying to connect to {}",
35                driver.name().to_case(Case::Title)
36            )
37        };
38        let mut result = Url::parse(&url).with_context(context)?;
39        if in_memory {
40            result.query_pairs_mut().append_pair("mode", "memory");
41        }
42        let names = <Self::Driver as Driver>::NAME;
43        'prefix: {
44            for name in names {
45                let prefix = format!("{}://", name);
46                if url.starts_with(&prefix) {
47                    break 'prefix prefix;
48                }
49            }
50            let error = Error::msg(format!(
51                "Connection URL must start with: {}",
52                names.join(", ")
53            ))
54            .context(context());
55            log::error!("{:#}", error);
56            return Err(error);
57        };
58        Ok(result)
59    }
60
61    /// Establishes a connection (or pool) to the database specified by the URL.
62    ///
63    /// Implementations may perform I/O or validation during `connect`.
64    /// Callers should treat this as a potentially expensive operation.
65    fn connect(
66        driver: &Self::Driver,
67        url: Cow<'static, str>,
68    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>>
69    where
70        Self: Sized;
71
72    /// Starts a new transaction on this connection.
73    ///
74    /// Must await `commit` or `rollback` to finalize the scope and release resources.
75    fn begin(&mut self) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>>;
76
77    /// Closes the connection and releases any session resources.
78    fn disconnect(self) -> impl Future<Output = Result<()>>
79    where
80        Self: Sized,
81    {
82        future::ready(Ok(()))
83    }
84}