Как использовать драйвер MongoDB в Rocket.rs?

У меня есть простое приложение rocket.rs с маршрутом регистрации
Вот сервис с драйвером синхронизации mongodb

pub async fn create_new_user(user_email: &str, password: &str, user_name: &str) -> Result<(), &'static str> {

  let users_collection: mongodb::Collection<UserModel> = DB.collection("users");

  // check if user with given email or name already exists
  let existing_user = users_collection.find_one(
    doc! {
      "$or": [
        {"user_email": user_email},
        {"user_name": user_name}
      ]
    },
    None
  );

  if existing_user.is_ok() {
    return Err("User with such email or name already exists");
  }


  // new user struct
  let new_user = UserModel {
    user_id: /* hash gen fn */,
    user_name: user_name.to_string(),
    email: user_email.to_string(),
    hashed_password: password.to_string(),
    verified: false,
  };



  // insert new user into db
  let insert_result = DB.collection("users").insert_one(new_user, None);

  if insert_result.is_ok() {
    return Ok(());
  } else {
    return Err("Failed to create user");
  }

}

К сожалению, ракета отказывается работать с драйвером синхронизации, и мне нужен асинхронный.

Как добавить его в приложение? Потому что я понятия не имею, и единственное, что я нашел в Google, - это слишком сложный код, который я не понимаю, или документация по асинхронным драйверам...

// how it was with sync one...
static DB: Lazy<Database> = Lazy::new(
    || {
        let client = Client::with_uri_str(&CONFIG.mongodb_uri_string);
        client.database("silly-tips")
    }
);
// now
static DB: once_cell:unsync::Lazy<Database> = once_cell:unsync::Lazy::new(
    async {
        let client = Client::with_uri_str(&CONFIG.mongodb_uri_string).await.expect("Error connecting to MongoDB");
        client.database("silly-tips")
    }
);
`Cell<std::option::Option<fn() -> Database>>` cannot be shared between threads safely
within `once_cell::unsync::Lazy<Database>`, the trait `Sync` is not implemented for `Cell<std::option::Option<fn() -> Database>>`, which is required by `once_cell::unsync::Lazy<Database>: Sync`
if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`
shared static variables must have a type that implements `Sync`rustcClick for full compiler diagnostic

🤔 А знаете ли вы, что...
MongoDB позволяет создавать кластеры на нескольких узлах для обеспечения отказоустойчивости и масштабируемости...


79
1

Ответ:

Решено

Есть два способа сделать это:

Продолжайте использовать глобальную переменную

Если вы хотите имитировать существующую настройку с помощью static DB: Lazy<Database>, вы можете продолжать делать это, используя tokio::sync::OnceCell, например:

use mongodb::{Client, Database};
use tokio::sync::OnceCell;

async fn get_db() -> &'static Database {
    static DB: OnceCell<Database> = OnceCell::const_new();

    DB.get_or_init(|| async {
        Client::with_uri_str(&CONFIG.mongodb_uri_string)
            .await
            .expect("Error connecting to MongoDB")
            .database("silly-tips")
    })
    .await
}
pub async fn create_new_user(...) {
    let users_collection = get_db().await.collection::<UserModel>("users");
    ...
}

Посмотрите еще несколько способов здесь: Как использовать lazy_static с инициализатором async/await?

Использовать состояние Ракеты

Другой вариант — позволить Rocket управлять состоянием за вас. Вы делаете это, передавая базу данных Rocket через .manage(), из которой вы можете получать маршруты Rocket через защиту запросов State. Затем вы просто передадите его в свою функцию create_new_user как обычный параметр:

use mongodb::{Client, Database};
use rocket::{launch, post, routes, State};

pub async fn create_new_user(db: &Database, ...) {
    let users_collection = db.collection::<UserModel>("users");
    ...
}

#[post("/login")]
async fn login(db: &State<Database>) {
    ...
    create_new_user(&db, ...).await;
    ...
}

#[launch]
async fn rocket() -> _ {
    ...

    let db = Client::with_uri_str(&CONFIG.mongodb_uri_string)
        .await
        .expect("Error connecting to MongoDB")
        .database("silly-tips");

    rocket::build().manage(db).mount("/", routes![login])
}

Обычно это предпочтительнее, поскольку вы избегаете глобальных переменных.