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}