1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct AppConfig {
11 #[serde(default = "default_db_uri")]
16 pub db_uri: String,
17
18 #[serde(default = "default_db_schemas")]
20 pub db_schemas: Vec<String>,
21
22 pub db_anon_role: Option<String>,
24
25 #[serde(default = "default_pool_size")]
27 pub db_pool_size: u32,
28
29 #[serde(default = "default_pool_timeout")]
31 pub db_pool_timeout: u64,
32
33 #[serde(default = "default_true")]
35 pub db_prepared_statements: bool,
36
37 #[serde(default)]
39 pub db_extra_search_path: Vec<String>,
40
41 #[serde(default = "default_db_channel")]
43 pub db_channel: String,
44
45 #[serde(default)]
47 pub db_channel_enabled: bool,
48
49 pub db_pre_request: Option<String>,
51
52 pub db_max_rows: Option<i64>,
54
55 #[serde(default = "default_true")]
57 pub db_aggregates_enabled: bool,
58
59 #[serde(default = "default_host")]
64 pub server_host: String,
65
66 #[serde(default = "default_port")]
68 pub server_port: u16,
69
70 pub server_unix_socket: Option<String>,
72
73 pub admin_server_port: Option<u16>,
75
76 pub jwt_secret: Option<String>,
81
82 #[serde(default)]
84 pub jwt_secret_is_base64: bool,
85
86 pub jwt_aud: Option<String>,
88
89 #[serde(default = "default_jwt_role_claim")]
91 pub jwt_role_claim_key: String,
92
93 #[serde(default = "default_true")]
95 pub jwt_cache_enabled: bool,
96
97 #[serde(default = "default_jwt_cache_max")]
99 pub jwt_cache_max_lifetime: u64,
100
101 pub openapi_server_proxy_uri: Option<String>,
106
107 #[serde(default = "default_openapi_mode")]
109 pub openapi_mode: OpenApiMode,
110
111 #[serde(default = "default_log_level")]
116 pub log_level: LogLevel,
117
118 #[serde(default)]
123 pub role_settings: HashMap<String, RoleSettings>,
124
125 #[serde(default)]
127 pub app_settings: HashMap<String, String>,
128
129 #[serde(default)]
141 pub compat_mode: bool,
142}
143
144impl Default for AppConfig {
145 fn default() -> Self {
146 Self {
147 db_uri: default_db_uri(),
148 db_schemas: default_db_schemas(),
149 db_anon_role: None,
150 db_pool_size: default_pool_size(),
151 db_pool_timeout: default_pool_timeout(),
152 db_prepared_statements: true,
153 db_extra_search_path: vec![],
154 db_channel: default_db_channel(),
155 db_channel_enabled: false,
156 db_pre_request: None,
157 db_max_rows: None,
158 db_aggregates_enabled: true,
159 server_host: default_host(),
160 server_port: default_port(),
161 server_unix_socket: None,
162 admin_server_port: None,
163 jwt_secret: None,
164 jwt_secret_is_base64: false,
165 jwt_aud: None,
166 jwt_role_claim_key: default_jwt_role_claim(),
167 jwt_cache_enabled: true,
168 jwt_cache_max_lifetime: default_jwt_cache_max(),
169 openapi_server_proxy_uri: None,
170 openapi_mode: OpenApiMode::FollowPrivileges,
171 log_level: LogLevel::Error,
172 role_settings: HashMap::new(),
173 app_settings: HashMap::new(),
174 compat_mode: false,
175 }
176 }
177}
178
179impl AppConfig {
180 pub fn from_env() -> Self {
182 let mut config = Self::default();
183
184 if let Ok(uri) = std::env::var("PGRST_DB_URI") {
185 config.db_uri = uri;
186 }
187 if let Ok(uri) = std::env::var("DATABASE_URL") {
188 config.db_uri = uri;
189 }
190 if let Ok(schemas) = std::env::var("PGRST_DB_SCHEMAS") {
191 config.db_schemas = schemas.split(',').map(|s| s.trim().to_string()).collect();
192 }
193 if let Ok(role) = std::env::var("PGRST_DB_ANON_ROLE") {
194 config.db_anon_role = Some(role);
195 }
196 if let Ok(size) = std::env::var("PGRST_DB_POOL") {
197 if let Ok(n) = size.parse() {
198 config.db_pool_size = n;
199 }
200 }
201 if let Ok(secret) = std::env::var("PGRST_JWT_SECRET") {
202 config.jwt_secret = Some(secret);
203 }
204 if let Ok(aud) = std::env::var("PGRST_JWT_AUD") {
205 config.jwt_aud = Some(aud);
206 }
207 if let Ok(host) = std::env::var("PGRST_SERVER_HOST") {
208 config.server_host = host;
209 }
210 if let Ok(port) = std::env::var("PGRST_SERVER_PORT") {
211 if let Ok(p) = port.parse() {
212 config.server_port = p;
213 }
214 }
215 if let Ok(port) = std::env::var("PORT") {
216 if let Ok(p) = port.parse() {
217 config.server_port = p;
218 }
219 }
220 for var in ["PGRST_COMPAT_MODE", "POSTRUST_COMPAT_MODE"] {
223 if let Ok(v) = std::env::var(var) {
224 config.compat_mode = matches!(
225 v.trim().to_ascii_lowercase().as_str(),
226 "true" | "1" | "yes" | "on"
227 );
228 }
229 }
230
231 config
232 }
233
234 pub fn default_schema(&self) -> &str {
236 self.db_schemas
237 .first()
238 .map(|s| s.as_str())
239 .unwrap_or("public")
240 }
241}
242
243#[derive(Clone, Debug, Serialize, Deserialize)]
245pub struct RoleSettings {
246 pub isolation_level: Option<IsolationLevel>,
248 pub statement_timeout: Option<u64>,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
254pub enum IsolationLevel {
255 ReadCommitted,
256 RepeatableRead,
257 Serializable,
258}
259
260impl IsolationLevel {
261 pub fn to_sql(&self) -> &'static str {
262 match self {
263 Self::ReadCommitted => "READ COMMITTED",
264 Self::RepeatableRead => "REPEATABLE READ",
265 Self::Serializable => "SERIALIZABLE",
266 }
267 }
268}
269
270#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
272pub enum OpenApiMode {
273 Disabled,
274 FollowPrivileges,
275 IgnorePrivileges,
276 SecurityDefiner,
277}
278
279#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
281pub enum LogLevel {
282 Crit,
283 Error,
284 Warn,
285 Info,
286 Debug,
287}
288
289impl LogLevel {
290 pub fn to_tracing(&self) -> tracing::Level {
291 match self {
292 Self::Crit | Self::Error => tracing::Level::ERROR,
293 Self::Warn => tracing::Level::WARN,
294 Self::Info => tracing::Level::INFO,
295 Self::Debug => tracing::Level::DEBUG,
296 }
297 }
298}
299
300fn default_db_uri() -> String {
302 "postgresql://localhost/postgres".to_string()
303}
304
305fn default_db_schemas() -> Vec<String> {
306 vec!["public".to_string()]
307}
308
309fn default_pool_size() -> u32 {
310 10
311}
312
313fn default_pool_timeout() -> u64 {
314 10
315}
316
317fn default_db_channel() -> String {
318 "pgrst".to_string()
319}
320
321fn default_host() -> String {
322 "127.0.0.1".to_string()
323}
324
325fn default_port() -> u16 {
326 3000
327}
328
329fn default_jwt_role_claim() -> String {
330 "role".to_string()
331}
332
333fn default_jwt_cache_max() -> u64 {
334 3600
335}
336
337fn default_openapi_mode() -> OpenApiMode {
338 OpenApiMode::FollowPrivileges
339}
340
341fn default_log_level() -> LogLevel {
342 LogLevel::Error
343}
344
345fn default_true() -> bool {
346 true
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_default_config() {
355 let config = AppConfig::default();
356 assert_eq!(config.server_port, 3000);
357 assert_eq!(config.db_pool_size, 10);
358 assert!(config.db_prepared_statements);
359 }
360
361 #[test]
362 fn test_default_schema() {
363 let mut config = AppConfig::default();
364 assert_eq!(config.default_schema(), "public");
365
366 config.db_schemas = vec!["api".to_string(), "public".to_string()];
367 assert_eq!(config.default_schema(), "api");
368 }
369
370 #[test]
371 fn test_isolation_level_sql() {
372 assert_eq!(IsolationLevel::ReadCommitted.to_sql(), "READ COMMITTED");
373 assert_eq!(IsolationLevel::Serializable.to_sql(), "SERIALIZABLE");
374 }
375}