supabase_client_core/
client.rs1use std::sync::Arc;
2
3#[cfg(feature = "direct-sql")]
4use sqlx::postgres::PgPoolOptions;
5#[cfg(feature = "direct-sql")]
6use sqlx::PgPool;
7
8use crate::config::SupabaseConfig;
9use crate::error::SupabaseResult;
10
11#[derive(Debug, Clone)]
16pub struct SupabaseClient {
17 config: Arc<SupabaseConfig>,
18 http: reqwest::Client,
19 #[cfg(feature = "direct-sql")]
20 pool: Option<Arc<PgPool>>,
21}
22
23impl SupabaseClient {
24 pub fn new(config: SupabaseConfig) -> SupabaseResult<Self> {
28 let http = reqwest::Client::new();
29 Ok(Self {
30 config: Arc::new(config),
31 http,
32 #[cfg(feature = "direct-sql")]
33 pool: None,
34 })
35 }
36
37 #[cfg(feature = "direct-sql")]
41 pub async fn with_database(config: SupabaseConfig) -> SupabaseResult<Self> {
42 let db_url = config
43 .database_url
44 .as_ref()
45 .ok_or_else(|| crate::error::SupabaseError::Config(
46 "database_url is required for direct-sql mode".into(),
47 ))?;
48
49 let pool = PgPoolOptions::new()
50 .max_connections(config.pool.max_connections)
51 .min_connections(config.pool.min_connections)
52 .acquire_timeout(config.pool.acquire_timeout)
53 .idle_timeout(config.pool.idle_timeout)
54 .max_lifetime(config.pool.max_lifetime)
55 .connect(db_url)
56 .await?;
57
58 let http = reqwest::Client::new();
59
60 Ok(Self {
61 config: Arc::new(config),
62 http,
63 pool: Some(Arc::new(pool)),
64 })
65 }
66
67 #[cfg(feature = "direct-sql")]
69 pub fn from_pool(pool: PgPool, config: SupabaseConfig) -> Self {
70 Self {
71 config: Arc::new(config),
72 http: reqwest::Client::new(),
73 pool: Some(Arc::new(pool)),
74 }
75 }
76
77 pub fn http(&self) -> &reqwest::Client {
79 &self.http
80 }
81
82 pub fn supabase_url(&self) -> &str {
84 &self.config.supabase_url
85 }
86
87 pub fn api_key(&self) -> &str {
89 &self.config.supabase_key
90 }
91
92 pub fn schema(&self) -> &str {
94 &self.config.schema
95 }
96
97 pub fn config(&self) -> &SupabaseConfig {
99 &self.config
100 }
101
102 #[cfg(feature = "direct-sql")]
104 pub fn pool(&self) -> Option<&PgPool> {
105 self.pool.as_deref()
106 }
107
108 #[cfg(feature = "direct-sql")]
110 pub fn pool_arc(&self) -> Option<Arc<PgPool>> {
111 self.pool.clone()
112 }
113
114 #[cfg(feature = "direct-sql")]
116 pub fn has_pool(&self) -> bool {
117 self.pool.is_some()
118 }
119
120 #[cfg(feature = "direct-sql")]
122 pub async fn close(&self) {
123 if let Some(pool) = &self.pool {
124 pool.close().await;
125 }
126 }
127
128 #[cfg(feature = "direct-sql")]
130 pub fn is_closed(&self) -> bool {
131 self.pool.as_ref().map_or(true, |p| p.is_closed())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::config::SupabaseConfig;
139
140 fn test_config() -> SupabaseConfig {
141 SupabaseConfig::new("http://localhost:54321", "test-anon-key")
142 }
143
144 #[test]
145 fn test_new_succeeds_with_valid_config() {
146 let client = SupabaseClient::new(test_config());
147 assert!(client.is_ok());
148 }
149
150 #[test]
151 fn test_supabase_url_returns_correct_url() {
152 let client = SupabaseClient::new(test_config()).unwrap();
153 assert_eq!(client.supabase_url(), "http://localhost:54321");
154 }
155
156 #[test]
157 fn test_api_key_returns_correct_key() {
158 let client = SupabaseClient::new(test_config()).unwrap();
159 assert_eq!(client.api_key(), "test-anon-key");
160 }
161
162 #[test]
163 fn test_schema_returns_public_by_default() {
164 let client = SupabaseClient::new(test_config()).unwrap();
165 assert_eq!(client.schema(), "public");
166 }
167
168 #[test]
169 fn test_schema_returns_custom_schema() {
170 let config = SupabaseConfig::new("http://localhost:54321", "key").schema("custom");
171 let client = SupabaseClient::new(config).unwrap();
172 assert_eq!(client.schema(), "custom");
173 }
174
175 #[test]
176 fn test_http_returns_client_reference() {
177 let client = SupabaseClient::new(test_config()).unwrap();
178 let _http: &reqwest::Client = client.http();
180 }
181
182 #[test]
183 fn test_config_returns_config_reference() {
184 let client = SupabaseClient::new(test_config()).unwrap();
185 let config = client.config();
186 assert_eq!(config.supabase_url, "http://localhost:54321");
187 assert_eq!(config.supabase_key, "test-anon-key");
188 assert_eq!(config.schema, "public");
189 }
190
191 #[test]
192 fn test_client_is_clone() {
193 let client = SupabaseClient::new(test_config()).unwrap();
194 let cloned = client.clone();
195 assert_eq!(cloned.supabase_url(), client.supabase_url());
196 assert_eq!(cloned.api_key(), client.api_key());
197 assert_eq!(cloned.schema(), client.schema());
198 }
199}