1use crate::{
4 auth::Auth,
5 database::Database,
6 error::{Error, Result},
7 realtime::Realtime,
8 storage::Storage,
9 types::{AuthConfig, DatabaseConfig, HttpConfig, StorageConfig, SupabaseConfig},
10};
11use reqwest::{header::HeaderMap, Client as HttpClient};
12use std::{collections::HashMap, sync::Arc, time::Duration};
13use tracing::{debug, error, info};
14use url::Url;
15
16#[derive(Debug, Clone)]
18pub struct Client {
19 http_client: Arc<HttpClient>,
21 config: Arc<SupabaseConfig>,
23 auth: Auth,
25 database: Database,
27 storage: Storage,
29 realtime: Realtime,
31}
32
33impl Client {
34 pub fn new(url: &str, key: &str) -> Result<Self> {
50 let config = SupabaseConfig {
51 url: url.to_string(),
52 key: key.to_string(),
53 service_role_key: None,
54 http_config: HttpConfig::default(),
55 auth_config: AuthConfig::default(),
56 database_config: DatabaseConfig::default(),
57 storage_config: StorageConfig::default(),
58 };
59
60 Self::new_with_config(config)
61 }
62
63 pub fn new_with_service_role(
84 url: &str,
85 anon_key: &str,
86 service_role_key: &str,
87 ) -> Result<Self> {
88 let config = SupabaseConfig {
89 url: url.to_string(),
90 key: anon_key.to_string(),
91 service_role_key: Some(service_role_key.to_string()),
92 http_config: HttpConfig::default(),
93 auth_config: AuthConfig::default(),
94 database_config: DatabaseConfig::default(),
95 storage_config: StorageConfig::default(),
96 };
97
98 Self::new_with_config(config)
99 }
100
101 pub fn new_with_config(config: SupabaseConfig) -> Result<Self> {
126 let _base_url =
128 Url::parse(&config.url).map_err(|e| Error::config(format!("Invalid URL: {}", e)))?;
129
130 debug!("Creating Supabase client for URL: {}", config.url);
131
132 let http_client = Arc::new(Self::build_http_client(&config)?);
134 let config = Arc::new(config);
135
136 let auth = Auth::new(Arc::clone(&config), Arc::clone(&http_client))?;
138 let database = Database::new(Arc::clone(&config), Arc::clone(&http_client))?;
139 let storage = Storage::new(Arc::clone(&config), Arc::clone(&http_client))?;
140 let realtime = Realtime::new(Arc::clone(&config))?;
141
142 info!("Supabase client initialized successfully");
143
144 Ok(Self {
145 http_client,
146 config,
147 auth,
148 database,
149 storage,
150 realtime,
151 })
152 }
153
154 pub fn auth(&self) -> &Auth {
156 &self.auth
157 }
158
159 pub fn database(&self) -> &Database {
161 &self.database
162 }
163
164 pub fn storage(&self) -> &Storage {
166 &self.storage
167 }
168
169 pub fn realtime(&self) -> &Realtime {
171 &self.realtime
172 }
173
174 pub fn http_client(&self) -> Arc<HttpClient> {
176 Arc::clone(&self.http_client)
177 }
178
179 pub fn config(&self) -> Arc<SupabaseConfig> {
181 Arc::clone(&self.config)
182 }
183
184 pub fn url(&self) -> &str {
186 &self.config.url
187 }
188
189 pub fn key(&self) -> &str {
191 &self.config.key
192 }
193
194 pub async fn set_auth(&self, token: &str) -> Result<()> {
196 self.auth.set_session_token(token).await
197 }
198
199 pub async fn clear_auth(&self) -> Result<()> {
201 self.auth.clear_session().await
202 }
203
204 pub fn is_authenticated(&self) -> bool {
206 self.auth.is_authenticated()
207 }
208
209 pub async fn current_user(&self) -> Result<Option<crate::auth::User>> {
211 self.auth.current_user().await
212 }
213
214 fn build_http_client(config: &SupabaseConfig) -> Result<HttpClient> {
216 let mut headers = HeaderMap::new();
217
218 headers.insert(
220 "apikey",
221 config
222 .key
223 .parse()
224 .map_err(|e| Error::config(format!("Invalid API key: {}", e)))?,
225 );
226 headers.insert(
227 "Authorization",
228 format!("Bearer {}", config.key)
229 .parse()
230 .map_err(|e| Error::config(format!("Invalid authorization header: {}", e)))?,
231 );
232
233 for (key, value) in &config.http_config.default_headers {
235 let header_name = key
236 .parse::<reqwest::header::HeaderName>()
237 .map_err(|e| Error::config(format!("Invalid header key '{}': {}", key, e)))?;
238 let header_value = value
239 .parse::<reqwest::header::HeaderValue>()
240 .map_err(|e| Error::config(format!("Invalid header value for '{}': {}", key, e)))?;
241 headers.insert(header_name, header_value);
242 }
243
244 let client = HttpClient::builder()
245 .timeout(Duration::from_secs(config.http_config.timeout))
246 .connect_timeout(Duration::from_secs(config.http_config.connect_timeout))
247 .redirect(reqwest::redirect::Policy::limited(
248 config.http_config.max_redirects,
249 ))
250 .default_headers(headers)
251 .build()
252 .map_err(|e| Error::config(format!("Failed to build HTTP client: {}", e)))?;
253
254 Ok(client)
255 }
256
257 pub async fn health_check(&self) -> Result<bool> {
259 debug!("Performing health check");
260
261 let response = self
262 .http_client
263 .get(format!("{}/health", self.config.url))
264 .send()
265 .await?;
266
267 let is_healthy = response.status().is_success();
268
269 if is_healthy {
270 info!("Health check passed");
271 } else {
272 error!("Health check failed with status: {}", response.status());
273 }
274
275 Ok(is_healthy)
276 }
277
278 pub async fn version(&self) -> Result<HashMap<String, serde_json::Value>> {
280 debug!("Fetching version information");
281
282 let response = self
283 .http_client
284 .get(format!("{}/rest/v1/", self.config.url))
285 .send()
286 .await?;
287
288 if !response.status().is_success() {
289 return Err(Error::network(format!(
290 "Failed to fetch version info: {}",
291 response.status()
292 )));
293 }
294
295 let version_info = response.json().await?;
296 Ok(version_info)
297 }
298}