Use couchbase with actix-web

I'm trying to build a small with API with actix-web on top of couchbase.

I'm using couchbase-rs which looks like bleeding edge stuff.

Here is my main:

use std::env;

use actix_web::{get, web, App, HttpResponse, HttpServer};
use couchbase::{Cluster, Collection, GetOptions};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
pub struct QueryParameters {
    stuff: String,
}

#[derive(Deserialize, Serialize)]
pub struct CouchbaseDocument {
    click: String,
    #[serde(rename = "with JSON")]
    with_json: String,
}

#[get("/")]
async fn index(
    parameters: web::Query<QueryParameters>,
    collection: web::Data<Collection>,
) -> HttpResponse {
    let digest = format!("{:x}", md5::compute(parameters.stuff.as_bytes()));
    let doc = collection.get(digest, GetOptions::default()).await.unwrap();

    HttpResponse::Ok()
        .content_type("application/json")
        .json(doc.content::<CouchbaseDocument>().unwrap())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let cb_url = env::var("COUCHBASE_URL").unwrap();
    let cb_user = env::var("COUCHBASE_USER").unwrap();
    let cb_password = env::var("COUCHBASE_PASSWORD").unwrap();
    let cb_bucket = env::var("COUCHBASE_PASSWORD").unwrap();

    let cluster = Cluster::connect(cb_url, cb_user, cb_password);
    let bucket = cluster.bucket(cb_bucket);
    let collection = bucket.default_collection();

    HttpServer::new(move || App::new().service(index).data(collection))
        .bind(env::var("LISTEN_ON").unwrap_or_else(|_| String::from("0.0.0.0:8080")))?
        .run()
        .await
}

Obviously, it doesn't work, as collection does not implements the Copy/Clone traits.

Is there a way to handle this gracefully?

If you want to share a value, the answer is typically Arc. This type is a wrapper that you can put around any value. Whenever you clone an Arc, this doesn't clone the inner value, but provides a new shared wrapper that points to the same value.

I tried with this approach, and it fails:

error[E0599]: no method named `arc_bucket` found for struct `Bucket` in the current scope
  --> src/main.rs:88:67
   |
88 |     HttpServer::new(move || App::new().service(index).data(bucket.arc_bucket()))
   |                                                                   ^^^^^^^^^^ method not found in `Bucket`

Here is the code:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let cb_url = env::var("COUCHBASE_URL").unwrap();
    let cb_user = env::var("COUCHBASE_USER").unwrap();
    let cb_password = env::var("COUCHBASE_PASSWORD").unwrap();
    let cb_bucket = env::var("COUCHBASE_PASSWORD").unwrap();
    
    let cluster= Cluster::connect(cb_url, cb_user, cb_password);
    let bucket = cluster.bucket(cb_bucket);
    let arc_bucket = Arc::new(bucket);
    
    HttpServer::new(move || App::new().service(index).data(bucket.arc_bucket()))
        .bind(env::var("LISTEN_ON").unwrap_or_else(|_| String::from("0.0.0.0:8080")))?
        .run()
        .await
}

It's not a field on bucket. It's a new variable.

You probably need something like this:

HttpServer::new(move || App::new().service(index).data(arc_bucket.clone()))
    .bind(env::var("LISTEN_ON").unwrap_or_else(|_| String::from("0.0.0.0:8080")))?
    .run()
    .await

Indeed, I messed up my code when trying this, thanks!

It compiles now, but when I try to get in couchbase, it stays stuck

#[get("/api/profile")]
async fn index(
    parameters: web::Query<QueryParameters>,
    collection: web::Data<Arc<Collection>>,
) -> HttpResponse {
    println!("Input: {:?}", parameters);
    let digest = format!("{:x}", md5::compute(parameters.url.as_bytes()));
    let r = collection.get(digest, GetOptions::default()).await.unwrap();
    
    println!("Please show me the result {:?}", r); // <-- Stuck just before this print

    HttpResponse::Ok()
        .content_type("application/json")
        .json(r.content::<CouchbaseDocument>().unwrap())
}

Maybe it's due to how the couchbase lib is implemented?

I don't know. I tried to look up the documentation for the couchbase library, but it seems like it failed to build.

Ok, so the issue was a silly copy past I made

    let cb_url = env::var("COUCHBASE_URL").unwrap();
    let cb_user = env::var("COUCHBASE_USER").unwrap();
    let cb_password = env::var("COUCHBASE_PASSWORD").unwrap();
    let cb_bucket = env::var("COUCHBASE_PASSWORD").unwrap();

should obviously be

    let cb_url = env::var("COUCHBASE_URL").unwrap();
    let cb_user = env::var("COUCHBASE_USER").unwrap();
    let cb_password = env::var("COUCHBASE_PASSWORD").unwrap();
    let cb_bucket = env::var("COUCHBASE_BUCKET").unwrap();

The couchbase library has a weird behavior here, but once I fix this, everything went fine!

Thanks @alice for the help!

Can you share the final code. Even I am stuck with this. @fistons

Sure!

use std::env;

use actix_web::{get, web, App, HttpResponse, HttpServer};
use couchbase::{Cluster, Collection, GetOptions};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
pub struct QueryParameters {
    stuff: String,
}

#[derive(Deserialize, Serialize)]
pub struct CouchbaseDocument {
    click: String,
    #[serde(rename = "with JSON")]
    with_json: String,
}

#[get("/")]
async fn index(
    parameters: web::Query<QueryParameters>,
    collection: web::Data<Arc<Collection>>,
) -> HttpResponse {
    let digest = format!("{:x}", md5::compute(parameters.stuff.as_bytes()));
    let doc = collection.get(digest, GetOptions::default()).await.unwrap();

    HttpResponse::Ok()
        .content_type("application/json")
        .json(doc.content::<CouchbaseDocument>().unwrap())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let cb_url = env::var("COUCHBASE_URL").unwrap();
    let cb_user = env::var("COUCHBASE_USER").unwrap();
    let cb_password = env::var("COUCHBASE_PASSWORD").unwrap();
    let cb_bucket = env::var("COUCHBASE_BUCKET").unwrap();

    let cluster = Cluster::connect(cb_url, cb_user, cb_password);
    let bucket = cluster.bucket(cb_bucket);
    let collection = bucket.default_collection();
    let collection_arc = Arc::new(collection);

    HttpServer::new(move || App::new().service(index).data(collection_arc.clone()))
        .bind(env::var("LISTEN_ON").unwrap_or_else(|_| String::from("0.0.0.0:8080")))?
        .run()
        .await
}

As Alice said, I passed a Arc to the .data method, and retrieve it in my endpoint method