@@ -416,7 +416,7 @@ impl ConnectionEntry {
416416 let mut url = "postgres://" . to_string ( ) ;
417417
418418 // Add user
419- url. push_str ( & self . user ) ;
419+ url. push_str ( & urlencoding :: encode ( & self . user ) ) ;
420420
421421 // Add password if provided
422422 if let Some ( pwd) = password {
@@ -528,7 +528,9 @@ impl ConnectionEntry {
528528 DbKind :: Mongo => String :: new ( ) ,
529529 }
530530 } else {
531- url. username ( ) . to_string ( )
531+ urlencoding:: decode ( url. username ( ) )
532+ . map ( |s| s. into_owned ( ) )
533+ . unwrap_or_else ( |_| url. username ( ) . to_string ( ) )
532534 } ;
533535
534536 let password = url. password ( ) . map ( |p| {
@@ -2088,6 +2090,51 @@ user = "me"
20882090 assert ! ( url. contains( "p%40ss%3Aword%2F123" ) ) ;
20892091 }
20902092
2093+ #[ test]
2094+ fn test_connection_to_url_with_at_in_username ( ) {
2095+ let entry = ConnectionEntry {
2096+ name : "test" . to_string ( ) ,
2097+ host : "localhost" . to_string ( ) ,
2098+ port : 5432 ,
2099+ database : "mydb" . to_string ( ) ,
2100+ user : "user@domain.com" . to_string ( ) ,
2101+ ..Default :: default ( )
2102+ } ;
2103+
2104+ let url = entry. to_url ( None ) ;
2105+ assert_eq ! ( url, "postgres://user%40domain.com@localhost/mydb" ) ;
2106+ }
2107+
2108+ #[ test]
2109+ fn test_connection_to_url_encodes_reserved_username_chars ( ) {
2110+ let entry = ConnectionEntry {
2111+ name : "test" . to_string ( ) ,
2112+ host : "localhost" . to_string ( ) ,
2113+ port : 5432 ,
2114+ database : "mydb" . to_string ( ) ,
2115+ user : "user:name/role" . to_string ( ) ,
2116+ ..Default :: default ( )
2117+ } ;
2118+
2119+ let url = entry. to_url ( None ) ;
2120+ assert_eq ! ( url, "postgres://user%3Aname%2Frole@localhost/mydb" ) ;
2121+ }
2122+
2123+ #[ test]
2124+ fn test_connection_to_url_preserves_literal_percent_username ( ) {
2125+ let entry = ConnectionEntry {
2126+ name : "test" . to_string ( ) ,
2127+ host : "localhost" . to_string ( ) ,
2128+ port : 5432 ,
2129+ database : "mydb" . to_string ( ) ,
2130+ user : "user%40team" . to_string ( ) ,
2131+ ..Default :: default ( )
2132+ } ;
2133+
2134+ let url = entry. to_url ( None ) ;
2135+ assert_eq ! ( url, "postgres://user%2540team@localhost/mydb" ) ;
2136+ }
2137+
20912138 #[ test]
20922139 fn test_connection_to_url_non_default_port ( ) {
20932140 let entry = ConnectionEntry {
@@ -2117,6 +2164,28 @@ user = "me"
21172164 assert ! ( entry. ssl_mode. is_none( ) ) ;
21182165 }
21192166
2167+ #[ test]
2168+ fn test_connection_from_url_decodes_encoded_username ( ) {
2169+ let ( entry, password) =
2170+ ConnectionEntry :: from_url ( "test" , "postgres://user%40domain.com@localhost/mydb" )
2171+ . unwrap ( ) ;
2172+
2173+ assert_eq ! ( entry. user, "user@domain.com" ) ;
2174+ assert ! ( password. is_none( ) ) ;
2175+ }
2176+
2177+ #[ test]
2178+ fn test_connection_url_round_trip_literal_percent_username ( ) {
2179+ let ( entry, password) =
2180+ ConnectionEntry :: from_url ( "test" , "postgres://user%2540team@localhost/mydb" ) . unwrap ( ) ;
2181+
2182+ assert_eq ! ( entry. user, "user%40team" ) ;
2183+ assert ! ( password. is_none( ) ) ;
2184+
2185+ let url = entry. to_url ( None ) ;
2186+ assert_eq ! ( url, "postgres://user%2540team@localhost/mydb" ) ;
2187+ }
2188+
21202189 #[ test]
21212190 fn test_connection_from_url_with_password ( ) {
21222191 let ( entry, password) =
0 commit comments