@@ -11,29 +11,29 @@ use serde::{Deserialize, Serialize};
1111pub struct DatabaseConfiguration {
1212 /// A connection string that encodes the full connection setup.
1313 /// When provided, it takes precedence over the individual fields.
14- #[ partial( bpaf( long( "connection-string" ) ) ) ]
14+ #[ partial( bpaf( env ( "DATABASE_URL" ) , long( "connection-string" ) ) ) ]
1515 pub connection_string : Option < String > ,
1616
1717 /// The host of the database.
1818 /// Required if you want database-related features.
1919 /// All else falls back to sensible defaults.
20- #[ partial( bpaf( long( "host" ) ) ) ]
20+ #[ partial( bpaf( env ( "PGHOST" ) , long( "host" ) ) ) ]
2121 pub host : String ,
2222
2323 /// The port of the database.
24- #[ partial( bpaf( long( "port" ) ) ) ]
24+ #[ partial( bpaf( env ( "PGPORT" ) , long( "port" ) ) ) ]
2525 pub port : u16 ,
2626
2727 /// The username to connect to the database.
28- #[ partial( bpaf( long( "username" ) ) ) ]
28+ #[ partial( bpaf( env ( "PGUSER" ) , long( "username" ) ) ) ]
2929 pub username : String ,
3030
3131 /// The password to connect to the database.
32- #[ partial( bpaf( long( "password" ) ) ) ]
32+ #[ partial( bpaf( env ( "PGPASSWORD" ) , long( "password" ) ) ) ]
3333 pub password : String ,
3434
3535 /// The name of the database.
36- #[ partial( bpaf( long( "database" ) ) ) ]
36+ #[ partial( bpaf( env ( "PGDATABASE" ) , long( "database" ) ) ) ]
3737 pub database : String ,
3838
3939 #[ partial( bpaf( long( "allow_statement_executions_against" ) ) ) ]
@@ -64,3 +64,156 @@ impl Default for DatabaseConfiguration {
6464 }
6565 }
6666}
67+
68+ impl PartialDatabaseConfiguration {
69+ /// Creates a partial configuration from standard Postgres environment variables.
70+ ///
71+ /// Reads `DATABASE_URL`, `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, and `PGDATABASE`.
72+ /// Returns `None` if no relevant env vars are set.
73+ pub fn from_env ( ) -> Option < Self > {
74+ let database_url = std:: env:: var ( "DATABASE_URL" ) . ok ( ) ;
75+ let pghost = std:: env:: var ( "PGHOST" ) . ok ( ) ;
76+ let pgport = std:: env:: var ( "PGPORT" )
77+ . ok ( )
78+ . and_then ( |p| p. parse ( ) . ok ( ) ) ;
79+ let pguser = std:: env:: var ( "PGUSER" ) . ok ( ) ;
80+ let pgpassword = std:: env:: var ( "PGPASSWORD" ) . ok ( ) ;
81+ let pgdatabase = std:: env:: var ( "PGDATABASE" ) . ok ( ) ;
82+
83+ let has_any = database_url. is_some ( )
84+ || pghost. is_some ( )
85+ || pgport. is_some ( )
86+ || pguser. is_some ( )
87+ || pgpassword. is_some ( )
88+ || pgdatabase. is_some ( ) ;
89+
90+ if !has_any {
91+ return None ;
92+ }
93+
94+ Some ( Self {
95+ connection_string : database_url,
96+ host : pghost,
97+ port : pgport,
98+ username : pguser,
99+ password : pgpassword,
100+ database : pgdatabase,
101+ ..Default :: default ( )
102+ } )
103+ }
104+ }
105+
106+ #[ cfg( test) ]
107+ mod tests {
108+ use super :: * ;
109+ use std:: sync:: Mutex ;
110+
111+ static ENV_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
112+
113+ const ALL_VARS : & [ & str ] = & [
114+ "DATABASE_URL" ,
115+ "PGHOST" ,
116+ "PGPORT" ,
117+ "PGUSER" ,
118+ "PGPASSWORD" ,
119+ "PGDATABASE" ,
120+ ] ;
121+
122+ fn clear_env_vars ( ) {
123+ for var in ALL_VARS {
124+ unsafe {
125+ std:: env:: remove_var ( var) ;
126+ }
127+ }
128+ }
129+
130+ #[ test]
131+ fn from_env_none_when_no_vars_set ( ) {
132+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
133+ clear_env_vars ( ) ;
134+
135+ assert ! ( PartialDatabaseConfiguration :: from_env( ) . is_none( ) ) ;
136+ }
137+
138+ #[ test]
139+ fn from_env_all_vars_set ( ) {
140+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
141+ clear_env_vars ( ) ;
142+
143+ unsafe {
144+ std:: env:: set_var ( "DATABASE_URL" , "postgres://u:p@h:1234/d" ) ;
145+ std:: env:: set_var ( "PGHOST" , "myhost" ) ;
146+ std:: env:: set_var ( "PGPORT" , "5433" ) ;
147+ std:: env:: set_var ( "PGUSER" , "myuser" ) ;
148+ std:: env:: set_var ( "PGPASSWORD" , "mypass" ) ;
149+ std:: env:: set_var ( "PGDATABASE" , "mydb" ) ;
150+ }
151+
152+ let config = PartialDatabaseConfiguration :: from_env ( ) . unwrap ( ) ;
153+ assert_eq ! (
154+ config. connection_string,
155+ Some ( "postgres://u:p@h:1234/d" . to_string( ) )
156+ ) ;
157+ assert_eq ! ( config. host, Some ( "myhost" . to_string( ) ) ) ;
158+ assert_eq ! ( config. port, Some ( 5433 ) ) ;
159+ assert_eq ! ( config. username, Some ( "myuser" . to_string( ) ) ) ;
160+ assert_eq ! ( config. password, Some ( "mypass" . to_string( ) ) ) ;
161+ assert_eq ! ( config. database, Some ( "mydb" . to_string( ) ) ) ;
162+
163+ clear_env_vars ( ) ;
164+ }
165+
166+ #[ test]
167+ fn from_env_partial_vars ( ) {
168+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
169+ clear_env_vars ( ) ;
170+
171+ unsafe {
172+ std:: env:: set_var ( "PGHOST" , "remotehost" ) ;
173+ std:: env:: set_var ( "PGDATABASE" , "appdb" ) ;
174+ }
175+
176+ let config = PartialDatabaseConfiguration :: from_env ( ) . unwrap ( ) ;
177+ assert_eq ! ( config. connection_string, None ) ;
178+ assert_eq ! ( config. host, Some ( "remotehost" . to_string( ) ) ) ;
179+ assert_eq ! ( config. port, None ) ;
180+ assert_eq ! ( config. username, None ) ;
181+ assert_eq ! ( config. password, None ) ;
182+ assert_eq ! ( config. database, Some ( "appdb" . to_string( ) ) ) ;
183+
184+ clear_env_vars ( ) ;
185+ }
186+
187+ #[ test]
188+ fn from_env_invalid_pgport_ignored ( ) {
189+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
190+ clear_env_vars ( ) ;
191+
192+ unsafe {
193+ std:: env:: set_var ( "PGHOST" , "localhost" ) ;
194+ std:: env:: set_var ( "PGPORT" , "not_a_number" ) ;
195+ }
196+
197+ let config = PartialDatabaseConfiguration :: from_env ( ) . unwrap ( ) ;
198+ assert_eq ! ( config. host, Some ( "localhost" . to_string( ) ) ) ;
199+ assert_eq ! ( config. port, None ) ;
200+
201+ clear_env_vars ( ) ;
202+ }
203+
204+ #[ test]
205+ fn from_env_only_invalid_pgport_returns_none ( ) {
206+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
207+ clear_env_vars ( ) ;
208+
209+ unsafe {
210+ std:: env:: set_var ( "PGPORT" , "not_a_number" ) ;
211+ }
212+
213+ // PGPORT is set but invalid — parse fails so it becomes None.
214+ // No other vars are set, so has_any is false.
215+ assert ! ( PartialDatabaseConfiguration :: from_env( ) . is_none( ) ) ;
216+
217+ clear_env_vars ( ) ;
218+ }
219+ }
0 commit comments