use std::{fmt, sync::LazyLock};
mod auth;
mod error;
mod postgres_configs;
mod project;
mod query;
mod storage;
mod supavisor;
pub use auth::*;
pub use error::Error;
pub use postgres_configs::*;
pub use project::*;
use serde::{de::DeserializeOwned, Serialize};
pub use storage::*;
pub use supavisor::*;
macro_rules! error {
($($arg:tt)*) => {
crate::error::with_context(format_args!($($arg)*))
};
}
const BASE_URL: &str = "https://api.supabase.com/v1";
pub(crate) static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);
#[derive(Clone)]
pub struct Client {
api_key: String,
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client").finish()
}
}
impl Client {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
pub(crate) async fn send_request<T: DeserializeOwned>(
&self,
builder: reqwest::RequestBuilder,
) -> Result<T, Error> {
let builder = builder.bearer_auth(&self.api_key);
send_request(builder).await
}
pub(crate) async fn get<T: DeserializeOwned>(&self, endpoint: String) -> Result<T, Error> {
let url = format!("{BASE_URL}/{}", endpoint);
self.send_request(CLIENT.get(url)).await
}
pub(crate) async fn post<T: DeserializeOwned>(
&self,
endpoint: String,
payload: Option<impl Serialize>,
) -> Result<T, Error> {
let url = format!("{BASE_URL}/{}", endpoint);
let mut builder = CLIENT.post(url);
if let Some(payload) = payload {
builder = builder.json(&payload);
}
self.send_request(builder).await
}
pub(crate) async fn put<T: DeserializeOwned>(
&self,
endpoint: String,
payload: Option<impl Serialize>,
) -> Result<T, Error> {
let url = format!("{BASE_URL}/{}", endpoint);
let mut builder = CLIENT.put(url);
if let Some(payload) = payload {
builder = builder.json(&payload);
}
self.send_request(builder).await
}
}
pub(crate) async fn send_request<T: DeserializeOwned>(
builder: reqwest::RequestBuilder,
) -> Result<T, Error> {
let resp = builder
.send()
.await
.map_err(|err| error!("Failed to send request: {err}"))?;
let status = resp.status();
if status.is_client_error() || status.is_server_error() {
let code = status.as_u16();
let msg = resp
.text()
.await
.map_err(|err| Error(format!("Failed to get text from response: {err}").into()))?;
return Err(error!("{code}: {msg}"));
}
resp.json().await.map_err(|err| {
error!(
"Failed to parse JSON into {}: {err}",
std::any::type_name::<T>()
)
})
}