Skip to content

Commit 5ca992f

Browse files
authored
RUST-409 Add DriverInfo to client metadata (#175)
1 parent 4ccd448 commit 5ca992f

File tree

5 files changed

+283
-100
lines changed

5 files changed

+283
-100
lines changed

src/client/options/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ pub struct ClientOptions {
243243
#[builder(default)]
244244
pub direct_connection: Option<bool>,
245245

246+
/// Extra information to append to the driver version in the metadata of the handshake with the
247+
/// server. This should be used by libraries wrapping the driver, e.g. ODMs.
248+
#[builder(default)]
249+
pub driver_info: Option<DriverInfo>,
250+
246251
/// The amount of time each monitoring thread should wait between sending an isMaster command
247252
/// to its respective server.
248253
///
@@ -504,6 +509,22 @@ impl TlsOptions {
504509
}
505510
}
506511

512+
/// Extra information to append to the driver version in the metadata of the handshake with the
513+
/// server. This should be used by libraries wrapping the driver, e.g. ODMs.
514+
#[derive(Clone, Debug, TypedBuilder, PartialEq)]
515+
pub struct DriverInfo {
516+
/// The name of the library wrapping the driver.
517+
pub name: String,
518+
519+
/// The version of the library wrapping the driver.
520+
#[builder(default)]
521+
pub version: Option<String>,
522+
523+
/// Optional platform information for the wrapping driver.
524+
#[builder(default)]
525+
pub platform: Option<String>,
526+
}
527+
507528
impl From<ClientOptionsParser> for ClientOptions {
508529
fn from(parser: ClientOptionsParser) -> Self {
509530
Self {
@@ -528,6 +549,7 @@ impl From<ClientOptionsParser> for ClientOptions {
528549
socket_timeout: parser.socket_timeout,
529550
zlib_compression: parser.zlib_compression,
530551
direct_connection: parser.direct_connection,
552+
driver_info: None,
531553
credential: parser.credential,
532554
cmap_event_handler: None,
533555
command_event_handler: None,

src/cmap/establish/handshake.rs

Lines changed: 0 additions & 99 deletions
This file was deleted.

src/cmap/establish/handshake/mod.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#[cfg(test)]
2+
mod test;
3+
4+
use bson::{doc, Bson, Document};
5+
use lazy_static::lazy_static;
6+
use os_info::{Type, Version};
7+
8+
use crate::{
9+
cmap::{options::ConnectionPoolOptions, Command, Connection, StreamDescription},
10+
error::Result,
11+
is_master::IsMasterReply,
12+
};
13+
14+
#[derive(Clone, Debug)]
15+
struct ClientMetadata {
16+
application: Option<AppMetadata>,
17+
driver: DriverMetadata,
18+
os: OsMetadata,
19+
platform: Option<String>,
20+
}
21+
22+
#[derive(Clone, Debug)]
23+
struct AppMetadata {
24+
name: String,
25+
}
26+
27+
#[derive(Clone, Debug)]
28+
struct DriverMetadata {
29+
name: String,
30+
version: String,
31+
}
32+
33+
#[derive(Clone, Debug)]
34+
struct OsMetadata {
35+
os_type: String,
36+
name: Option<String>,
37+
architecture: String,
38+
version: Option<String>,
39+
}
40+
41+
impl From<ClientMetadata> for Bson {
42+
fn from(metadata: ClientMetadata) -> Self {
43+
let mut metadata_doc = Document::new();
44+
45+
if let Some(application) = metadata.application {
46+
metadata_doc.insert("application", doc! { "name": application.name });
47+
}
48+
49+
metadata_doc.insert(
50+
"driver",
51+
doc! {
52+
"name": metadata.driver.name,
53+
"version": metadata.driver.version,
54+
},
55+
);
56+
57+
metadata_doc.insert("os", metadata.os);
58+
59+
if let Some(platform) = metadata.platform {
60+
metadata_doc.insert("platform", platform);
61+
}
62+
63+
Bson::Document(metadata_doc)
64+
}
65+
}
66+
67+
impl From<OsMetadata> for Bson {
68+
fn from(metadata: OsMetadata) -> Self {
69+
let mut doc = doc! { "type": metadata.os_type };
70+
71+
if let Some(name) = metadata.name {
72+
doc.insert("name", name);
73+
}
74+
75+
doc.insert("architecture", metadata.architecture);
76+
77+
if let Some(version) = metadata.version {
78+
doc.insert("version", version);
79+
}
80+
81+
Bson::Document(doc)
82+
}
83+
}
84+
85+
lazy_static! {
86+
/// Contains the basic handshake information that can be statically determined. This document
87+
/// (potentially with additional fields added) can be cloned and put in the `client` field of
88+
/// the `isMaster` command.
89+
static ref BASE_CLIENT_METADATA: ClientMetadata = {
90+
let mut metadata = ClientMetadata {
91+
application: None,
92+
driver: DriverMetadata {
93+
name: "mongo-rust-driver".into(),
94+
version: env!("CARGO_PKG_VERSION").into(),
95+
},
96+
os: OsMetadata {
97+
os_type: std::env::consts::OS.into(),
98+
architecture: std::env::consts::ARCH.into(),
99+
name: None,
100+
version: None,
101+
},
102+
platform: None,
103+
};
104+
105+
let info = os_info::get();
106+
107+
if info.os_type() != Type::Unknown {
108+
let version = info.version();
109+
110+
if *version != Version::unknown() {
111+
metadata.os.version = Some(info.version().to_string());
112+
}
113+
}
114+
115+
if let Some((version, channel, date)) = version_check::triple() {
116+
metadata.platform =
117+
Some(format!("rustc {} {} ({})", version, channel, date));
118+
}
119+
120+
metadata
121+
};
122+
}
123+
124+
/// Contains the logic needed to handshake a connection.
125+
#[derive(Debug, Clone)]
126+
pub(super) struct Handshaker {
127+
/// The `isMaster` command to send when handshaking. This will always be identical
128+
/// given the same pool options, so it can be created at the time the Handshaker is created.
129+
command: Command,
130+
}
131+
132+
impl Handshaker {
133+
/// Creates a new Handshaker.
134+
pub(super) fn new(options: Option<&ConnectionPoolOptions>) -> Self {
135+
let mut metadata = BASE_CLIENT_METADATA.clone();
136+
137+
if let Some(app_name) = options.as_ref().and_then(|opts| opts.app_name.as_ref()) {
138+
metadata.application = Some(AppMetadata {
139+
name: app_name.to_string(),
140+
});
141+
}
142+
143+
if let Some(driver_info) = options.as_ref().and_then(|opts| opts.driver_info.as_ref()) {
144+
metadata.driver.name.push('|');
145+
metadata.driver.name.push_str(&driver_info.name);
146+
147+
if let Some(ref version) = driver_info.version {
148+
metadata.driver.version.push('|');
149+
metadata.driver.version.push_str(version);
150+
}
151+
152+
if let Some(ref mut platform) = metadata.platform {
153+
if let Some(ref driver_info_platform) = driver_info.platform {
154+
metadata.driver.version.push('|');
155+
metadata.driver.version.push_str(driver_info_platform);
156+
}
157+
}
158+
}
159+
160+
let mut db = "admin";
161+
162+
let mut body = doc! {
163+
"isMaster": 1,
164+
"client": metadata,
165+
};
166+
167+
if let Some(credential) = options.as_ref().and_then(|opts| opts.credential.as_ref()) {
168+
credential.append_needed_mechanism_negotiation(&mut body);
169+
db = credential.resolved_source();
170+
}
171+
172+
Self {
173+
command: Command::new_read("isMaster".to_string(), db.to_string(), None, body),
174+
}
175+
}
176+
177+
/// Handshakes a connection.
178+
pub(super) async fn handshake(&self, conn: &mut Connection) -> Result<()> {
179+
let response = conn.send_command(self.command.clone(), None).await?;
180+
let command_response = response.body()?;
181+
182+
// TODO RUST-192: Calculate round trip time.
183+
let is_master_reply = IsMasterReply {
184+
command_response,
185+
round_trip_time: None,
186+
cluster_time: None,
187+
};
188+
189+
conn.stream_description = Some(StreamDescription::from_is_master(is_master_reply));
190+
Ok(())
191+
}
192+
}

src/cmap/establish/handshake/test.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use bson::doc;
2+
3+
use super::Handshaker;
4+
use crate::{cmap::options::ConnectionPoolOptions, options::DriverInfo};
5+
6+
#[test]
7+
fn metadata_no_options() {
8+
let handshaker = Handshaker::new(None);
9+
10+
let metadata = handshaker.command.body.get_document("client").unwrap();
11+
assert!(!metadata.contains_key("application"));
12+
13+
let driver = metadata.get_document("driver").unwrap();
14+
assert_eq!(driver.keys().collect::<Vec<_>>(), vec!["name", "version"]);
15+
assert_eq!(driver.get_str("name"), Ok("mongo-rust-driver"));
16+
assert_eq!(driver.get_str("version"), Ok(env!("CARGO_PKG_VERSION")));
17+
18+
let os = metadata.get_document("os").unwrap();
19+
assert_eq!(os.get_str("type"), Ok(std::env::consts::OS));
20+
assert_eq!(os.get_str("architecture"), Ok(std::env::consts::ARCH));
21+
}
22+
23+
#[test]
24+
fn metadata_with_options() {
25+
let app_name = "myspace 2.0";
26+
let name = "even better Rust driver";
27+
let version = "the best version, of course";
28+
29+
let options = ConnectionPoolOptions::builder()
30+
.app_name(app_name.to_string())
31+
.driver_info(
32+
DriverInfo::builder()
33+
.name(name.to_string())
34+
.version(version.to_string())
35+
.build(),
36+
)
37+
.build();
38+
39+
let handshaker = Handshaker::new(Some(&options));
40+
41+
let metadata = handshaker.command.body.get_document("client").unwrap();
42+
assert_eq!(
43+
metadata.get_document("application"),
44+
Ok(&doc! { "name": app_name })
45+
);
46+
47+
let driver = metadata.get_document("driver").unwrap();
48+
assert_eq!(driver.keys().collect::<Vec<_>>(), vec!["name", "version"]);
49+
assert_eq!(
50+
driver.get_str("name"),
51+
Ok(format!("mongo-rust-driver|{}", name).as_str())
52+
);
53+
assert_eq!(
54+
driver.get_str("version"),
55+
Ok(format!("{}|{}", env!("CARGO_PKG_VERSION"), version).as_str())
56+
);
57+
58+
let os = metadata.get_document("os").unwrap();
59+
assert_eq!(os.get_str("type"), Ok(std::env::consts::OS));
60+
assert_eq!(os.get_str("architecture"), Ok(std::env::consts::ARCH));
61+
}

0 commit comments

Comments
 (0)