Skip to main content

tank_core/
connection.rs

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