@@ -75,7 +75,7 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback<HostA
75
75
76
76
// Resolve the SRV record and use the result as the list of hosts to connect to.
77
77
const lookupAddress = options . srvHost ;
78
- dns . resolveSrv ( `_mongodb ._tcp.${ lookupAddress } ` , ( err , addresses ) => {
78
+ dns . resolveSrv ( `_ ${ options . srvServiceName } ._tcp.${ lookupAddress } ` , ( err , addresses ) => {
79
79
if ( err ) return callback ( err ) ;
80
80
81
81
if ( addresses . length === 0 ) {
@@ -92,7 +92,7 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback<HostA
92
92
HostAddress . fromString ( `${ r . name } :${ r . port ?? 27017 } ` )
93
93
) ;
94
94
95
- const lbError = validateLoadBalancedOptions ( hostAddresses , options ) ;
95
+ const lbError = validateLoadBalancedOptions ( hostAddresses , options , true ) ;
96
96
if ( lbError ) {
97
97
return callback ( lbError ) ;
98
98
}
@@ -116,14 +116,14 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback<HostA
116
116
) ;
117
117
}
118
118
119
+ if ( VALID_TXT_RECORDS . some ( option => txtRecordOptions . get ( option ) === '' ) ) {
120
+ return callback ( new MongoParseError ( 'Cannot have empty URI params in DNS TXT Record' ) ) ;
121
+ }
122
+
119
123
const source = txtRecordOptions . get ( 'authSource' ) ?? undefined ;
120
124
const replicaSet = txtRecordOptions . get ( 'replicaSet' ) ?? undefined ;
121
125
const loadBalanced = txtRecordOptions . get ( 'loadBalanced' ) ?? undefined ;
122
126
123
- if ( source === '' || replicaSet === '' ) {
124
- return callback ( new MongoParseError ( 'Cannot have empty URI params in DNS TXT Record' ) ) ;
125
- }
126
-
127
127
if ( ! options . userSpecifiedAuthSource && source ) {
128
128
options . credentials = MongoCredentials . merge ( options . credentials , { source } ) ;
129
129
}
@@ -136,7 +136,11 @@ export function resolveSRVRecord(options: MongoOptions, callback: Callback<HostA
136
136
options . loadBalanced = true ;
137
137
}
138
138
139
- const lbError = validateLoadBalancedOptions ( hostAddresses , options ) ;
139
+ if ( options . replicaSet && options . srvMaxHosts > 0 ) {
140
+ return callback ( new MongoParseError ( 'Cannot combine replicaSet option with srvMaxHosts' ) ) ;
141
+ }
142
+
143
+ const lbError = validateLoadBalancedOptions ( hostAddresses , options , true ) ;
140
144
if ( lbError ) {
141
145
return callback ( lbError ) ;
142
146
}
@@ -251,13 +255,6 @@ export function parseOptions(
251
255
252
256
const mongoOptions = Object . create ( null ) ;
253
257
mongoOptions . hosts = isSRV ? [ ] : hosts . map ( HostAddress . fromString ) ;
254
- if ( isSRV ) {
255
- // SRV Record is resolved upon connecting
256
- mongoOptions . srvHost = hosts [ 0 ] ;
257
- if ( ! url . searchParams . has ( 'tls' ) && ! url . searchParams . has ( 'ssl' ) ) {
258
- options . tls = true ;
259
- }
260
- }
261
258
262
259
const urlOptions = new CaseInsensitiveMap ( ) ;
263
260
@@ -289,30 +286,34 @@ export function parseOptions(
289
286
throw new MongoAPIError ( 'URI cannot contain options with no value' ) ;
290
287
}
291
288
292
- if ( key . toLowerCase ( ) === 'serverapi' ) {
293
- throw new MongoParseError (
294
- 'URI cannot contain `serverApi`, it can only be passed to the client'
295
- ) ;
296
- }
297
-
298
- if ( key . toLowerCase ( ) === 'authsource' && urlOptions . has ( 'authSource' ) ) {
299
- // If authSource is an explicit key in the urlOptions we need to remove the implicit dbName
300
- urlOptions . delete ( 'authSource' ) ;
301
- }
302
-
303
289
if ( ! urlOptions . has ( key ) ) {
304
290
urlOptions . set ( key , values ) ;
305
291
}
306
292
}
307
293
294
+ if ( urlOptions . has ( 'authSource' ) ) {
295
+ // If authSource is an explicit key in the urlOptions we need to remove the dbName
296
+ urlOptions . delete ( 'dbName' ) ;
297
+ }
298
+
308
299
const objectOptions = new CaseInsensitiveMap (
309
300
Object . entries ( options ) . filter ( ( [ , v ] ) => v != null )
310
301
) ;
311
302
303
+ // Validate options that can only be provided by one of uri or object
304
+
305
+ if ( urlOptions . has ( 'serverApi' ) ) {
306
+ throw new MongoParseError (
307
+ 'URI cannot contain `serverApi`, it can only be passed to the client'
308
+ ) ;
309
+ }
310
+
312
311
if ( objectOptions . has ( 'loadBalanced' ) ) {
313
312
throw new MongoParseError ( 'loadBalanced is only a valid option in the URI' ) ;
314
313
}
315
314
315
+ // All option collection
316
+
316
317
const allOptions = new CaseInsensitiveMap ( ) ;
317
318
318
319
const allKeys = new Set < string > ( [
@@ -360,6 +361,8 @@ export function parseOptions(
360
361
) ;
361
362
}
362
363
364
+ // Option parsing and setting
365
+
363
366
for ( const [ key , descriptor ] of Object . entries ( OPTIONS ) ) {
364
367
const values = allOptions . get ( key ) ;
365
368
if ( ! values || values . length === 0 ) continue ;
@@ -401,33 +404,62 @@ export function parseOptions(
401
404
402
405
if ( options . promiseLibrary ) PromiseProvider . set ( options . promiseLibrary ) ;
403
406
404
- if ( mongoOptions . directConnection && typeof mongoOptions . srvHost === 'string' ) {
405
- throw new MongoAPIError ( 'SRV URI does not support directConnection' ) ;
406
- }
407
-
408
- const lbError = validateLoadBalancedOptions ( hosts , mongoOptions ) ;
407
+ const lbError = validateLoadBalancedOptions ( hosts , mongoOptions , isSRV ) ;
409
408
if ( lbError ) {
410
409
throw lbError ;
411
410
}
411
+ if ( mongoClient && mongoOptions . autoEncryption ) {
412
+ Encrypter . checkForMongoCrypt ( ) ;
413
+ mongoOptions . encrypter = new Encrypter ( mongoClient , uri , options ) ;
414
+ mongoOptions . autoEncrypter = mongoOptions . encrypter . autoEncrypter ;
415
+ }
416
+
417
+ // Potential SRV Overrides and SRV connection string validations
412
418
413
- // Potential SRV Overrides
414
419
mongoOptions . userSpecifiedAuthSource =
415
420
objectOptions . has ( 'authSource' ) || urlOptions . has ( 'authSource' ) ;
416
421
mongoOptions . userSpecifiedReplicaSet =
417
422
objectOptions . has ( 'replicaSet' ) || urlOptions . has ( 'replicaSet' ) ;
418
423
419
- if ( mongoClient && mongoOptions . autoEncryption ) {
420
- Encrypter . checkForMongoCrypt ( ) ;
421
- mongoOptions . encrypter = new Encrypter ( mongoClient , uri , options ) ;
422
- mongoOptions . autoEncrypter = mongoOptions . encrypter . autoEncrypter ;
424
+ if ( isSRV ) {
425
+ // SRV Record is resolved upon connecting
426
+ mongoOptions . srvHost = hosts [ 0 ] ;
427
+
428
+ if ( mongoOptions . directConnection ) {
429
+ throw new MongoAPIError ( 'SRV URI does not support directConnection' ) ;
430
+ }
431
+
432
+ if ( mongoOptions . srvMaxHosts > 0 && typeof mongoOptions . replicaSet === 'string' ) {
433
+ throw new MongoParseError ( 'Cannot use srvMaxHosts option with replicaSet' ) ;
434
+ }
435
+
436
+ // SRV turns on TLS by default, but users can override and turn it off
437
+ const noUserSpecifiedTLS = ! objectOptions . has ( 'tls' ) && ! urlOptions . has ( 'tls' ) ;
438
+ const noUserSpecifiedSSL = ! objectOptions . has ( 'ssl' ) && ! urlOptions . has ( 'ssl' ) ;
439
+ if ( noUserSpecifiedTLS && noUserSpecifiedSSL ) {
440
+ mongoOptions . tls = true ;
441
+ }
442
+ } else {
443
+ const userSpecifiedSrvOptions =
444
+ urlOptions . has ( 'srvMaxHosts' ) ||
445
+ objectOptions . has ( 'srvMaxHosts' ) ||
446
+ urlOptions . has ( 'srvServiceName' ) ||
447
+ objectOptions . has ( 'srvServiceName' ) ;
448
+
449
+ if ( userSpecifiedSrvOptions ) {
450
+ throw new MongoParseError (
451
+ 'Cannot use srvMaxHosts or srvServiceName with a non-srv connection string'
452
+ ) ;
453
+ }
423
454
}
424
455
425
456
return mongoOptions ;
426
457
}
427
458
428
459
function validateLoadBalancedOptions (
429
460
hosts : HostAddress [ ] | string [ ] ,
430
- mongoOptions : MongoOptions
461
+ mongoOptions : MongoOptions ,
462
+ isSrv : boolean
431
463
) : MongoParseError | undefined {
432
464
if ( mongoOptions . loadBalanced ) {
433
465
if ( hosts . length > 1 ) {
@@ -439,6 +471,10 @@ function validateLoadBalancedOptions(
439
471
if ( mongoOptions . directConnection ) {
440
472
return new MongoParseError ( LB_DIRECT_CONNECTION_ERROR ) ;
441
473
}
474
+
475
+ if ( isSrv && mongoOptions . srvMaxHosts > 0 ) {
476
+ return new MongoParseError ( 'Cannot limit srv hosts with loadBalanced enabled' ) ;
477
+ }
442
478
}
443
479
}
444
480
@@ -924,6 +960,14 @@ export const OPTIONS = {
924
960
default : 0 ,
925
961
type : 'uint'
926
962
} ,
963
+ srvMaxHosts : {
964
+ type : 'uint' ,
965
+ default : 0
966
+ } ,
967
+ srvServiceName : {
968
+ type : 'string' ,
969
+ default : 'mongodb'
970
+ } ,
927
971
ssl : {
928
972
target : 'tls' ,
929
973
type : 'boolean'
0 commit comments