breez_sdk_spark/
sdk_context.rs1use std::sync::Arc;
2
3use breez_sdk_common::breez_server::{BreezServer, PRODUCTION_BREEZSERVER_URL};
4use platform_utils::{HttpClient, create_http_client};
5
6use spark_wallet::{BalancedConnectionManager, ConnectionManager, DefaultConnectionManager};
7
8use crate::{Network, SdkError, default_user_agent, jwt_header_provider::BreezJwtHeaderProvider};
9
10#[cfg(feature = "mysql")]
11use crate::persist::mysql::{
12 MysqlConnectionPool, MysqlStorageConfig, create_mysql_connection_pool,
13};
14#[cfg(feature = "postgres")]
15use crate::persist::postgres::{
16 PostgresConnectionPool, PostgresStorageConfig, create_postgres_connection_pool,
17};
18
19#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
33pub struct SdkContext {
34 pub(crate) http_client: Arc<dyn HttpClient>,
37 pub(crate) breez_server: Arc<BreezServer>,
40 pub(crate) jwt_header_provider: Option<Arc<BreezJwtHeaderProvider>>,
45 pub(crate) network: Network,
48 pub(crate) api_key: Option<String>,
51 pub(crate) connection_manager: Arc<dyn ConnectionManager>,
52 #[cfg(feature = "postgres")]
53 pub(crate) postgres_pool: Option<Arc<PostgresConnectionPool>>,
54 #[cfg(feature = "mysql")]
55 pub(crate) mysql_pool: Option<Arc<MysqlConnectionPool>>,
56}
57
58#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
61pub struct SdkContextConfig {
62 pub network: Network,
66
67 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
71 pub api_key: Option<String>,
72
73 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
78 pub connections_per_operator: Option<u32>,
79
80 #[cfg(feature = "postgres")]
84 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
85 pub postgres_config: Option<PostgresStorageConfig>,
86
87 #[cfg(feature = "mysql")]
91 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
92 pub mysql_config: Option<MysqlStorageConfig>,
93}
94
95impl SdkContextConfig {
96 #[must_use]
101 pub fn new(network: Network) -> Self {
102 Self {
103 network,
104 api_key: None,
105 connections_per_operator: None,
106 #[cfg(feature = "postgres")]
107 postgres_config: None,
108 #[cfg(feature = "mysql")]
109 mysql_config: None,
110 }
111 }
112}
113
114#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
124pub async fn new_shared_sdk_context(config: SdkContextConfig) -> Result<Arc<SdkContext>, SdkError> {
125 #[cfg(all(feature = "postgres", feature = "mysql"))]
128 if config.postgres_config.is_some() && config.mysql_config.is_some() {
129 return Err(SdkError::Generic(
130 "Multiple storage configurations provided".to_string(),
131 ));
132 }
133
134 let user_agent = default_user_agent();
135 let http_client = create_http_client(Some(&user_agent));
136 let breez_server = Arc::new(
137 BreezServer::new(PRODUCTION_BREEZSERVER_URL, None, &user_agent)
138 .map_err(|e| SdkError::Generic(e.to_string()))?,
139 );
140 let api_key = config.api_key;
145 let jwt_header_provider = if matches!(config.network, Network::Mainnet)
146 && let Some(ref key) = api_key
147 {
148 Some(BreezJwtHeaderProvider::new(
149 key.clone(),
150 None,
151 http_client.clone(),
152 ))
153 } else {
154 None
155 };
156 let connection_manager: Arc<dyn ConnectionManager> = match config.connections_per_operator {
161 Some(n) if n > 1 => Arc::new(BalancedConnectionManager::new(n)),
162 _ => Arc::new(DefaultConnectionManager::new()),
163 };
164
165 #[cfg(feature = "postgres")]
166 let postgres_pool = match config.postgres_config {
167 Some(cfg) => Some(create_postgres_connection_pool(&cfg)?),
168 None => None,
169 };
170
171 #[cfg(feature = "mysql")]
172 let mysql_pool = match config.mysql_config {
173 Some(cfg) => Some(create_mysql_connection_pool(&cfg)?),
174 None => None,
175 };
176
177 Ok(Arc::new(SdkContext {
178 http_client,
179 breez_server,
180 jwt_header_provider,
181 network: config.network,
182 api_key,
183 connection_manager,
184 #[cfg(feature = "postgres")]
185 postgres_pool,
186 #[cfg(feature = "mysql")]
187 mysql_pool,
188 }))
189}
190
191#[cfg(all(test, not(target_family = "wasm")))]
192mod tests {
193 use super::*;
194
195 #[tokio::test]
196 async fn default_config_yields_context_with_shared_clients_and_no_db() {
197 let ctx = new_shared_sdk_context(SdkContextConfig::new(Network::Regtest))
198 .await
199 .expect("default context");
200 let _http = Arc::clone(&ctx.http_client);
202 let _breez = Arc::clone(&ctx.breez_server);
203 let _so = Arc::clone(&ctx.connection_manager);
204 assert!(ctx.jwt_header_provider.is_none());
206 assert_eq!(ctx.network, Network::Regtest);
208 assert!(ctx.api_key.is_none());
209 #[cfg(feature = "postgres")]
210 assert!(ctx.postgres_pool.is_none());
211 #[cfg(feature = "mysql")]
212 assert!(ctx.mysql_pool.is_none());
213 }
214
215 #[tokio::test]
216 async fn mainnet_with_api_key_constructs_jwt_provider_and_stores_inputs() {
217 let ctx = new_shared_sdk_context(SdkContextConfig {
218 api_key: Some("test-key".to_string()),
219 ..SdkContextConfig::new(Network::Mainnet)
220 })
221 .await
222 .expect("mainnet context");
223 assert!(ctx.jwt_header_provider.is_some());
224 assert_eq!(ctx.network, Network::Mainnet);
225 assert_eq!(ctx.api_key.as_deref(), Some("test-key"));
226 }
227
228 #[tokio::test]
229 async fn regtest_with_api_key_skips_jwt_but_still_stores_inputs() {
230 let ctx = new_shared_sdk_context(SdkContextConfig {
231 api_key: Some("test-key".to_string()),
232 ..SdkContextConfig::new(Network::Regtest)
233 })
234 .await
235 .expect("regtest context");
236 assert!(ctx.jwt_header_provider.is_none());
240 assert_eq!(ctx.network, Network::Regtest);
241 assert_eq!(ctx.api_key.as_deref(), Some("test-key"));
242 }
243}