use crate::config::SupabaseConfig;
use crate::error::{Error, Result};
use postgrest::Postgrest;
use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderName, HeaderValue};
#[cfg(feature = "realtime")]
use supabase_realtime_rs::{RealtimeClient, RealtimeClientOptions};
#[derive(Clone)]
pub struct SupabaseClient {
config: SupabaseConfig,
http: reqwest::Client,
postgrest: Postgrest,
#[cfg(feature = "realtime")]
realtime: std::sync::Arc<RealtimeClient>,
}
impl SupabaseClient {
pub fn new(url: impl Into<String>, api_key: impl Into<String>) -> Result<Self> {
let config = SupabaseConfig::new(url, api_key);
Self::with_config(config)
}
pub fn with_config(config: SupabaseConfig) -> Result<Self> {
if config.url.is_empty() {
return Err(Error::config("URL is required"));
}
if config.api_key.is_empty() {
return Err(Error::config("API key is required"));
}
let mut headers = HeaderMap::new();
headers.insert(
"apikey",
HeaderValue::from_str(&config.api_key).map_err(|e| Error::config(e.to_string()))?,
);
let auth_value = if let Some(ref jwt) = config.jwt {
format!("Bearer {}", jwt)
} else {
format!("Bearer {}", config.api_key)
};
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&auth_value).map_err(|e| Error::config(e.to_string()))?,
);
for (key, value) in &config.headers {
let name = HeaderName::try_from(key.as_str())
.map_err(|e| Error::config(format!("invalid header name: {}", e)))?;
let val = HeaderValue::from_str(value)
.map_err(|e| Error::config(format!("invalid header value: {}", e)))?;
headers.insert(name, val);
}
let http = reqwest::Client::builder()
.default_headers(headers.clone())
.timeout(config.timeout)
.build()?;
let postgrest = Postgrest::new(config.rest_url())
.insert_header("apikey", &config.api_key)
.insert_header("Authorization", &auth_value);
#[cfg(feature = "realtime")]
let realtime = {
let realtime_client = RealtimeClient::new(
&config.realtime_url(),
RealtimeClientOptions {
api_key: config.api_key.clone(),
..Default::default()
},
)?;
std::sync::Arc::new(realtime_client)
};
Ok(Self {
config: config.clone(),
http,
postgrest,
#[cfg(feature = "realtime")]
realtime,
})
}
pub fn from(&self, table: &str) -> postgrest::Builder {
self.postgrest.from(table)
}
pub fn rpc(&self, function: &str, params: &str) -> postgrest::Builder {
self.postgrest.rpc(function, params)
}
pub fn config(&self) -> &SupabaseConfig {
&self.config
}
pub fn http(&self) -> &reqwest::Client {
&self.http
}
pub fn postgrest(&self) -> &Postgrest {
&self.postgrest
}
pub fn with_jwt(&self, jwt: impl Into<String>) -> Result<Self> {
let mut new_config: SupabaseConfig = self.config.clone();
new_config.jwt = Some(jwt.into());
Self::with_config(new_config)
}
#[cfg(feature = "realtime")]
pub fn realtime(&self) -> &RealtimeClient {
&self.realtime
}
pub fn realtime_url(&self) -> String {
self.config.realtime_url()
}
}
impl std::fmt::Debug for SupabaseClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SupabaseClient")
.field("url", &self.config.url)
.field("schema", &self.config.schema)
.finish_non_exhaustive()
}
}