use std::sync::Arc;
#[cfg(feature = "direct-sql")]
use sqlx::postgres::PgPoolOptions;
#[cfg(feature = "direct-sql")]
use sqlx::PgPool;
use crate::config::SupabaseConfig;
use crate::error::SupabaseResult;
#[derive(Debug, Clone)]
pub struct SupabaseClient {
config: Arc<SupabaseConfig>,
http: reqwest::Client,
#[cfg(feature = "direct-sql")]
pool: Option<Arc<PgPool>>,
}
impl SupabaseClient {
pub fn new(config: SupabaseConfig) -> SupabaseResult<Self> {
let http = reqwest::Client::new();
Ok(Self {
config: Arc::new(config),
http,
#[cfg(feature = "direct-sql")]
pool: None,
})
}
#[cfg(feature = "direct-sql")]
pub async fn with_database(config: SupabaseConfig) -> SupabaseResult<Self> {
let db_url = config
.database_url
.as_ref()
.ok_or_else(|| crate::error::SupabaseError::Config(
"database_url is required for direct-sql mode".into(),
))?;
let pool = PgPoolOptions::new()
.max_connections(config.pool.max_connections)
.min_connections(config.pool.min_connections)
.acquire_timeout(config.pool.acquire_timeout)
.idle_timeout(config.pool.idle_timeout)
.max_lifetime(config.pool.max_lifetime)
.connect(db_url)
.await?;
let http = reqwest::Client::new();
Ok(Self {
config: Arc::new(config),
http,
pool: Some(Arc::new(pool)),
})
}
#[cfg(feature = "direct-sql")]
pub fn from_pool(pool: PgPool, config: SupabaseConfig) -> Self {
Self {
config: Arc::new(config),
http: reqwest::Client::new(),
pool: Some(Arc::new(pool)),
}
}
pub fn http(&self) -> &reqwest::Client {
&self.http
}
pub fn supabase_url(&self) -> &str {
&self.config.supabase_url
}
pub fn api_key(&self) -> &str {
&self.config.supabase_key
}
pub fn schema(&self) -> &str {
&self.config.schema
}
pub fn config(&self) -> &SupabaseConfig {
&self.config
}
#[cfg(feature = "direct-sql")]
pub fn pool(&self) -> Option<&PgPool> {
self.pool.as_deref()
}
#[cfg(feature = "direct-sql")]
pub fn pool_arc(&self) -> Option<Arc<PgPool>> {
self.pool.clone()
}
#[cfg(feature = "direct-sql")]
pub fn has_pool(&self) -> bool {
self.pool.is_some()
}
#[cfg(feature = "direct-sql")]
pub async fn close(&self) {
if let Some(pool) = &self.pool {
pool.close().await;
}
}
#[cfg(feature = "direct-sql")]
pub fn is_closed(&self) -> bool {
self.pool.as_ref().map_or(true, |p| p.is_closed())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::SupabaseConfig;
fn test_config() -> SupabaseConfig {
SupabaseConfig::new("http://localhost:54321", "test-anon-key")
}
#[test]
fn test_new_succeeds_with_valid_config() {
let client = SupabaseClient::new(test_config());
assert!(client.is_ok());
}
#[test]
fn test_supabase_url_returns_correct_url() {
let client = SupabaseClient::new(test_config()).unwrap();
assert_eq!(client.supabase_url(), "http://localhost:54321");
}
#[test]
fn test_api_key_returns_correct_key() {
let client = SupabaseClient::new(test_config()).unwrap();
assert_eq!(client.api_key(), "test-anon-key");
}
#[test]
fn test_schema_returns_public_by_default() {
let client = SupabaseClient::new(test_config()).unwrap();
assert_eq!(client.schema(), "public");
}
#[test]
fn test_schema_returns_custom_schema() {
let config = SupabaseConfig::new("http://localhost:54321", "key").schema("custom");
let client = SupabaseClient::new(config).unwrap();
assert_eq!(client.schema(), "custom");
}
#[test]
fn test_http_returns_client_reference() {
let client = SupabaseClient::new(test_config()).unwrap();
let _http: &reqwest::Client = client.http();
}
#[test]
fn test_config_returns_config_reference() {
let client = SupabaseClient::new(test_config()).unwrap();
let config = client.config();
assert_eq!(config.supabase_url, "http://localhost:54321");
assert_eq!(config.supabase_key, "test-anon-key");
assert_eq!(config.schema, "public");
}
#[test]
fn test_client_is_clone() {
let client = SupabaseClient::new(test_config()).unwrap();
let cloned = client.clone();
assert_eq!(cloned.supabase_url(), client.supabase_url());
assert_eq!(cloned.api_key(), client.api_key());
assert_eq!(cloned.schema(), client.schema());
}
}