I have simple rocket.rs app with register route
Here it's service with mongodb sync driver
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");
}
}
Unfortunately, rocket refuses to work with sync driver and i need an async one
How to add it into app? Cuz i have no idea and only stuff i found in google is overcomplicated code i don't understand or async driver docs...
// 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
There are a two ways to go about it:
If you want to mimic your existing setup using static DB: Lazy<Database>
, you can keep doing that using tokio::sync::OnceCell
for instance:
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");
...
}
See a few other ways here: How to use lazy_static with async/await initializer?
The other option is to have Rocket manage the state for you. You do this by giving the database to Rocket via .manage()
from which you can receive in your Rocket routes via the State
request guard. Then you would just pass it into your create_new_user
function as a normal parameter:
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])
}
This is generally preferred since you avoid global variables.