-2

I have set up a database connection but I want to share it with my warp API handlers.

my Cargo.toml

[package]
name = "mongo-warp"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dotenv = "0.15.0"
mongodb = "2.2.2"
bson = { version = "2", features = ["chrono-0_4"] } 
tokio = "1"
serde = "1" 
warp = "0.3"
serde_json = "1.0"

and in main I have the database setup and working along with the routes that I'm importing from auth.rs

mod auth;

use dotenv;
use tokio;
use std::{env, error::Error};
use mongodb::{options::{ClientOptions}, Client, bson::doc};
use warp::Filter;

use crate::auth::auth_filter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    dotenv::dotenv().ok();
    let client_uri = env::var("MONGODB_URI").expect("You must set the MONGODB_URI .env");
    let options = ClientOptions::parse(&client_uri).await?;
    let client = Client::with_options(options)?;

    // TODO: Do this bit in the join_handler
    let users = client.database("rusty_db").collection("users");
    let user = doc! { "password": "1984", "username": "GeorgeOrwell" };
    let insert_result = users.insert_one(user, None).await?;
    println!("New document ID: {}", insert_result.inserted_id);
    // End

    let apis = auth_filter();
    let welcome = warp::path::end().map(|| "Welcome to my api");

    let routes = apis.or(welcome);
    warp::serve(routes).run(([127, 0, 0, 1], 3000)).await;

    Ok(())
}

and finally, the route which is where I want to have access to the database with join_handler and signin_handler

use serde_json::{json, Value};
use warp::{Filter, reply::Json};

pub fn auth_filter() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    let join = warp::path("join")
    .and(warp::get())
    .and(warp::path::end())
    .and_then(join_handler);

    let signin = warp::path("signin")
    .and(warp::post())
    .and(warp::body::json())
    .and_then(signin_handler);

    join.or(signin)
}

async fn join_handler() -> Result<Json, warp::Rejection> {
    let user = json!({"username":"GeorgeOrwell", "password": "1984", "id": "62a5108336aaebf431faa522"});
    let user = warp::reply::json(&user);
    Ok(user)
}

async fn signin_handler(data: Value) -> Result<Json, warp::Rejection> {
    let credentials = data;
    let credentials = warp::reply::json(&credentials);
    Ok(credentials)
}
Bill
  • 4,614
  • 13
  • 77
  • 132
  • Never really used warp, but look into how to add data into routes via filters. This article has an example called `with_db` that probably does what you need. – SeedyROM Jun 14 '22 at 06:19
  • If you are just trying to share `Database` between handlers, you can make it a static, since [the docs specify](https://docs.rs/mongodb/2.2.2/mongodb/struct.Database.html) "`Database` uses `std::sync::Arc` internally, so it can safely be shared across threads or async tasks". It is hard to understand what exactly your problem is, though. – Jeremy Meadows Jun 14 '22 at 13:41
  • inside `join_handler()` and `signin_handler()` i want to be able to insert an item into the database, and I can't figure out how I can share the database connection I made in `main()` – Bill Jun 14 '22 at 13:54

1 Answers1

0

I can't really test this without setting up a new database and everything, but if you move your Database client out to a lazy_static, then you can import it into your auth module and use it as if it were a local variable:

use dotenv;
use tokio;
use std::{env, error::Error};
use mongodb::{options::ClientOptions, Client, bson::doc};
use warp::Filter;
use lazy_static::lazy_static;

use crate::auth::auth_filter;

lazy_static! {
    static ref MONGO_DB: mongodb::Database = {
        tokio::runtime::Runtime::new().unwrap().block_on(async {
            Client::with_options(
                ClientOptions::parse(
                    env::var("MONGODB_URI").expect("You must set the MONGODB_URI .env")
                )
                .await
                .unwrap()
            )
            .unwrap()
            .database("rusty_db")
        })
    };
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    dotenv::dotenv().ok();

    let apis = auth_filter();
    let welcome = warp::path::end().map(|| "Welcome to my api");

    let routes = apis.or(welcome);
    warp::serve(routes).run(([127, 0, 0, 1], 3000)).await;

    Ok(())
}

mod auth {
    use mongodb::bson::doc;
    use serde_json::{json, Value};
    use warp::{Filter, reply::Json};
    use crate::MONGO_DB;

    pub fn auth_filter() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
        let join = warp::path("join")
        .and(warp::get())
        .and(warp::path::end())
        .and_then(join_handler);

        let signin = warp::path("signin")
        .and(warp::post())
        .and(warp::body::json())
        .and_then(signin_handler);

        join.or(signin)
    }

    async fn join_handler() -> Result<Json, warp::Rejection> {
        let users = MONGO_DB.collection("users");
        let user = doc! { "password": "1984", "username": "GeorgeOrwell" };
        let insert_result = users.insert_one(user, None).await.unwrap();
        println!("New document ID: {}", insert_result.inserted_id);

        let user = json!({"username":"GeorgeOrwell", "password": "1984", "id": "62a5108336aaebf431faa522"});
        let user = warp::reply::json(&user);
        Ok(user)
    }

    async fn signin_handler(data: Value) -> Result<Json, warp::Rejection> {
        let credentials = data;
        let credentials = warp::reply::json(&credentials);
        Ok(credentials)
    }
}

This way, the database connection is still in your main.rs, but it is not in main itself, which I think is the only part that you were missing when trying to use it in other modules. (I moved your auth module into the same file for testing, but you should be able to put it wherever you had it)

Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22