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 for var in ["PGRST_DB_MAX_ROWS", "PGRST_MAX_ROWS"] {
204 if let Ok(max_rows) = std::env::var(var) {
205 match max_rows.parse::<i64>() {
206 Ok(n) if n >= 0 => config.db_max_rows = Some(n),
207 _ => {
208 tracing::warn!(
209 "Ignoring {}={:?}: expected a non-negative integer",
210 var,
211 max_rows
212 );
213 }
214 }
215 }
216 }
217 if let Ok(secret) = std::env::var("PGRST_JWT_SECRET") {
218 config.jwt_secret = Some(secret);
219 }
220 if let Ok(aud) = std::env::var("PGRST_JWT_AUD") {
221 config.jwt_aud = Some(aud);
222 }
223 if let Ok(host) = std::env::var("PGRST_SERVER_HOST") {
224 config.server_host = host;
225 }
226 if let Ok(port) = std::env::var("PGRST_SERVER_PORT") {
227 if let Ok(p) = port.parse() {
228 config.server_port = p;
229 }
230 }
231 if let Ok(port) = std::env::var("PORT") {
232 if let Ok(p) = port.parse() {
233 config.server_port = p;
234 }
235 }
236 for var in ["PGRST_COMPAT_MODE", "POSTRUST_COMPAT_MODE"] {
239 if let Ok(v) = std::env::var(var) {
240 config.compat_mode = matches!(
241 v.trim().to_ascii_lowercase().as_str(),
242 "true" | "1" | "yes" | "on"
243 );
244 }
245 }
246
247 config
248 }
249
250 pub fn default_schema(&self) -> &str {
252 self.db_schemas
253 .first()
254 .map(|s| s.as_str())
255 .unwrap_or("public")
256 }
257}
258
259#[derive(Clone, Debug, Serialize, Deserialize)]
261pub struct RoleSettings {
262 pub isolation_level: Option<IsolationLevel>,
264 pub statement_timeout: Option<u64>,
266}
267
268#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
270pub enum IsolationLevel {
271 ReadCommitted,
272 RepeatableRead,
273 Serializable,
274}
275
276impl IsolationLevel {
277 pub fn to_sql(&self) -> &'static str {
278 match self {
279 Self::ReadCommitted => "READ COMMITTED",
280 Self::RepeatableRead => "REPEATABLE READ",
281 Self::Serializable => "SERIALIZABLE",
282 }
283 }
284}
285
286#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
288pub enum OpenApiMode {
289 Disabled,
290 FollowPrivileges,
291 IgnorePrivileges,
292 SecurityDefiner,
293}
294
295#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
297pub enum LogLevel {
298 Crit,
299 Error,
300 Warn,
301 Info,
302 Debug,
303}
304
305impl LogLevel {
306 pub fn to_tracing(&self) -> tracing::Level {
307 match self {
308 Self::Crit | Self::Error => tracing::Level::ERROR,
309 Self::Warn => tracing::Level::WARN,
310 Self::Info => tracing::Level::INFO,
311 Self::Debug => tracing::Level::DEBUG,
312 }
313 }
314}
315
316fn default_db_uri() -> String {
318 "postgresql://localhost/postgres".to_string()
319}
320
321fn default_db_schemas() -> Vec<String> {
322 vec!["public".to_string()]
323}
324
325fn default_pool_size() -> u32 {
326 10
327}
328
329fn default_pool_timeout() -> u64 {
330 10
331}
332
333fn default_db_channel() -> String {
334 "pgrst".to_string()
335}
336
337fn default_host() -> String {
338 "127.0.0.1".to_string()
339}
340
341fn default_port() -> u16 {
342 3000
343}
344
345fn default_jwt_role_claim() -> String {
346 "role".to_string()
347}
348
349fn default_jwt_cache_max() -> u64 {
350 3600
351}
352
353fn default_openapi_mode() -> OpenApiMode {
354 OpenApiMode::FollowPrivileges
355}
356
357fn default_log_level() -> LogLevel {
358 LogLevel::Error
359}
360
361fn default_true() -> bool {
362 true
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_default_config() {
371 let config = AppConfig::default();
372 assert_eq!(config.server_port, 3000);
373 assert_eq!(config.db_pool_size, 10);
374 assert!(config.db_prepared_statements);
375 }
376
377 #[test]
378 fn test_default_schema() {
379 let mut config = AppConfig::default();
380 assert_eq!(config.default_schema(), "public");
381
382 config.db_schemas = vec!["api".to_string(), "public".to_string()];
383 assert_eq!(config.default_schema(), "api");
384 }
385
386 #[test]
387 fn test_isolation_level_sql() {
388 assert_eq!(IsolationLevel::ReadCommitted.to_sql(), "READ COMMITTED");
389 assert_eq!(IsolationLevel::Serializable.to_sql(), "SERIALIZABLE");
390 }
391}