Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection method find_one not usable with typed Collection #1146

Closed
SkylerMime opened this issue Jun 24, 2024 · 2 comments
Closed

Collection method find_one not usable with typed Collection #1146

SkylerMime opened this issue Jun 24, 2024 · 2 comments
Assignees

Comments

@SkylerMime
Copy link

SkylerMime commented Jun 24, 2024

Versions/Environment

  1. What version of Rust are you using?
    1.79.0
  2. What operating system are you using?
    macOS Sonoma 14.5
  3. What versions of the driver and its dependencies are you using?
    mongodb 2.8.2, bson 2.11.0
  4. What version of MongoDB are you using?
    7.0.11
  5. What is your MongoDB topology (standalone, replica set, sharded cluster, serverless)?
    Standalone, locally hosted

Describe the bug

I am trying to use the find_one() method on a collection typed with a custom struct, but the compiler cannot find the method.

rustc output: 'error[E0599]: no method named find_one found for struct mongodb::Collection<User> in the current scope'

This error doesn't occur if I just retrieve Documents rather than my User struct.

Reproduce with this code:

use mongodb::{
    bson::{doc, DateTime},
    Client,
};
use serde::{Deserialize, Serialize};

const MONGODB_URI: &'static str = "mongodb://localhost";

// Represents a document in the collection
#[derive(Clone, Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: &'static str,
    last_login: DateTime,
}

impl PartialEq for User {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && self.name == other.name && self.last_login == other.last_login
    }
}

async fn get_user(name: &str) -> User {
    let client = Client::with_uri_str(MONGODB_URI)
        .await
        .expect("Database should be connectable");

    // Get user bios collection
    let users_collection = client.database("skyserver").collection::<User>("users");

    let retrieved_user: User = users_collection
        .find_one(doc! { "name": name}, None)
        .await
        .expect("User should be in the database");

    retrieved_user
}

#[cfg(test)]
mod test {
    use mongodb::bson::DateTime;

    use crate::{get_user, User};

    const TEST_USER: User = User {
        id: 1,
        name: "Sample User",
        last_login: DateTime::from_millis(0),
    };

    #[tokio::test]
    async fn gets_sample_user() {
        // upsert_sample_user().await;
        assert_eq!(get_user("Sample User").await, TEST_USER);
    }
}

I have already tried to uses the clone_with_type() collection method, with the same results.

I am new to the mongodb Rust driver so I might be missing something obvious, thank you in advance for your help!

@isabelatkinson
Copy link
Contributor

Hey @SkylerMime, thanks for opening this issue! The find_one method only exists for Collection<T> where T: DeserializeOwned. Deriving or implementing the Deserialize trait is usually sufficient to satisfy this bound; however, this doesn't work when your struct contains borrowed data. This deserializer lifetimes guide gives a nice overview of why references in structs lead to different deserialization behavior, specifically the bullet point for <T> where T: DeserializeOwned under the trait bounds section. Note this sentence in particular:

the data that is being deserialized from is going to be thrown away before the function returns, so T must not be allowed to borrow from it

The driver does not hold onto the BSON bytes it receives from the server after find_one finishes executing, so the type deserializing from those bytes cannot store references to them.

The fix for this problem is to make sure that all of the fields in your struct are owned, which in your case would entail updating the name field to be a regular String:

#[derive(Clone, Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    last_login: DateTime,
}

@SkylerMime
Copy link
Author

@isabelatkinson,

Thank you for the prompt response and helpful links! The borrowed fields issue makes sense, switching to String fixed my problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants