@@ -415,6 +415,54 @@ pub struct ServerApi {
415415 pub deprecation_errors : Option < bool > ,
416416}
417417
418+ /// Configuration for connecting to a SOCKS5 proxy.
419+ #[ cfg( feature = "socks5-proxy" ) ]
420+ #[ derive( Clone , Debug , Deserialize , PartialEq , TypedBuilder ) ]
421+ #[ builder( field_defaults( default , setter( into) ) ) ]
422+ #[ non_exhaustive]
423+ pub struct Socks5Proxy {
424+ /// The hostname or IP address on which the proxy is listening.
425+ pub host : String ,
426+
427+ /// The port on which the proxy is listening. Defaults to 1080 if unset.
428+ pub port : Option < u16 > ,
429+
430+ /// A username/password pair to authenticate to the proxy.
431+ pub authentication : Option < ( String , String ) > ,
432+ }
433+
434+ #[ cfg( feature = "socks5-proxy" ) ]
435+ impl Socks5Proxy {
436+ fn serialize < S > (
437+ proxy : & Option < Socks5Proxy > ,
438+ serializer : S ,
439+ ) -> std:: result:: Result < S :: Ok , S :: Error >
440+ where
441+ S : serde:: Serializer ,
442+ {
443+ #[ derive( Serialize ) ]
444+ #[ serde( rename_all = "camelCase" ) ]
445+ struct Helper < ' a > {
446+ proxy_host : & ' a String ,
447+ proxy_port : Option < u16 > ,
448+ proxy_username : Option < & ' a String > ,
449+ proxy_password : Option < & ' a String > ,
450+ }
451+
452+ if let Some ( proxy) = proxy. as_ref ( ) {
453+ let helper = Helper {
454+ proxy_host : & proxy. host ,
455+ proxy_port : proxy. port ,
456+ proxy_username : proxy. authentication . as_ref ( ) . map ( |auth| & auth. 0 ) ,
457+ proxy_password : proxy. authentication . as_ref ( ) . map ( |auth| & auth. 1 ) ,
458+ } ;
459+ helper. serialize ( serializer)
460+ } else {
461+ serializer. serialize_none ( )
462+ }
463+ }
464+ }
465+
418466/// Contains the options that can be used to create a new [`Client`](../struct.Client.html).
419467#[ derive( Clone , Deserialize , TypedBuilder ) ]
420468#[ builder( field_defaults( default , setter( into) ) ) ]
@@ -624,22 +672,9 @@ pub struct ClientOptions {
624672 #[ cfg( feature = "opentelemetry" ) ]
625673 pub tracing : Option < crate :: otel:: OpentelemetryOptions > ,
626674
627- /// The IPv4 or IPv6 address or domain name of a proxy server used for connecting to MongoDB
628- /// services.
675+ /// Configuration for connecting to a SOCKS5 proxy.
629676 #[ cfg( feature = "socks5-proxy" ) ]
630- pub proxy_host : Option < String > ,
631-
632- /// The port of the proxy server. Defaults to 1080 if unspecified.
633- #[ cfg( feature = "socks5-proxy" ) ]
634- pub proxy_port : Option < u16 > ,
635-
636- /// The username for authentication to the proxy server.
637- #[ cfg( feature = "socks5-proxy" ) ]
638- pub proxy_username : Option < String > ,
639-
640- /// The password for authentication to the proxy server.
641- #[ cfg( feature = "socks5-proxy" ) ]
642- pub proxy_password : Option < String > ,
677+ pub socks5_proxy : Option < Socks5Proxy > ,
643678
644679 /// Information from the SRV URI that generated these client options, if applicable.
645680 #[ builder( setter( skip) ) ]
@@ -773,6 +808,10 @@ impl Serialize for ClientOptions {
773808 srvmaxhosts : Option < i32 > ,
774809
775810 srvservicename : & ' a Option < String > ,
811+
812+ #[ cfg( feature = "socks5-proxy" ) ]
813+ #[ serde( flatten, serialize_with = "Socks5Proxy::serialize" ) ]
814+ socks5proxy : & ' a Option < Socks5Proxy > ,
776815 }
777816
778817 let client_options = ClientOptionsHelper {
@@ -807,6 +846,8 @@ impl Serialize for ClientOptions {
807846 . transpose ( )
808847 . map_err ( serde:: ser:: Error :: custom) ?,
809848 srvservicename : & self . srv_service_name ,
849+ #[ cfg( feature = "socks5-proxy" ) ]
850+ socks5proxy : & self . socks5_proxy ,
810851 } ;
811852
812853 client_options. serialize ( serializer)
@@ -1002,22 +1043,9 @@ pub struct ConnectionString {
10021043 /// Overrides the default "mongodb" service name for SRV lookup in both discovery and polling
10031044 pub srv_service_name : Option < String > ,
10041045
1005- /// The IPv4 or IPv6 address or domain name of a proxy server used for connecting to MongoDB
1006- /// services.
1007- #[ cfg( feature = "socks5-proxy" ) ]
1008- pub proxy_host : Option < String > ,
1009-
1010- /// The port of the proxy server. Defaults to 1080 if unspecified.
1011- #[ cfg( feature = "socks5-proxy" ) ]
1012- pub proxy_port : Option < u16 > ,
1013-
1014- /// The username for authentication to the proxy server.
1015- #[ cfg( feature = "socks5-proxy" ) ]
1016- pub proxy_username : Option < String > ,
1017-
1018- /// The password for authentication to the proxy server.
1019- #[ cfg( feature = "socks5-proxy" ) ]
1020- pub proxy_password : Option < String > ,
1046+ /// Configuration for connecting to a SOCKS5 proxy.
1047+ #[ serde( serialize_with = "Socks5Proxy::serialize" ) ]
1048+ pub socks5_proxy : Option < Socks5Proxy > ,
10211049
10221050 #[ serde( serialize_with = "serde_util::serialize_duration_option_as_int_millis" ) ]
10231051 wait_queue_timeout : Option < Duration > ,
@@ -1037,6 +1065,14 @@ struct ConnectionStringParts {
10371065 auth_mechanism_properties : Option < Document > ,
10381066 zlib_compression : Option < i32 > ,
10391067 auth_source : Option < String > ,
1068+ #[ cfg( feature = "socks5-proxy" ) ]
1069+ proxy_host : Option < String > ,
1070+ #[ cfg( feature = "socks5-proxy" ) ]
1071+ proxy_port : Option < u16 > ,
1072+ #[ cfg( feature = "socks5-proxy" ) ]
1073+ proxy_username : Option < String > ,
1074+ #[ cfg( feature = "socks5-proxy" ) ]
1075+ proxy_password : Option < String > ,
10401076}
10411077
10421078/// Specification for mongodb server connections.
@@ -1361,7 +1397,7 @@ impl ClientOptions {
13611397
13621398 #[ cfg( feature = "socks5-proxy" ) ]
13631399 {
1364- if self . proxy_host . is_some ( ) {
1400+ if let Some ( proxy ) = self . socks5_proxy . as_ref ( ) {
13651401 if self
13661402 . hosts
13671403 . iter ( )
@@ -1372,25 +1408,12 @@ impl ClientOptions {
13721408 ) ) ;
13731409 }
13741410
1375- if self . proxy_username . is_some ( ) != self . proxy_password . is_some ( ) {
1376- return Err ( Error :: invalid_argument (
1377- "proxy username and proxy password must both be specified or unset" ,
1378- ) ) ;
1379- }
1380- } else {
1381- let error = |option : & str | {
1382- Error :: invalid_argument ( format ! (
1383- "cannot specify a proxy {option} without a proxy host"
1384- ) )
1385- } ;
1386- if self . proxy_port . is_some ( ) {
1387- return Err ( error ( "port" ) ) ;
1388- }
1389- if self . proxy_username . is_some ( ) {
1390- return Err ( error ( "username" ) ) ;
1391- }
1392- if self . proxy_password . is_some ( ) {
1393- return Err ( error ( "password" ) ) ;
1411+ if let Some ( ( username, password) ) = proxy. authentication . as_ref ( ) {
1412+ if username. is_empty ( ) || password. is_empty ( ) {
1413+ return Err ( Error :: invalid_argument (
1414+ "cannot specify an empty username or password for proxy host" ,
1415+ ) ) ;
1416+ }
13941417 }
13951418 }
13961419 }
@@ -1742,6 +1765,43 @@ impl ConnectionString {
17421765 conn_str. tls = Some ( Tls :: Enabled ( Default :: default ( ) ) ) ;
17431766 }
17441767
1768+ #[ cfg( feature = "socks5-proxy" ) ]
1769+ {
1770+ if let Some ( host) = parts. proxy_host {
1771+ let mut proxy = Socks5Proxy :: builder ( ) . host ( host) . build ( ) ;
1772+ if let Some ( port) = parts. proxy_port {
1773+ proxy. port = Some ( port) ;
1774+ }
1775+ match ( parts. proxy_username , parts. proxy_password ) {
1776+ ( Some ( username) , Some ( password) ) => {
1777+ proxy. authentication = Some ( ( username, password) )
1778+ }
1779+ ( None , None ) => { }
1780+ _ => {
1781+ return Err ( Error :: invalid_argument (
1782+ "proxy username and password must both be specified as nonempty \
1783+ strings or unset",
1784+ ) ) ;
1785+ }
1786+ }
1787+ } else {
1788+ let error = |option : & str | {
1789+ Error :: invalid_argument ( format ! (
1790+ "{option} cannot be set if {PROXY_HOST} is unspecified"
1791+ ) )
1792+ } ;
1793+ if parts. proxy_port . is_some ( ) {
1794+ return Err ( error ( PROXY_PORT ) ) ;
1795+ }
1796+ if parts. proxy_username . is_some ( ) {
1797+ return Err ( error ( PROXY_USERNAME ) ) ;
1798+ }
1799+ if parts. proxy_password . is_some ( ) {
1800+ return Err ( error ( PROXY_PASSWORD ) ) ;
1801+ }
1802+ }
1803+ }
1804+
17451805 Ok ( conn_str)
17461806 }
17471807
@@ -1783,13 +1843,7 @@ impl ConnectionString {
17831843 wait_queue_timeout,
17841844 tls_insecure,
17851845 #[ cfg ( feature = "socks5-proxy" ) ]
1786- proxy_host,
1787- #[ cfg ( feature = "socks5-proxy" ) ]
1788- proxy_port,
1789- #[ cfg ( feature = "socks5-proxy" ) ]
1790- proxy_username,
1791- #[ cfg ( feature = "socks5-proxy" ) ]
1792- proxy_password,
1846+ socks5_proxy,
17931847 #[ cfg ( test) ]
17941848 original_uri: _,
17951849 } = self ;
@@ -2092,21 +2146,15 @@ impl ConnectionString {
20922146 }
20932147
20942148 #[ cfg( feature = "socks5-proxy" ) ]
2095- {
2096- if let Some ( proxy_host) = proxy_host {
2097- opts. push_str ( & format ! ( "&proxyHost={proxy_host}" ) ) ;
2098- }
2099-
2100- if let Some ( proxy_port) = proxy_port {
2101- opts. push_str ( & format ! ( "&proxyPort={proxy_port}" ) ) ;
2149+ if let Some ( proxy) = socks5_proxy {
2150+ opts. push_str ( & format ! ( "&proxyHost={}" , proxy. host) ) ;
2151+ if let Some ( port) = proxy. port {
2152+ opts. push_str ( & format ! ( "&proxyPort={port}" ) ) ;
21022153 }
2103-
2104- if let Some ( proxy_username) = proxy_username {
2105- opts. push_str ( & format ! ( "&proxyUsername={proxy_username}" ) ) ;
2106- }
2107-
2108- if let Some ( proxy_password) = proxy_password {
2109- opts. push_str ( & format ! ( "&proxyPassword={proxy_password}" ) ) ;
2154+ if let Some ( ( username, password) ) = proxy. authentication . as_ref ( ) {
2155+ opts. push_str ( & format ! (
2156+ "&proxyUsername={username}&proxyPassword={password}"
2157+ ) ) ;
21102158 }
21112159 }
21122160
@@ -2689,17 +2737,17 @@ impl ConnectionString {
26892737 parts. zlib_compression = Some ( i) ;
26902738 }
26912739 #[ cfg( feature = "socks5-proxy" ) ]
2692- PROXY_HOST => self . proxy_host = Some ( value. to_string ( ) ) ,
2740+ PROXY_HOST => parts . proxy_host = Some ( value. to_string ( ) ) ,
26932741 #[ cfg( feature = "socks5-proxy" ) ]
26942742 PROXY_PORT => {
26952743 let port = u16:: from_str ( value)
26962744 . map_err ( |_| Error :: invalid_argument ( format ! ( "invalid proxy port: {value}" ) ) ) ?;
2697- self . proxy_port = Some ( port) ;
2745+ parts . proxy_port = Some ( port) ;
26982746 }
26992747 #[ cfg( feature = "socks5-proxy" ) ]
2700- PROXY_USERNAME => self . proxy_username = Some ( value. to_string ( ) ) ,
2748+ PROXY_USERNAME if !value . is_empty ( ) => parts . proxy_username = Some ( value. to_string ( ) ) ,
27012749 #[ cfg( feature = "socks5-proxy" ) ]
2702- PROXY_PASSWORD => self . proxy_password = Some ( value. to_string ( ) ) ,
2750+ PROXY_PASSWORD if !value . is_empty ( ) => parts . proxy_password = Some ( value. to_string ( ) ) ,
27032751 #[ cfg( not( feature = "socks5-proxy" ) ) ]
27042752 PROXY_HOST | PROXY_PORT | PROXY_USERNAME | PROXY_PASSWORD => {
27052753 return Err ( Error :: invalid_argument ( format ! (
0 commit comments