Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/action/gridfs/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ impl GridFsBucket {
} else {
(-1, -revision - 1)
};
// unwrap safety: `skip` is always >= 0
let skip: u64 = skip.try_into().unwrap();

match self
.files()
.find_one(doc! { "filename": filename })
.sort(doc! { "uploadDate": sort })
.skip(skip as u64)
.skip(skip)
.await?
{
Some(fcd) => Ok(fcd),
Expand Down
52 changes: 51 additions & 1 deletion src/bson_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,39 @@ pub(crate) fn get_int_raw(val: RawBsonRef<'_>) -> Option<i64> {
}
}

#[allow(private_bounds)]
pub(crate) fn round_clamp<T: RoundClampTarget>(input: f64) -> T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it this way allows the type to be specified inline with the call rather than the awkwardness that Into and friends have.

T::round_clamp(input)
}

trait RoundClampTarget {
fn round_clamp(input: f64) -> Self;
}

impl RoundClampTarget for u64 {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn round_clamp(input: f64) -> Self {
input as u64
}
}

impl RoundClampTarget for u32 {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn round_clamp(input: f64) -> Self {
input as u32
}
}

/// Coerce numeric types into an `u64` if it would be lossless to do so. If this Bson is not numeric
/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`.
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn get_u64(val: &Bson) -> Option<u64> {
match *val {
Bson::Int32(i) => u64::try_from(i).ok(),
Bson::Int64(i) => u64::try_from(i).ok(),
Bson::Double(f) if (f - (f as u64 as f64)).abs() <= f64::EPSILON => Some(f as u64),
Bson::Double(f) if (f - (round_clamp::<u64>(f) as f64)).abs() <= f64::EPSILON => {
Some(round_clamp(f))
}
_ => None,
}
}
Expand Down Expand Up @@ -291,6 +316,31 @@ impl RawDocumentCollection for RawArrayBuf {
}
}

pub(crate) mod option_u64_as_i64 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be just bson::serde_helpers::u64::AsI64 but we can't rely on bson 3.0 stuff :(

use serde::{Deserialize, Serialize};

pub(crate) fn serialize<S: serde::Serializer>(
value: &Option<u64>,
s: S,
) -> std::result::Result<S::Ok, S::Error> {
let conv: Option<i64> = value
.as_ref()
.map(|&u| u.try_into())
.transpose()
.map_err(serde::ser::Error::custom)?;
conv.serialize(s)
}

pub(crate) fn deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> std::result::Result<Option<u64>, D::Error> {
let conv = Option::<i64>::deserialize(d)?;
conv.map(|i| i.try_into())
.transpose()
.map_err(serde::de::Error::custom)
}
}

#[cfg(test)]
mod test {
use crate::bson_util::num_decimal_digits;
Expand Down
5 changes: 4 additions & 1 deletion src/client/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1952,7 +1952,10 @@ impl ConnectionString {
// -1 maxStaleness means no maxStaleness, which is the default
return Ok(());
}
Ordering::Greater => Duration::from_secs(max_staleness_seconds as u64),
Ordering::Greater => {
// unwrap safety: `max_staleness_seconds` will always be >= 0
Duration::from_secs(max_staleness_seconds.try_into().unwrap())
}
};

parts.max_staleness = Some(max_staleness);
Expand Down
2 changes: 1 addition & 1 deletion src/cmap/conn/stream_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl StreamDescription {
logical_session_timeout: reply
.command_response
.logical_session_timeout_minutes
.map(|mins| Duration::from_secs(mins as u64 * 60)),
.map(|mins| Duration::from_secs(mins * 60)),
max_bson_object_size: reply.command_response.max_bson_object_size,
// The defaulting to 100,000 is here because mongocryptd doesn't include this field in
// hello replies; this should never happen when talking to a real server.
Expand Down
3 changes: 2 additions & 1 deletion src/cmap/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ impl<'de> Deserialize<'de> for BackgroundThreadInterval {
Ordering::Less => BackgroundThreadInterval::Never,
Ordering::Equal => return Err(D::Error::custom("zero is not allowed")),
Ordering::Greater => {
BackgroundThreadInterval::Every(Duration::from_millis(millis as u64))
// unwrap safety: millis is validated to be in the u64 range
BackgroundThreadInterval::Every(Duration::from_millis(millis.try_into().unwrap()))
}
})
}
Expand Down
3 changes: 2 additions & 1 deletion src/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ pub(crate) struct HelloCommandResponse {
pub is_replica_set: Option<bool>,

/// The time in minutes that a session remains active after its most recent use.
pub logical_session_timeout_minutes: Option<i64>,
#[serde(default, with = "crate::bson_util::option_u64_as_i64")]
pub logical_session_timeout_minutes: Option<u64>,

/// Optime and date information for the server's most recent write operation.
pub last_write: Option<LastWrite>,
Expand Down
12 changes: 9 additions & 3 deletions src/index/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ impl<'de> Deserialize<'de> for IndexVersion {
0 => Ok(IndexVersion::V0),
1 => Ok(IndexVersion::V1),
2 => Ok(IndexVersion::V2),
i => Ok(IndexVersion::Custom(i as u32)),
i => Ok(IndexVersion::Custom(
i.try_into().map_err(serde::de::Error::custom)?,
)),
}
}
}
Expand Down Expand Up @@ -213,7 +215,9 @@ impl<'de> Deserialize<'de> for TextIndexVersion {
1 => Ok(TextIndexVersion::V1),
2 => Ok(TextIndexVersion::V2),
3 => Ok(TextIndexVersion::V3),
i => Ok(TextIndexVersion::Custom(i as u32)),
i => Ok(TextIndexVersion::Custom(
i.try_into().map_err(serde::de::Error::custom)?,
)),
}
}
}
Expand Down Expand Up @@ -253,7 +257,9 @@ impl<'de> Deserialize<'de> for Sphere2DIndexVersion {
match i32::deserialize(deserializer)? {
2 => Ok(Sphere2DIndexVersion::V2),
3 => Ok(Sphere2DIndexVersion::V3),
i => Ok(Sphere2DIndexVersion::Custom(i as u32)),
i => Ok(Sphere2DIndexVersion::Custom(
i.try_into().map_err(serde::de::Error::custom)?,
)),
}
}
}
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
#![warn(clippy::cast_possible_truncation)]
#![warn(clippy::cast_possible_wrap)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
#![allow(
clippy::unreadable_literal,
clippy::cognitive_complexity,
Expand Down
2 changes: 1 addition & 1 deletion src/sdam/description/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl ServerDescription {
Ok(Some(ref reply)) => Ok(reply
.command_response
.logical_session_timeout_minutes
.map(|timeout| Duration::from_secs(timeout as u64 * 60))),
.map(|timeout| Duration::from_secs(timeout * 60))),
Err(ref e) => Err(e.clone()),
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{collections::HashMap, sync::Arc, time::Duration};

use crate::bson::{doc, Document};
use crate::{
bson::{doc, Document},
bson_util::round_clamp,
};
use approx::abs_diff_eq;
use serde::Deserialize;

Expand Down Expand Up @@ -188,14 +191,14 @@ async fn load_balancing_test() {
assert!(
share_of_selections <= max_share,
"expected no more than {}% of selections, instead got {}%",
(max_share * 100.0) as u32,
(share_of_selections * 100.0) as u32
round_clamp::<u32>(max_share * 100.0),
round_clamp::<u32>(share_of_selections * 100.0)
);
assert!(
share_of_selections >= min_share,
"expected at least {}% of selections, instead got {}%",
(min_share * 100.0) as u32,
(share_of_selections * 100.0) as u32
round_clamp::<u32>(min_share * 100.0),
round_clamp::<u32>(share_of_selections * 100.0)
);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/sdam/description/topology/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::time::Duration;

pub use event::TestSdamEvent;

#[allow(clippy::cast_possible_truncation)]
use crate::bson_util::round_clamp;

pub(crate) fn f64_ms_as_duration(f: f64) -> Duration {
Duration::from_micros((f * 1000.0) as u64)
Duration::from_micros(round_clamp(f * 1000.0))
}
15 changes: 8 additions & 7 deletions src/sdam/description/topology/test/sdam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ pub(crate) struct TestHelloCommandResponse {
pub arbiter_only: Option<bool>,
#[serde(rename = "isreplicaset")]
pub is_replica_set: Option<bool>,
pub logical_session_timeout_minutes: Option<i64>,
#[serde(default, with = "crate::bson_util::option_u64_as_i64")]
pub logical_session_timeout_minutes: Option<u64>,
pub last_write: Option<LastWrite>,
pub min_wire_version: Option<i32>,
pub max_wire_version: Option<i32>,
Expand Down Expand Up @@ -202,7 +203,8 @@ pub struct DescriptionOutcome {
topology_type: TopologyType,
set_name: Option<String>,
servers: HashMap<String, Server>,
logical_session_timeout_minutes: Option<i32>,
#[serde(default, with = "crate::bson_util::option_u64_as_i64")]
logical_session_timeout_minutes: Option<u64>,
compatible: Option<bool>,
}

Expand All @@ -219,7 +221,8 @@ pub struct Server {
set_name: Option<String>,
set_version: Option<i32>,
election_id: Option<ObjectId>,
logical_session_timeout_minutes: Option<i32>,
#[serde(default, with = "crate::bson_util::option_u64_as_i64")]
logical_session_timeout_minutes: Option<u64>,
min_wire_version: Option<i32>,
max_wire_version: Option<i32>,
topology_version: Option<TopologyVersion>,
Expand Down Expand Up @@ -417,7 +420,7 @@ fn verify_description_outcome(

let expected_timeout = outcome
.logical_session_timeout_minutes
.map(|mins| Duration::from_secs((mins as u64) * 60));
.map(|mins| Duration::from_secs(mins * 60));
assert_eq!(
topology_description.logical_session_timeout, expected_timeout,
"{test_description}: {phase_description}"
Expand Down Expand Up @@ -475,9 +478,7 @@ fn verify_description_outcome(
if let Some(logical_session_timeout_minutes) = server.logical_session_timeout_minutes {
assert_eq!(
actual_server.logical_session_timeout().unwrap(),
Some(Duration::from_secs(
logical_session_timeout_minutes as u64 * 60
)),
Some(Duration::from_secs(logical_session_timeout_minutes * 60)),
"{test_description} (phase {phase_description})"
);
}
Expand Down
18 changes: 10 additions & 8 deletions src/test/bulk_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ async fn max_write_batch_size_batching() {
let models = vec![model; max_write_batch_size + 1];

let result = client.bulk_write(models).await.unwrap();
assert_eq!(result.inserted_count as usize, max_write_batch_size + 1);
let inserted_count: usize = result.inserted_count.try_into().unwrap();
assert_eq!(inserted_count, max_write_batch_size + 1);

let mut command_started_events = client
.events
Expand Down Expand Up @@ -105,7 +106,8 @@ async fn max_message_size_bytes_batching() {
let models = vec![model; num_models];

let result = client.bulk_write(models).await.unwrap();
assert_eq!(result.inserted_count as usize, num_models);
let inserted_count: usize = result.inserted_count.try_into().unwrap();
assert_eq!(inserted_count, num_models);

let mut command_started_events = client
.events
Expand Down Expand Up @@ -162,10 +164,8 @@ async fn write_concern_error_batches() {
assert_eq!(bulk_write_error.write_concern_errors.len(), 2);

let partial_result = bulk_write_error.partial_result.unwrap();
assert_eq!(
partial_result.inserted_count() as usize,
max_write_batch_size + 1
);
let inserted_count: usize = partial_result.inserted_count().try_into().unwrap();
assert_eq!(inserted_count, max_write_batch_size + 1);

let command_started_events = client.events.get_command_started_events(&["bulkWrite"]);
assert_eq!(command_started_events.len(), 2);
Expand Down Expand Up @@ -428,7 +428,8 @@ async fn namespace_batch_splitting() {
let num_models = first_models.len();

let result = client.bulk_write(first_models).await.unwrap();
assert_eq!(result.inserted_count as usize, num_models);
let inserted_count: usize = result.inserted_count.try_into().unwrap();
assert_eq!(inserted_count, num_models);

let command_started_events = client.events.get_command_started_events(&["bulkWrite"]);
assert_eq!(command_started_events.len(), 1);
Expand Down Expand Up @@ -459,7 +460,8 @@ async fn namespace_batch_splitting() {
let num_models = second_models.len();

let result = client.bulk_write(second_models).await.unwrap();
assert_eq!(result.inserted_count as usize, num_models);
let inserted_count: usize = result.inserted_count.try_into().unwrap();
assert_eq!(inserted_count, num_models);

let command_started_events = client.events.get_command_started_events(&["bulkWrite"]);
assert_eq!(command_started_events.len(), 2);
Expand Down
3 changes: 2 additions & 1 deletion src/test/coll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1314,5 +1314,6 @@ async fn aggregate_with_generics() {
.await
.unwrap();
let lens: Vec<B> = cursor.try_collect().await.unwrap();
assert_eq!(lens[0].len as usize, len);
let first_len: usize = lens[0].len.try_into().unwrap();
assert_eq!(first_len, len);
}