supabase_client_rs/lib.rs
1//! # supabase-client-rs
2//!
3//! A Rust client for [Supabase](https://supabase.com), the open-source Firebase alternative.
4//!
5//! This crate provides a unified interface to Supabase services by composing existing
6//! community crates:
7//!
8//! - **Database**: Uses [`postgrest-rs`](https://crates.io/crates/postgrest) for PostgREST queries
9//! - **Realtime**: Integrates with [`supabase-realtime-rs`](https://github.com/scaraude/supabase-realtime-rs)
10//! - **Auth, Storage, Functions**: Extensible via traits for community implementations
11//!
12//! ## Quick Start
13//!
14//! ```rust,no_run
15//! use supabase_client_rs::SupabaseClient;
16//!
17//! #[tokio::main]
18//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! // Create a client
20//! let client = SupabaseClient::new(
21//! "https://your-project.supabase.co",
22//! "your-anon-key"
23//! )?;
24//!
25//! // Query the database
26//! let response = client
27//! .from("users")
28//! .select("id, name, email")
29//! .eq("active", "true")
30//! .execute()
31//! .await?;
32//!
33//! let body = response.text().await?;
34//! println!("Users: {}", body);
35//!
36//! Ok(())
37//! }
38//! ```
39//!
40//! ## Database Queries
41//!
42//! The client wraps `postgrest-rs` and provides a fluent API for database operations:
43//!
44//! ```rust,no_run
45//! # use supabase_client_rs::SupabaseClient;
46//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
47//! # let client = SupabaseClient::new("url", "key")?;
48//! // Select with filters
49//! let users = client
50//! .from("users")
51//! .select("*")
52//! .eq("status", "active")
53//! .order("created_at.desc")
54//! .limit(10)
55//! .execute()
56//! .await?;
57//!
58//! // Insert
59//! let new_user = client
60//! .from("users")
61//! .insert(r#"{"name": "Alice", "email": "alice@example.com"}"#)
62//! .execute()
63//! .await?;
64//!
65//! // Update
66//! let updated = client
67//! .from("users")
68//! .update(r#"{"status": "inactive"}"#)
69//! .eq("id", "123")
70//! .execute()
71//! .await?;
72//!
73//! // Delete
74//! let deleted = client
75//! .from("users")
76//! .delete()
77//! .eq("id", "123")
78//! .execute()
79//! .await?;
80//!
81//! // RPC (stored procedures)
82//! let result = client
83//! .rpc("get_user_stats", r#"{"user_id": "123"}"#)
84//! .execute()
85//! .await?;
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! ## Configuration
91//!
92//! For advanced configuration, use `SupabaseConfig`:
93//!
94//! ```rust,no_run
95//! use supabase_client_rs::{SupabaseClient, SupabaseConfig};
96//! use std::time::Duration;
97//!
98//! let config = SupabaseConfig::new(
99//! "https://your-project.supabase.co",
100//! "your-anon-key"
101//! )
102//! .schema("custom_schema")
103//! .timeout(Duration::from_secs(60))
104//! .header("X-Custom-Header", "value");
105//!
106//! let client = SupabaseClient::with_config(config).unwrap();
107//! ```
108//!
109//! ## Authenticated Requests
110//!
111//! After a user signs in, set their JWT:
112//!
113//! ```rust,no_run
114//! # use supabase_client_rs::SupabaseClient;
115//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
116//! # let client = SupabaseClient::new("url", "key")?;
117//! // Get JWT from your auth flow
118//! let user_jwt = "eyJhbGciOiJIUzI1NiIs...";
119//!
120//! // Create an authenticated client
121//! let auth_client = client.with_jwt(user_jwt)?;
122//!
123//! // Now requests include the user's JWT
124//! // RLS policies will apply based on the user
125//! # Ok(())
126//! # }
127//! ```
128//!
129//! ## Extending with Community Crates
130//!
131//! This crate defines traits for auth, storage, and functions that community
132//! crates can implement. See the [`traits`] module for details.
133//!
134//! ## Feature Flags
135//!
136//! - `rustls` (default): Use rustls for TLS
137//! - `native-tls`: Use native TLS instead of rustls
138//! - `realtime`: Enable Supabase Realtime support (requires `supabase-realtime-rs`)
139
140#![warn(missing_docs)]
141#![warn(rustdoc::missing_crate_level_docs)]
142
143mod client;
144mod config;
145mod error;
146pub mod traits;
147
148// Re-export main types
149pub use client::SupabaseClient;
150pub use config::SupabaseConfig;
151pub use error::{Error, Result};
152
153// Re-export postgrest for advanced usage
154pub use postgrest;
155
156// Re-export realtime types when feature is enabled
157#[cfg(feature = "realtime")]
158pub use supabase_realtime_rs;
159
160/// Create a new Supabase client.
161///
162/// This is a convenience function equivalent to `SupabaseClient::new()`.
163///
164/// # Example
165///
166/// ```rust,no_run
167/// use supabase_client_rs::create_client;
168///
169/// let client = create_client(
170/// "https://your-project.supabase.co",
171/// "your-anon-key"
172/// ).unwrap();
173/// ```
174pub fn create_client(url: impl Into<String>, api_key: impl Into<String>) -> Result<SupabaseClient> {
175 SupabaseClient::new(url, api_key)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_create_client() {
184 let client = create_client("https://example.supabase.co", "test-key");
185 assert!(client.is_ok());
186 }
187
188 #[test]
189 fn test_create_client_empty_url() {
190 let client = create_client("", "test-key");
191 assert!(client.is_err());
192 }
193
194 #[test]
195 fn test_create_client_empty_key() {
196 let client = create_client("https://example.supabase.co", "");
197 assert!(client.is_err());
198 }
199
200 #[test]
201 fn test_config_urls() {
202 let config = SupabaseConfig::new("https://example.supabase.co", "key");
203 assert_eq!(config.rest_url(), "https://example.supabase.co/rest/v1");
204 assert_eq!(config.auth_url(), "https://example.supabase.co/auth/v1");
205 assert_eq!(
206 config.storage_url(),
207 "https://example.supabase.co/storage/v1"
208 );
209 assert_eq!(
210 config.realtime_url(),
211 "wss://example.supabase.co/realtime/v1"
212 );
213 assert_eq!(
214 config.functions_url(),
215 "https://example.supabase.co/functions/v1"
216 );
217 }
218
219 #[test]
220 fn test_config_trailing_slash() {
221 let config = SupabaseConfig::new("https://example.supabase.co/", "key");
222 assert_eq!(config.rest_url(), "https://example.supabase.co/rest/v1");
223 }
224
225 #[test]
226 fn test_with_jwt() {
227 let client = create_client("https://example.supabase.co", "test-key").unwrap();
228 let auth_client = client.with_jwt("user-jwt");
229 assert!(auth_client.is_ok());
230 }
231
232 #[test]
233 fn test_config_builder() {
234 let config = SupabaseConfig::new("https://example.supabase.co", "key")
235 .schema("custom")
236 .timeout(std::time::Duration::from_secs(60))
237 .header("X-Test", "value")
238 .auto_refresh_token(false);
239
240 assert_eq!(config.schema, "custom");
241 assert_eq!(config.timeout, std::time::Duration::from_secs(60));
242 assert!(!config.auto_refresh_token);
243 }
244}