Skip to main content

stateset_embedded/commerce/
mod.rs

1//! Main Commerce struct - the entry point to the library
2
3mod accessors;
4mod builder;
5mod constructors;
6mod events;
7mod introspection;
8
9#[cfg(all(test, feature = "sqlite"))]
10mod tests;
11
12use std::sync::Arc;
13
14#[cfg(feature = "postgres")]
15use stateset_core::CommerceError;
16use stateset_db::Database;
17#[cfg(feature = "postgres")]
18use stateset_db::PostgresDatabase;
19use stateset_observability::Metrics;
20
21#[cfg(feature = "events")]
22use crate::events::EventSystem;
23
24#[cfg(feature = "sqlite")]
25use stateset_db::SqliteDatabase;
26
27pub use builder::CommerceBuilder;
28pub use introspection::CommerceHealth;
29
30#[cfg(feature = "postgres")]
31fn new_postgres_runtime() -> Result<tokio::runtime::Runtime, CommerceError> {
32    tokio::runtime::Runtime::new()
33        .map_err(|e| CommerceError::Internal(format!("Failed to create runtime: {e}")))
34}
35
36#[cfg(feature = "postgres")]
37fn block_on_postgres_connect(url: String) -> Result<PostgresDatabase, CommerceError> {
38    match tokio::runtime::Handle::try_current() {
39        Ok(handle)
40            if matches!(handle.runtime_flavor(), tokio::runtime::RuntimeFlavor::MultiThread) =>
41        {
42            tokio::task::block_in_place(|| handle.block_on(PostgresDatabase::connect(url)))
43        }
44        Ok(_) => std::thread::spawn(move || {
45            let rt = new_postgres_runtime()?;
46            rt.block_on(PostgresDatabase::connect(url))
47        })
48        .join()
49        .map_err(|_| {
50            CommerceError::Internal("PostgreSQL initialization thread panicked".to_string())
51        })?,
52        Err(_) => {
53            let rt = new_postgres_runtime()?;
54            rt.block_on(PostgresDatabase::connect(url))
55        }
56    }
57}
58
59#[cfg(feature = "postgres")]
60fn block_on_postgres_connect_with_options(
61    url: String,
62    max_connections: u32,
63    acquire_timeout_secs: u64,
64) -> Result<PostgresDatabase, CommerceError> {
65    match tokio::runtime::Handle::try_current() {
66        Ok(handle)
67            if matches!(handle.runtime_flavor(), tokio::runtime::RuntimeFlavor::MultiThread) =>
68        {
69            tokio::task::block_in_place(|| {
70                handle.block_on(PostgresDatabase::connect_with_options(
71                    url,
72                    max_connections,
73                    acquire_timeout_secs,
74                ))
75            })
76        }
77        Ok(_) => std::thread::spawn(move || {
78            let rt = new_postgres_runtime()?;
79            rt.block_on(PostgresDatabase::connect_with_options(
80                url,
81                max_connections,
82                acquire_timeout_secs,
83            ))
84        })
85        .join()
86        .map_err(|_| {
87            CommerceError::Internal("PostgreSQL initialization thread panicked".to_string())
88        })?,
89        Err(_) => {
90            let rt = new_postgres_runtime()?;
91            rt.block_on(PostgresDatabase::connect_with_options(
92                url,
93                max_connections,
94                acquire_timeout_secs,
95            ))
96        }
97    }
98}
99
100/// Active database backend used by a [`Commerce`] instance.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[non_exhaustive]
103pub enum CommerceBackend {
104    /// Embedded SQLite backend.
105    Sqlite,
106    /// PostgreSQL backend.
107    Postgres,
108    /// Caller-provided database implementation.
109    External,
110}
111
112/// The main commerce interface.
113///
114/// This is the entry point to all commerce operations. Initialize it once
115/// and use the accessor methods to perform operations.
116///
117/// # Example
118///
119/// ```rust,ignore
120/// use stateset_embedded::Commerce;
121///
122/// // SQLite (default)
123/// let commerce = Commerce::new("./store.db")?;
124///
125/// // Access different domains
126/// let orders = commerce.orders();
127/// let inventory = commerce.inventory();
128/// let customers = commerce.customers();
129/// let products = commerce.products();
130/// let returns = commerce.returns();
131/// # Ok::<(), stateset_embedded::CommerceError>(())
132/// ```
133pub struct Commerce {
134    db: Arc<dyn Database>,
135    backend: CommerceBackend,
136    metrics: Metrics,
137    #[cfg(feature = "events")]
138    event_system: Arc<EventSystem>,
139    #[cfg(feature = "sqlite")]
140    sqlite_db: Option<Arc<SqliteDatabase>>,
141    /// Filesystem path of the SQLite database, when one is backing this
142    /// instance. Needed by backup/restore, which operate on files rather than
143    /// on the repository abstraction. `None` for `:memory:` and non-SQLite.
144    #[cfg(feature = "sqlite")]
145    sqlite_path: Option<String>,
146}
147
148impl std::fmt::Debug for Commerce {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        f.debug_struct("Commerce").field("backend", &self.backend).finish_non_exhaustive()
151    }
152}