Skip to content

Commit 5aa29c2

Browse files
authored
RUST-740 Return Deserialize structs from list_databases and list_collections (#328)
1 parent f9b1aa4 commit 5aa29c2

File tree

13 files changed

+209
-88
lines changed

13 files changed

+209
-88
lines changed

src/bson_util/mod.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
pub(crate) mod async_encoding;
22

3-
use std::time::Duration;
3+
use std::{convert::TryFrom, time::Duration};
44

5-
use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
5+
use serde::{de::Error, ser, Deserialize, Deserializer, Serialize, Serializer};
66

77
use crate::{
88
bson::{doc, oid::ObjectId, Binary, Bson, Document, JavaScriptCodeWithScope, Regex},
@@ -15,7 +15,18 @@ pub(crate) fn get_int(val: &Bson) -> Option<i64> {
1515
match *val {
1616
Bson::Int32(i) => Some(i64::from(i)),
1717
Bson::Int64(i) => Some(i),
18-
Bson::Double(f) if f == f as i64 as f64 => Some(f as i64),
18+
Bson::Double(f) if (f - (f as i64 as f64)).abs() <= f64::EPSILON => Some(f as i64),
19+
_ => None,
20+
}
21+
}
22+
23+
/// Coerce numeric types into an `u64` if it would be lossless to do so. If this Bson is not numeric
24+
/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`.
25+
pub(crate) fn get_u64(val: &Bson) -> Option<u64> {
26+
match *val {
27+
Bson::Int32(i) => u64::try_from(i).ok(),
28+
Bson::Int64(i) => u64::try_from(i).ok(),
29+
Bson::Double(f) if (f - (f as u64 as f64)).abs() <= f64::EPSILON => Some(f as u64),
1930
_ => None,
2031
}
2132
}
@@ -125,6 +136,18 @@ pub(crate) fn serialize_batch_size<S: Serializer>(
125136
}
126137
}
127138

139+
/// Deserialize an u64 from any BSON number type if it could be done losslessly.
140+
pub(crate) fn deserialize_u64_from_bson_number<'de, D>(
141+
deserializer: D,
142+
) -> std::result::Result<u64, D::Error>
143+
where
144+
D: Deserializer<'de>,
145+
{
146+
let bson = Bson::deserialize(deserializer)?;
147+
get_u64(&bson)
148+
.ok_or_else(|| D::Error::custom(format!("could not deserialize u64 from {:?}", bson)))
149+
}
150+
128151
pub fn doc_size_bytes(doc: &Document) -> usize {
129152
//
130153
// * i32 length prefix (4 bytes)

src/client/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ pub mod session;
55

66
use std::{sync::Arc, time::Duration};
77

8+
use bson::Bson;
89
use derivative::Derivative;
910
use std::time::Instant;
1011

1112
#[cfg(test)]
1213
use crate::options::StreamAddress;
1314
use crate::{
14-
bson::{Bson, Document},
15+
bson::Document,
1516
concern::{ReadConcern, WriteConcern},
1617
db::Database,
1718
error::{ErrorKind, Result},
@@ -25,6 +26,7 @@ use crate::{
2526
SelectionCriteria,
2627
SessionOptions,
2728
},
29+
results::DatabaseSpecification,
2830
sdam::{SelectedServer, SessionSupportStatus, Topology},
2931
ClientSession,
3032
};
@@ -161,9 +163,13 @@ impl Client {
161163
&self,
162164
filter: impl Into<Option<Document>>,
163165
options: impl Into<Option<ListDatabasesOptions>>,
164-
) -> Result<Vec<Document>> {
166+
) -> Result<Vec<DatabaseSpecification>> {
165167
let op = ListDatabases::new(filter.into(), false, options.into());
166-
self.execute_operation(op, None).await
168+
self.execute_operation(op, None).await.and_then(|dbs| {
169+
dbs.into_iter()
170+
.map(|db_spec| bson::from_document(db_spec).map_err(crate::error::Error::from))
171+
.collect()
172+
})
167173
}
168174

169175
/// Gets the names of the databases present in the cluster the Client is connected to.

src/db/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919
DropDatabaseOptions,
2020
ListCollectionsOptions,
2121
},
22+
results::CollectionSpecification,
2223
selection_criteria::SelectionCriteria,
2324
Client,
2425
ClientSession,
@@ -194,7 +195,7 @@ impl Database {
194195
&self,
195196
filter: impl Into<Option<Document>>,
196197
options: impl Into<Option<ListCollectionsOptions>>,
197-
) -> Result<Cursor<Document>> {
198+
) -> Result<Cursor<CollectionSpecification>> {
198199
let list_collections = ListCollections::new(
199200
self.name().to_string(),
200201
filter.into(),
@@ -215,7 +216,7 @@ impl Database {
215216
filter: impl Into<Option<Document>>,
216217
options: impl Into<Option<ListCollectionsOptions>>,
217218
session: &mut ClientSession,
218-
) -> Result<SessionCursor<Document>> {
219+
) -> Result<SessionCursor<CollectionSpecification>> {
219220
let list_collections = ListCollections::new(
220221
self.name().to_string(),
221222
filter.into(),

src/db/options.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct DatabaseOptions {
2929
/// These are the valid options for creating a collection with
3030
/// [`Database::create_collection`](../struct.Database.html#method.create_collection).
3131
#[skip_serializing_none]
32-
#[derive(Debug, Default, TypedBuilder, Serialize)]
32+
#[derive(Clone, Debug, Default, TypedBuilder, Serialize, Deserialize)]
3333
#[serde(rename_all = "camelCase")]
3434
#[builder(field_defaults(default, setter(strip_option)))]
3535
#[non_exhaustive]
@@ -55,8 +55,7 @@ pub struct CreateCollectionOptions {
5555
/// Specifies a validator to restrict the schema of documents which can exist in the
5656
/// collection. Expressions can be specified using any query operators except `$near`,
5757
/// `$nearSphere`, `$text`, and `$where`.
58-
#[serde(rename = "validator")]
59-
pub validation: Option<Document>,
58+
pub validator: Option<Document>,
6059

6160
/// Specifies how strictly the database should apply the validation rules to existing documents
6261
/// during an update.
@@ -86,7 +85,7 @@ pub struct CreateCollectionOptions {
8685

8786
/// Specifies how strictly the database should apply validation rules to existing documents during
8887
/// an update.
89-
#[derive(Debug, Serialize)]
88+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
9089
#[serde(rename_all = "camelCase")]
9190
#[non_exhaustive]
9291
pub enum ValidationLevel {
@@ -101,7 +100,7 @@ pub enum ValidationLevel {
101100

102101
/// Specifies whether the database should return an error or simply raise a warning if inserted
103102
/// documents do not pass the validation.
104-
#[derive(Debug, Serialize)]
103+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
105104
#[serde(rename_all = "camelCase")]
106105
#[non_exhaustive]
107106
pub enum ValidationAction {
@@ -112,8 +111,9 @@ pub enum ValidationAction {
112111
}
113112

114113
/// Specifies default configuration for indexes created on a collection, including the _id index.
115-
#[derive(Clone, Debug, PartialEq, Serialize)]
114+
#[derive(Clone, Debug, TypedBuilder, PartialEq, Serialize, Deserialize)]
116115
#[serde(rename_all = "camelCase")]
116+
#[non_exhaustive]
117117
pub struct IndexOptionDefaults {
118118
/// The `storageEngine` document should be in the following form:
119119
///

src/operation/create/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async fn build_validator() {
5353
coll: "test_coll".to_string(),
5454
},
5555
Some(CreateCollectionOptions {
56-
validation: Some(query.clone()),
56+
validator: Some(query.clone()),
5757
..Default::default()
5858
}),
5959
);

src/operation/list_databases/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,4 @@ impl Operation for ListDatabases {
8585
#[derive(Debug, Deserialize)]
8686
struct ResponseBody {
8787
databases: Vec<Document>,
88-
total_size: Option<i64>,
8988
}

src/results.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
33
use std::collections::{HashMap, VecDeque};
44

5-
use crate::bson::{Bson, Document};
5+
use crate::{bson::{Bson, Document}, db::options::CreateCollectionOptions};
66

7-
use serde::Serialize;
7+
use bson::Binary;
8+
use serde::{Deserialize, Serialize};
89

910
/// The result of a [`Collection::insert_one`](../struct.Collection.html#method.insert_one)
1011
/// operation.
@@ -71,3 +72,77 @@ pub(crate) struct GetMoreResult {
7172
pub(crate) batch: VecDeque<Document>,
7273
pub(crate) exhausted: bool,
7374
}
75+
76+
/// Describes the type of data store returned when executing
77+
/// [`Database::list_collections`](../struct.Database.html#method.list_collections).
78+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79+
#[serde(rename_all = "camelCase")]
80+
#[non_exhaustive]
81+
pub enum CollectionType {
82+
/// Indicates that the data store is a view.
83+
View,
84+
85+
/// Indicates that the data store is a collection.
86+
Collection,
87+
}
88+
89+
/// Info about the collection that is contained in the `CollectionSpecification::info` field of a specification returned from
90+
/// [`Database::list_collections`](../struct.Database.html#method.list_collections).
91+
///
92+
/// See the MongoDB [manual](https://docs.mongodb.com/manual/reference/command/listCollections/#listCollections.cursor)
93+
/// for more information.
94+
#[derive(Debug, Clone, Deserialize, Serialize)]
95+
#[serde(rename_all = "camelCase")]
96+
#[non_exhaustive]
97+
pub struct CollectionSpecificationInfo {
98+
/// Indicates whether or not the data store is read-only.
99+
pub read_only: bool,
100+
101+
/// The collection's UUID - once established, this does not change and remains the same across replica
102+
/// set members and shards in a sharded cluster. If the data store is a view, this field is `None`.
103+
pub uuid: Option<Binary>,
104+
}
105+
106+
/// Information about a collection as reported by [`Database::list_collections`](../struct.Database.html#method.list_collections).
107+
#[derive(Debug, Clone, Deserialize, Serialize)]
108+
#[serde(rename_all = "camelCase")]
109+
#[non_exhaustive]
110+
pub struct CollectionSpecification {
111+
/// The name of the collection.
112+
pub name: String,
113+
114+
/// Type of the data store.
115+
#[serde(rename="type")]
116+
pub collection_type: CollectionType,
117+
118+
/// The options used to create the collection.
119+
pub options: CreateCollectionOptions,
120+
121+
/// Additional info pertaining to the collection.
122+
pub info: CollectionSpecificationInfo,
123+
124+
/// Provides information on the _id index for the collection
125+
/// For views, this is `None`.
126+
pub id_index: Option<Document>,
127+
}
128+
129+
/// A struct modeling the information about an individual database returned from
130+
/// [`Client::list_databases`](../struct.Client.html#method.list_databases).
131+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
132+
#[serde(rename_all = "camelCase")]
133+
#[non_exhaustive]
134+
pub struct DatabaseSpecification {
135+
/// The name of the database.
136+
pub name: String,
137+
138+
/// The amount of disk space in bytes that is consumed by the database.
139+
#[serde(deserialize_with = "crate::bson_util::deserialize_u64_from_bson_number")]
140+
pub size_on_disk: u64,
141+
142+
/// Whether the database has any data.
143+
pub empty: bool,
144+
145+
/// For sharded clusters, this field includes a document which maps each shard to the size in bytes of the database
146+
/// on disk on that shard. For non sharded environments, this field is `None`.
147+
pub shards: Option<Document>,
148+
}

src/sync/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
SelectionCriteria,
1111
SessionOptions,
1212
},
13+
results::DatabaseSpecification,
1314
Client as AsyncClient,
1415
ClientSession,
1516
RUNTIME,
@@ -116,7 +117,7 @@ impl Client {
116117
&self,
117118
filter: impl Into<Option<Document>>,
118119
options: impl Into<Option<ListDatabasesOptions>>,
119-
) -> Result<Vec<Document>> {
120+
) -> Result<Vec<DatabaseSpecification>> {
120121
RUNTIME.block_on(
121122
self.async_client
122123
.list_databases(filter.into(), options.into()),

src/sync/db.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::{
2020
SelectionCriteria,
2121
WriteConcern,
2222
},
23+
results::CollectionSpecification,
2324
Database as AsyncDatabase,
2425
RUNTIME,
2526
};
@@ -140,7 +141,7 @@ impl Database {
140141
&self,
141142
filter: impl Into<Option<Document>>,
142143
options: impl Into<Option<ListCollectionsOptions>>,
143-
) -> Result<Cursor<Document>> {
144+
) -> Result<Cursor<CollectionSpecification>> {
144145
RUNTIME
145146
.block_on(
146147
self.async_database
@@ -157,7 +158,7 @@ impl Database {
157158
filter: impl Into<Option<Document>>,
158159
options: impl Into<Option<ListCollectionsOptions>>,
159160
session: &mut ClientSession,
160-
) -> Result<SessionCursor<Document>> {
161+
) -> Result<SessionCursor<CollectionSpecification>> {
161162
RUNTIME
162163
.block_on(self.async_database.list_collections_with_session(
163164
filter.into(),

src/test/client.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,7 @@ async fn list_databases() {
161161
let prev_dbs = client.list_databases(None, None).await.unwrap();
162162

163163
for name in expected_dbs {
164-
assert!(!prev_dbs
165-
.iter()
166-
.any(|doc| doc.get("name") == Some(&Bson::String(name.to_string()))));
164+
assert!(!prev_dbs.iter().any(|doc| doc.name.as_str() == name));
167165

168166
let db = client.database(name);
169167

@@ -176,20 +174,17 @@ async fn list_databases() {
176174
let new_dbs = client.list_databases(None, None).await.unwrap();
177175
let new_dbs: Vec<_> = new_dbs
178176
.into_iter()
179-
.filter(|doc| match doc.get("name") {
180-
Some(&Bson::String(ref name)) => expected_dbs.contains(name),
181-
_ => false,
182-
})
177+
.filter(|db_spec| expected_dbs.contains(&db_spec.name))
183178
.collect();
184179
assert_eq!(new_dbs.len(), expected_dbs.len());
185180

186181
for name in expected_dbs {
187182
let db_doc = new_dbs
188183
.iter()
189-
.find(|doc| doc.get("name") == Some(&Bson::String(name.to_string())))
184+
.find(|db_spec| db_spec.name.as_str() == name)
190185
.unwrap();
191-
assert!(db_doc.contains_key("sizeOnDisk"));
192-
assert!(db_doc.contains_key("empty"));
186+
assert!(db_doc.size_on_disk > 0);
187+
assert!(!db_doc.empty);
193188
}
194189
}
195190

0 commit comments

Comments
 (0)