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>> + Send
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(
76        &mut self,
77    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send;
78
79    /// Closes the connection and releases any session resources.
80    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
81    where
82        Self: Sized,
83    {
84        future::ready(Ok(()))
85    }
86}
87
88impl<S: Connection> Connection for &mut S {
89    fn connect(
90        driver: &Self::Driver,
91        url: Cow<'static, str>,
92    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>> + Send
93    where
94        Self: Sized,
95    {
96        S::connect(driver, url)
97    }
98
99    fn begin(
100        &mut self,
101    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send {
102        (**self).begin()
103    }
104
105    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
106    where
107        Self: Sized,
108    {
109        future::ready(Err(Error::msg(
110            "Cannot disconnect using `&mut Connection`, use the actual object.",
111        )))
112    }
113}