Skip to content

Commit 1e17849

Browse files
RUST-333 Improve invalid URI option error message (#167)
1 parent f843e6c commit 1e17849

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ serde_with = "1.3.1"
3737
sha-1 = "0.8.1"
3838
sha2 = "0.8.0"
3939
stringprep = "0.1.2"
40+
strsim = "0.10.0"
4041
time = "0.1.42"
4142
trust-dns-proto = "0.19.4"
4243
trust-dns-resolver = "0.19.0"

src/client/options/mod.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustls::{
2323
ServerCertVerifier,
2424
TLSError,
2525
};
26+
use strsim::jaro_winkler;
2627
use typed_builder::TypedBuilder;
2728
use webpki_roots::TLS_SERVER_ROOTS;
2829

@@ -38,6 +39,41 @@ use crate::{
3839

3940
const DEFAULT_PORT: u16 = 27017;
4041

42+
const URI_OPTIONS: &[&str] = &[
43+
"appname",
44+
"authmechanism",
45+
"authsource",
46+
"authmechanismproperties",
47+
"compressors",
48+
"connecttimeoutms",
49+
"directconnection",
50+
"heartbeatfrequencyms",
51+
"journal",
52+
"localthresholdms",
53+
"maxidletimems",
54+
"maxstalenessseconds",
55+
"maxpoolsize",
56+
"minpoolsize",
57+
"readconcernlevel",
58+
"readpreference",
59+
"readpreferencetags",
60+
"replicaset",
61+
"retrywrites",
62+
"retryreads",
63+
"serverselectiontimeoutms",
64+
"sockettimeoutms",
65+
"tls",
66+
"ssl",
67+
"tlsinsecure",
68+
"tlsallowinvalidcertificates",
69+
"tlscafile",
70+
"tlscertificatekeyfile",
71+
"w",
72+
"waitqueuetimeoutms",
73+
"wtimeoutms",
74+
"zlibcompressionlevel",
75+
];
76+
4177
lazy_static! {
4278
/// Reserved characters as defined by [Section 2.2 of RFC-3986](https://tools.ietf.org/html/rfc3986#section-2.2).
4379
/// Usernames / passwords that contain these characters must instead include the URL encoded version of them when included
@@ -1393,11 +1429,22 @@ impl ClientOptionsParser {
13931429
self.zlib_compression = Some(i);
13941430
}
13951431

1396-
_ => {
1397-
return Err(ErrorKind::ArgumentError {
1398-
message: "invalid option warning".to_string(),
1432+
other => {
1433+
let (jaro_winkler, option) = URI_OPTIONS.iter().fold((0.0, ""), |acc, option| {
1434+
let jaro_winkler = jaro_winkler(option, other).abs();
1435+
if jaro_winkler > acc.0 {
1436+
return (jaro_winkler, option);
1437+
}
1438+
acc
1439+
});
1440+
let mut message = format!("{} is an invalid option", other);
1441+
if jaro_winkler >= 0.84 {
1442+
message.push_str(&format!(
1443+
". An option with a similar name exists: {}",
1444+
option
1445+
));
13991446
}
1400-
.into());
1447+
return Err(ErrorKind::ArgumentError { message: message }.into());
14011448
}
14021449
}
14031450

src/client/options/test.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pretty_assertions::assert_eq;
33
use serde::Deserialize;
44

55
use crate::{
6-
client::options::{ClientOptions, StreamAddress},
6+
client::options::{ClientOptions, ClientOptionsParser, StreamAddress},
77
error::ErrorKind,
88
selection_criteria::{ReadPreference, SelectionCriteria},
99
test::run_spec_test,
@@ -332,3 +332,37 @@ async fn run_uri_options_spec_tests() {
332332
async fn run_connection_string_spec_tests() {
333333
run_spec_test(&["connection-string"], run_test).await;
334334
}
335+
336+
async fn parse_uri(option: &str, suggestion: Option<&str>) {
337+
match ClientOptionsParser::parse(&format!("mongodb://host:27017/?{}=test", option))
338+
.as_ref()
339+
.map_err(|e| e.as_ref())
340+
{
341+
Ok(_) => panic!("expected error for option {}", option),
342+
Err(ErrorKind::ArgumentError { message, .. }) => {
343+
match suggestion {
344+
Some(s) => assert!(message.contains(s)),
345+
None => assert!(!message.contains("similar")),
346+
};
347+
}
348+
Err(e) => panic!("expected ArgumentError, but got {:?}", e),
349+
}
350+
}
351+
352+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
353+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
354+
async fn parse_unknown_options() {
355+
parse_uri("invalidoption", None).await;
356+
parse_uri("x", None).await;
357+
parse_uri("max", None).await;
358+
parse_uri("tlstimeout", None).await;
359+
parse_uri("waitqueuetimeout", Some("waitqueuetimeoutms")).await;
360+
parse_uri("retry_reads", Some("retryreads")).await;
361+
parse_uri("poolsize", Some("maxpoolsize")).await;
362+
parse_uri(
363+
"tlspermitinvalidcertificates",
364+
Some("tlsallowinvalidcertificates"),
365+
)
366+
.await;
367+
parse_uri("maxstalenessms", Some("maxstalenessseconds")).await;
368+
}

0 commit comments

Comments
 (0)