Skip to main content

stateset_embedded/commerce/
builder.rs

1#[cfg(feature = "postgres")]
2use super::block_on_postgres_connect_with_options;
3use super::{Commerce, CommerceBackend};
4
5use std::sync::Arc;
6
7use stateset_core::CommerceError;
8use stateset_db::Database;
9use stateset_observability::{MetricsConfig, init_metrics};
10
11#[cfg(feature = "events")]
12use crate::events::{EventConfig, EventSystem};
13
14#[cfg(feature = "sqlite")]
15use stateset_db::{DatabaseConfig, SqliteDatabase};
16
17/// Builder for creating a Commerce instance with custom configuration.
18#[derive(Default)]
19#[must_use]
20pub struct CommerceBuilder {
21    #[cfg(feature = "sqlite")]
22    sqlite_path: Option<String>,
23    #[cfg(feature = "postgres")]
24    postgres_url: Option<String>,
25    max_connections: Option<u32>,
26    #[cfg(feature = "postgres")]
27    acquire_timeout_secs: Option<u64>,
28    #[cfg(feature = "events")]
29    event_config: Option<EventConfig>,
30    metrics_config: MetricsConfig,
31}
32
33impl std::fmt::Debug for CommerceBuilder {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("CommerceBuilder").finish_non_exhaustive()
36    }
37}
38
39impl CommerceBuilder {
40    /// Create a new builder with default settings.
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// Set the SQLite database path.
46    #[cfg(feature = "sqlite")]
47    pub fn sqlite(mut self, path: &str) -> Self {
48        self.sqlite_path = Some(path.to_string());
49        self
50    }
51
52    /// Set the database path (alias for sqlite).
53    #[cfg(feature = "sqlite")]
54    pub fn database(self, path: &str) -> Self {
55        self.sqlite(path)
56    }
57
58    /// Configure for in-memory SQLite database.
59    #[cfg(feature = "sqlite")]
60    pub fn in_memory(mut self) -> Self {
61        self.sqlite_path = Some(":memory:".to_string());
62        self
63    }
64
65    /// Set the PostgreSQL connection URL.
66    ///
67    /// When this is set, the builder will create a PostgreSQL connection
68    /// instead of SQLite.
69    #[cfg(feature = "postgres")]
70    pub fn postgres(mut self, url: &str) -> Self {
71        self.postgres_url = Some(url.to_string());
72        self
73    }
74
75    /// Set the maximum number of database connections.
76    pub const fn max_connections(mut self, count: u32) -> Self {
77        self.max_connections = Some(count);
78        self
79    }
80
81    /// Configure in-process engine metrics collection.
82    pub const fn metrics_config(mut self, config: MetricsConfig) -> Self {
83        self.metrics_config = config;
84        self
85    }
86
87    /// Disable in-process engine metrics collection.
88    pub const fn disable_metrics(mut self) -> Self {
89        self.metrics_config.enabled = false;
90        self
91    }
92
93    /// Set the acquire timeout for PostgreSQL connections.
94    #[cfg(feature = "postgres")]
95    pub const fn acquire_timeout_secs(mut self, secs: u64) -> Self {
96        self.acquire_timeout_secs = Some(secs);
97        self
98    }
99
100    /// Configure the event system.
101    ///
102    /// # Example
103    ///
104    /// ```rust,ignore
105    /// use stateset_embedded::{Commerce, EventConfig};
106    ///
107    /// let commerce = Commerce::builder()
108    ///     .database(":memory:")
109    ///     .event_config(EventConfig {
110    ///         channel_capacity: 2048,
111    ///         enable_webhooks: true,
112    ///         ..Default::default()
113    ///     })
114    ///     .build()?;
115    /// # Ok::<(), stateset_embedded::CommerceError>(())
116    /// ```
117    #[cfg(feature = "events")]
118    pub fn event_config(mut self, config: EventConfig) -> Self {
119        self.event_config = Some(config);
120        self
121    }
122
123    /// Build with sensible defaults for development/testing.
124    ///
125    /// Creates an in-memory SQLite database with default settings.
126    /// This is the quickest way to get started for testing.
127    ///
128    /// # Example
129    ///
130    /// ```rust,ignore
131    /// use stateset_embedded::Commerce;
132    ///
133    /// let commerce = Commerce::builder().build_with_defaults()?;
134    /// // Equivalent to Commerce::in_memory()
135    /// # Ok::<(), stateset_embedded::CommerceError>(())
136    /// ```
137    #[cfg(feature = "sqlite")]
138    pub fn build_with_defaults(self) -> Result<Commerce, CommerceError> {
139        self.in_memory().build()
140    }
141
142    /// Build the Commerce instance.
143    pub fn build(self) -> Result<Commerce, CommerceError> {
144        let metrics = init_metrics(self.metrics_config.clone());
145
146        // Create event system if events feature is enabled
147        #[cfg(feature = "events")]
148        let event_system =
149            Arc::new(self.event_config.map(EventSystem::with_config).unwrap_or_default());
150
151        // Check if PostgreSQL URL is set
152        #[cfg(feature = "postgres")]
153        if let Some(url) = self.postgres_url {
154            let max_connections = self.max_connections.unwrap_or(10);
155            let acquire_timeout_secs = self.acquire_timeout_secs.unwrap_or(30);
156            let db =
157                block_on_postgres_connect_with_options(url, max_connections, acquire_timeout_secs)?;
158            let db: Arc<dyn Database> = Arc::new(db);
159
160            return Ok(Commerce {
161                db,
162                backend: CommerceBackend::Postgres,
163                metrics,
164                #[cfg(feature = "events")]
165                event_system,
166                #[cfg(feature = "sqlite")]
167                sqlite_db: None,
168                #[cfg(feature = "sqlite")]
169                sqlite_path: None,
170            });
171        }
172
173        #[cfg(all(feature = "postgres", not(feature = "sqlite")))]
174        return Err(CommerceError::Internal(
175            "PostgreSQL URL is required when the sqlite feature is disabled".to_string(),
176        ));
177
178        // Default to SQLite
179        #[cfg(feature = "sqlite")]
180        {
181            let path = self.sqlite_path.unwrap_or_else(|| "stateset.db".to_string());
182
183            let mut config = if path == ":memory:" {
184                DatabaseConfig::in_memory()
185            } else {
186                DatabaseConfig::sqlite(&path)
187            };
188            if let Some(max) = self.max_connections {
189                config.max_connections = max;
190            }
191
192            let sqlite_db = Arc::new(SqliteDatabase::new(&config)?);
193            let db: Arc<dyn Database> = sqlite_db.clone();
194            let file_path = (path != ":memory:").then(|| path.clone());
195            Ok(Commerce {
196                db,
197                backend: CommerceBackend::Sqlite,
198                metrics,
199                #[cfg(feature = "events")]
200                event_system,
201                #[cfg(feature = "sqlite")]
202                sqlite_db: Some(sqlite_db),
203                #[cfg(feature = "sqlite")]
204                sqlite_path: file_path,
205            })
206        }
207
208        #[cfg(not(any(feature = "sqlite", feature = "postgres")))]
209        Err(CommerceError::Internal(
210            "No database backend enabled. Enable 'sqlite' or 'postgres' feature.".to_string(),
211        ))
212    }
213}