supabase_management_rs/
lib.rs

1//! # Supabase Management API Client
2//!
3//! **⚠️ Note: This crate is still a work in progress and not all API endpoints are implemented yet.**
4//!
5//! This crate provides a client for interacting with the [Supabase Management API](https://supabase.com/docs/reference/api/introduction).
6//!
7//! It allows management of Supabase projects, including:
8//!
9//! - **Organization management**: View and manage organizations
10//! - **Project operations**: Create, list, retrieve, update, delete, pause, and restore projects
11//! - **Project configuration**: Manage database settings, API keys, and network restrictions
12//! - **Database management**: Execute queries, manage database branches, and view usage metrics
13//! - **Storage management**: Configure buckets, policies, and other storage settings
14//! - **Functions management**: Deploy, list and configure edge functions
15//! - **Project monitoring**: Check health status and view logs
16//! - **SSL enforcement**: Configure custom domains and SSL settings
17//! - **Postgres extensions**: Manage available and enabled extensions
18//!
19//! ## Example
20//!
21//! ```no_run
22//! use supabase_management_rs::Client;
23//!
24//! #[tokio::main]
25//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
26//!     // Create a client with your Supabase management API key
27//!     let client = Client::new("your-api-key".to_string());
28//!
29//!     // List all projects
30//!     let projects = client.list_projects().await?;
31//!
32//!     // Get the first project
33//!     if let Some(project) = projects.first() {
34//!         println!("Project name: {}", project.name);
35//!
36//!         // Check project health
37//!         let health = client.get_project_health(&project.id).await?;
38//!         println!("Project health: {:?}", health);
39//!
40//!         // Execute a query
41//!         let results: serde_json::Value = client
42//!             .query(&project.id, "SELECT now()")
43//!             .await?;
44//!         println!("Query result: {:?}", results);
45//!
46//!         // Pause a project
47//!         client.pause_project(&project.id).await?;
48//!     }
49//!
50//!     Ok(())
51//! }
52//! ```
53
54use std::{fmt, sync::LazyLock};
55
56/// Module for generating access tokens for Supabase projects
57mod auth;
58mod error;
59/// Module for managing Postgres configuration settings of a Supabase project
60mod postgres_configs;
61/// Module for managing Supabase projects, including creation, deletion, and configuration
62mod project;
63/// Module for executing SQL queries on Supabase projects
64mod query;
65/// Module for listing and managing storage settings in Supabase projects
66mod storage;
67/// Get supavisor details
68mod supavisor;
69
70pub use auth::*;
71pub use error::Error;
72pub use postgres_configs::*;
73pub use project::*;
74use serde::{de::DeserializeOwned, Serialize};
75pub use storage::*;
76pub use supavisor::*;
77
78macro_rules! error {
79    ($($arg:tt)*) => {
80        crate::error::with_context(format_args!($($arg)*))
81    };
82}
83
84const BASE_URL: &str = "https://api.supabase.com/v1";
85pub(crate) static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);
86
87/// A client to interact with the Supabase Management API.
88#[derive(Clone)]
89pub struct Client {
90    api_key: String,
91}
92
93impl fmt::Debug for Client {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("Client").finish()
96    }
97}
98
99impl Client {
100    /// Creates a new client with the given API key.
101    ///
102    /// See [the docs](https://supabase.com/docs/reference/api/introduction) to learn how to obtain an API key.
103    pub fn new(api_key: String) -> Self {
104        Self { api_key }
105    }
106
107    pub(crate) async fn send_request<T: DeserializeOwned>(
108        &self,
109        builder: reqwest::RequestBuilder,
110    ) -> Result<T, Error> {
111        let builder = builder.bearer_auth(&self.api_key);
112        send_request(builder).await
113    }
114
115    pub(crate) async fn get<T: DeserializeOwned>(&self, endpoint: String) -> Result<T, Error> {
116        let url = format!("{BASE_URL}/{}", endpoint);
117        // Use send_request so that the bearer token is applied.
118        self.send_request(CLIENT.get(url)).await
119    }
120
121    pub(crate) async fn post<T: DeserializeOwned>(
122        &self,
123        endpoint: String,
124        payload: Option<impl Serialize>,
125    ) -> Result<T, Error> {
126        let url = format!("{BASE_URL}/{}", endpoint);
127        let mut builder = CLIENT.post(url);
128        if let Some(payload) = payload {
129            builder = builder.json(&payload);
130        }
131        self.send_request(builder).await
132    }
133
134    pub(crate) async fn put<T: DeserializeOwned>(
135        &self,
136        endpoint: String,
137        payload: Option<impl Serialize>,
138    ) -> Result<T, Error> {
139        let url = format!("{BASE_URL}/{}", endpoint);
140        let mut builder = CLIENT.put(url);
141        if let Some(payload) = payload {
142            builder = builder.json(&payload);
143        }
144        self.send_request(builder).await
145    }
146}
147
148pub(crate) async fn send_request<T: DeserializeOwned>(
149    builder: reqwest::RequestBuilder,
150) -> Result<T, Error> {
151    let resp = builder
152        .send()
153        .await
154        .map_err(|err| error!("Failed to send request: {err}"))?;
155    let status = resp.status();
156
157    if status.is_client_error() || status.is_server_error() {
158        let code = status.as_u16();
159        let msg = resp
160            .text()
161            .await
162            .map_err(|err| Error(format!("Failed to get text from response: {err}").into()))?;
163        return Err(error!("{code}: {msg}"));
164    }
165
166    resp.json().await.map_err(|err| {
167        error!(
168            "Failed to parse JSON into {}: {err}",
169            std::any::type_name::<T>()
170        )
171    })
172}