Skip to content

Commit

Permalink
RUST-1652 Add a find_one method to GridFsBucket (#1015)
Browse files Browse the repository at this point in the history
Co-authored-by: Isabel Atkinson <isabelatkinson@gmail.com>
  • Loading branch information
kkloberdanz and isabelatkinson authored Jan 25, 2024
1 parent 0f928c9 commit 7f0d356
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 6 deletions.
20 changes: 19 additions & 1 deletion src/gridfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ use crate::{
bson::{doc, oid::ObjectId, Bson, DateTime, Document, RawBinaryRef},
cursor::Cursor,
error::{Error, ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result},
options::{CollectionOptions, FindOptions, ReadConcern, SelectionCriteria, WriteConcern},
options::{
CollectionOptions,
FindOneOptions,
FindOptions,
ReadConcern,
SelectionCriteria,
WriteConcern,
},
Collection,
Database,
};
Expand Down Expand Up @@ -232,6 +239,17 @@ impl GridFsBucket {
self.files().find(filter, find_options).await
}

/// Finds and returns a single [`FilesCollectionDocument`] within this bucket that matches the
/// given filter.
pub async fn find_one(
&self,
filter: Document,
options: impl Into<Option<GridFsFindOneOptions>>,
) -> Result<Option<FilesCollectionDocument>> {
let find_options = options.into().map(FindOneOptions::from);
self.files().find_one(filter, find_options).await
}

/// Renames the file with the given 'id' to the provided `new_filename`. This method returns an
/// error if the `id` does not match any files in the bucket.
pub async fn rename(&self, id: Bson, new_filename: impl AsRef<str>) -> Result<()> {
Expand Down
2 changes: 1 addition & 1 deletion src/gridfs/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
// Utility functions for finding files within the bucket.
impl GridFsBucket {
async fn find_file_by_id(&self, id: &Bson) -> Result<FilesCollectionDocument> {
match self.files().find_one(doc! { "_id": id }, None).await? {
match self.find_one(doc! { "_id": id }, None).await? {
Some(file) => Ok(file),
None => Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound {
identifier: GridFsFileIdentifier::Id(id.clone()),
Expand Down
30 changes: 29 additions & 1 deletion src/gridfs/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use typed_builder::TypedBuilder;

use crate::{
bson::Document,
options::{FindOptions, ReadConcern, SelectionCriteria, WriteConcern},
options::{FindOneOptions, FindOptions, ReadConcern, SelectionCriteria, WriteConcern},
};

/// Contains the options for creating a [`GridFsBucket`](crate::gridfs::GridFsBucket).
Expand Down Expand Up @@ -103,3 +103,31 @@ impl From<GridFsFindOptions> for FindOptions {
}
}
}

/// Contains the options for finding a single
/// [`FilesCollectionDocument`](crate::gridfs::FilesCollectionDocument) in a
/// [`GridFsBucket`](crate::gridfs::GridFsBucket).
#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)]
#[builder(field_defaults(default, setter(into)))]
#[non_exhaustive]
pub struct GridFsFindOneOptions {
/// The maximum amount of time to allow the query to run.
pub max_time: Option<Duration>,

/// The number of documents to skip before returning.
pub skip: Option<u64>,

/// The order by which to sort results. Defaults to not sorting.
pub sort: Option<Document>,
}

impl From<GridFsFindOneOptions> for FindOneOptions {
fn from(options: GridFsFindOneOptions) -> Self {
Self {
max_time: options.max_time,
skip: options.skip,
sort: options.sort,
..Default::default()
}
}
}
50 changes: 47 additions & 3 deletions src/test/spec/gridfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use futures_util::io::{AsyncReadExt, AsyncWriteExt};
use crate::{
bson::{doc, Bson, Document},
error::{Error, ErrorKind, GridFsErrorKind},
gridfs::{GridFsBucket, GridFsUploadStream},
options::{GridFsBucketOptions, GridFsUploadOptions},
gridfs::{GridFsBucket, GridFsFindOneOptions, GridFsUploadStream},
options::{FindOneOptions, GridFsBucketOptions, GridFsUploadOptions},
runtime,
test::{
get_client_options,
Expand Down Expand Up @@ -120,7 +120,6 @@ async fn upload_test(bucket: &GridFsBucket, data: &[u8], options: Option<GridFsU
assert_eq!(data, &uploaded);

let file = bucket
.files()
.find_one(doc! { "_id": upload_stream.id() }, None)
.await
.unwrap()
Expand Down Expand Up @@ -332,3 +331,48 @@ async fn assert_no_chunks_written(bucket: &GridFsBucket, id: &Bson) {
.unwrap()
.is_none());
}

#[cfg_attr(feature = "tokio-runtime", tokio::test)]
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
async fn test_gridfs_bucket_find_one() {
let data = &[1, 2, 3, 4];
let client = TestClient::new().await;

let options = GridFsBucketOptions::default();
let bucket = client.database("gridfs_find_one").gridfs_bucket(options);

let filename = String::from("somefile");
let mut upload_stream = bucket.open_upload_stream(&filename, None);
upload_stream.write_all(data).await.unwrap();
upload_stream.close().await.unwrap();

let found = bucket
.find_one(doc! { "_id": upload_stream.id() }, None)
.await
.unwrap()
.unwrap();

assert_eq!(&found.id, upload_stream.id());
assert_eq!(found.length, 4);
assert_eq!(found.filename, Some(filename));
}

#[test]
fn test_gridfs_find_one_options_from() {
let default_options = GridFsFindOneOptions::default();
let find_one_options = FindOneOptions::from(default_options);
assert_eq!(find_one_options.max_time, None);
assert_eq!(find_one_options.skip, None);
assert_eq!(find_one_options.sort, None);

let options = GridFsFindOneOptions::builder()
.sort(doc! { "foo": -1 })
.skip(1)
.max_time(Duration::from_millis(42))
.build();

let find_one_options = FindOneOptions::from(options);
assert_eq!(find_one_options.max_time, Some(Duration::from_millis(42)));
assert_eq!(find_one_options.skip, Some(1));
assert_eq!(find_one_options.sort, Some(doc! {"foo": -1}));
}

0 comments on commit 7f0d356

Please sign in to comment.