Skip to main content

supabase_client_core/
config.rs

1#[cfg(feature = "direct-sql")]
2use std::time::Duration;
3
4/// Configuration for connecting to a Supabase instance.
5///
6/// By default, uses the PostgREST REST API (no database connection needed).
7/// Enable the `direct-sql` feature and call `.database_url()` to use direct SQL via sqlx.
8#[derive(Debug, Clone)]
9pub struct SupabaseConfig {
10    /// Supabase project URL (e.g. "http://localhost:64321")
11    pub supabase_url: String,
12    /// Supabase API key (anon or service_role)
13    pub supabase_key: String,
14    /// Optional PostgreSQL connection string (for direct-sql feature)
15    #[cfg(feature = "direct-sql")]
16    pub database_url: Option<String>,
17    /// Default schema (defaults to "public")
18    pub schema: String,
19    /// Connection pool configuration (only used with direct-sql)
20    #[cfg(feature = "direct-sql")]
21    pub pool: PoolConfig,
22}
23
24/// Connection pool settings (only available with `direct-sql` feature).
25#[cfg(feature = "direct-sql")]
26#[derive(Debug, Clone)]
27pub struct PoolConfig {
28    pub max_connections: u32,
29    pub min_connections: u32,
30    pub acquire_timeout: Duration,
31    pub idle_timeout: Option<Duration>,
32    pub max_lifetime: Option<Duration>,
33}
34
35#[cfg(feature = "direct-sql")]
36impl Default for PoolConfig {
37    fn default() -> Self {
38        Self {
39            max_connections: 10,
40            min_connections: 1,
41            acquire_timeout: Duration::from_secs(30),
42            idle_timeout: Some(Duration::from_secs(600)),
43            max_lifetime: Some(Duration::from_secs(1800)),
44        }
45    }
46}
47
48impl SupabaseConfig {
49    /// Create a new REST-first config with Supabase URL and API key.
50    ///
51    /// This is the primary constructor. No database connection is needed.
52    pub fn new(supabase_url: impl Into<String>, supabase_key: impl Into<String>) -> Self {
53        Self {
54            supabase_url: supabase_url.into(),
55            supabase_key: supabase_key.into(),
56            #[cfg(feature = "direct-sql")]
57            database_url: None,
58            schema: "public".to_string(),
59            #[cfg(feature = "direct-sql")]
60            pool: PoolConfig::default(),
61        }
62    }
63
64    /// Set the default schema.
65    pub fn schema(mut self, schema: impl Into<String>) -> Self {
66        self.schema = schema.into();
67        self
68    }
69
70    /// Set the PostgreSQL database URL for direct SQL access.
71    #[cfg(feature = "direct-sql")]
72    pub fn database_url(mut self, url: impl Into<String>) -> Self {
73        self.database_url = Some(url.into());
74        self
75    }
76
77    /// Set maximum number of pool connections.
78    #[cfg(feature = "direct-sql")]
79    pub fn max_connections(mut self, n: u32) -> Self {
80        self.pool.max_connections = n;
81        self
82    }
83
84    /// Set minimum number of pool connections.
85    #[cfg(feature = "direct-sql")]
86    pub fn min_connections(mut self, n: u32) -> Self {
87        self.pool.min_connections = n;
88        self
89    }
90
91    /// Set pool acquire timeout.
92    #[cfg(feature = "direct-sql")]
93    pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
94        self.pool.acquire_timeout = timeout;
95        self
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_new_sets_url_and_key() {
105        let config = SupabaseConfig::new("http://localhost:54321", "my-anon-key");
106        assert_eq!(config.supabase_url, "http://localhost:54321");
107        assert_eq!(config.supabase_key, "my-anon-key");
108    }
109
110    #[test]
111    fn test_new_defaults_schema_to_public() {
112        let config = SupabaseConfig::new("http://localhost:54321", "key");
113        assert_eq!(config.schema, "public");
114    }
115
116    #[test]
117    fn test_schema_builder_changes_schema() {
118        let config = SupabaseConfig::new("http://localhost:54321", "key").schema("private");
119        assert_eq!(config.schema, "private");
120    }
121
122    #[test]
123    fn test_schema_builder_with_custom_schema() {
124        let config = SupabaseConfig::new("http://localhost:54321", "key").schema("my_schema");
125        assert_eq!(config.schema, "my_schema");
126    }
127
128    #[test]
129    fn test_accessor_fields() {
130        let config =
131            SupabaseConfig::new("https://project.supabase.co", "service-role-key").schema("api");
132        assert_eq!(config.supabase_url, "https://project.supabase.co");
133        assert_eq!(config.supabase_key, "service-role-key");
134        assert_eq!(config.schema, "api");
135    }
136
137    #[test]
138    fn test_new_with_string_owned() {
139        let url = String::from("http://example.com");
140        let key = String::from("secret");
141        let config = SupabaseConfig::new(url, key);
142        assert_eq!(config.supabase_url, "http://example.com");
143        assert_eq!(config.supabase_key, "secret");
144    }
145
146    #[test]
147    fn test_config_clone() {
148        let config = SupabaseConfig::new("http://localhost:54321", "key").schema("custom");
149        let cloned = config.clone();
150        assert_eq!(cloned.supabase_url, config.supabase_url);
151        assert_eq!(cloned.supabase_key, config.supabase_key);
152        assert_eq!(cloned.schema, config.schema);
153    }
154}