Home > Mobile >  Rust API working locally but not in docker
Rust API working locally but not in docker

Time:04-12

I am working on a sample api in rust. Everything is working fine in locally. I'm using windows 10 and this version of rust and cargo.

rustc --version
rustc 1.60.0 (7737e0b5c 2022-04-04)
cargo --version
cargo 1.60.0 (d1fd9fe2c 2022-03-01)

The issue happens when I try to create a docker image. This is my dockerfile

FROM rust:1.60.0

# Copy local code to the container image.
WORKDIR /usr/src/app
COPY . .

# Install production dependencies and build a release artifact.
RUN cargo build --release

# Service must listen to $PORT environment variable.
# This default value facilitates local development.
ENV PORT 8080

# Run the web service on container startup.
CMD ["./target/release/simple_server.exe"]

The error happens on RUN cargo build --realease, it is showing this error:

#8 402.4    Compiling actix-router v0.5.0
#8 404.6 error[E0597]: `version_regex` does not live long enough
#8 404.6   --> /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/os_info-1.3.3/src/linux/file_release.rs:36:13
#8 404.6    |
#8 404.6 36 | /             version_regex
#8 404.6 37 | |                 .captures_iter(&file_content)
#8 404.6    | |                                             ^
#8 404.6    | |                                             |
#8 404.6    | |_____________________________________________borrowed value does not live long enough
#8 404.6    |                                               a temporary with access to the borrow is created here ...
#8 404.6 ...
#8 404.6 41 |           } else {
#8 404.6    |           -
#8 404.6    |           |
#8 404.6    |           `version_regex` dropped here while still borrowed
#8 404.6    |           ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `regex::CaptureMatches<'_, '_>`       
#8 404.6    |
#8 404.6    = note: the temporary is part of an expression at the end of a block;
#8 404.6            consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
#8 404.6 help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
#8 404.6    |
#8 404.6 36 ~             let x = version_regex
#8 404.6 37 |                 .captures_iter(&file_content)
#8 404.6 38 |                 .next()
#8 404.6 39 |                 .and_then(|c| c.get(1))
#8 404.6 40 ~                 .map(|v| v.as_str().trim_end().to_owned()); x
#8 404.6    |
#8 404.6
#8 404.7 For more information about this error, try `rustc --explain E0597`.
#8 404.7 error: could not compile `os_info` due to previous error
#8 404.7 warning: build failed, waiting for other jobs to finish...
#8 420.3 error: build failed

I have been trying to replicate this error locally but it never happens. I don't really get what I am doing wrong, I am still learning Rust.

Source code

Cargo.tml

[package]
name = "simple_server"
version = "0.1.0"
authors = ["AlexLeo99 <[email protected]>"]
edition = "2018"

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

[dependencies]
mongodb = "0.9.2"
actix-web = "4"
actix-rt = "1"
bson = "0.14.0"
serde = "1.0.103"
futures = "0.3.4"
env_logger = "0.7"
dotenv = "0.15.0"
actix-cors = "0.2.0"
serde_json = "1.0"

main.rs

// External imports
use actix_cors::Cors;
use actix_web::{http, middleware, App, HttpServer};
use dotenv::dotenv;
use mongodb::{options::ClientOptions, Client};
use std::env;
use api_service::ApiService;

// External modules reference
mod api_router;
mod api_service;

// Api Service constructor
pub struct ServiceManager {
    api: ApiService,
}

// Api Servie Implementation
impl ServiceManager {
    pub fn new(api: ApiService) -> Self {
        ServiceManager { api }
    }
}

// Service Manager constructor
pub struct AppState {
    service_manager: ServiceManager,
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    // init env
    dotenv().ok();

    // init logger middleware
    env::set_var("RUST_LOG", "actix_web=debug,actix_server=info");
    env_logger::init();

    // Parse a connection string into an options struct.
    let database_url = env::var("DATABASE_URL").expect("DATABASE URL is not in .env file");
    let client_options = ClientOptions::parse(&database_url).unwrap();

    // Get the reference to Mongo DB
    let client = Client::with_options(client_options).unwrap();

    // get the reference to the Data Base
    let database_name = env::var("DATABASE_NAME").expect("DATABASE NAME is not in .env file");
    let db = client.database(&database_name);

    // get the reference to the Collection
    let user_collection_name = env::var("USER_COLLECTION_NAME").expect("COLLECTION NAME is not in .env file");
    let user_collection = db.collection(&user_collection_name);

    // Gte the server URL
    let server_url = env::var("SERVER_URL").expect("SERVER URL is not in .env file");

    // Start the server
    HttpServer::new(move || {
        let user_service_worker = ApiService::new(user_collection.clone());
        let service_manager = ServiceManager::new(user_service_worker);

        // cors
        let cors_middleware = Cors::new()
            .allowed_methods(vec!["GET", "POST", "DELETE", "PUT"])
            .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
            .allowed_header(http::header::CONTENT_TYPE)
            .max_age(3600)
            .finish();

        // Init http server
        App::new()
            .wrap(cors_middleware)
            .wrap(middleware::Logger::default())
            .data(AppState { service_manager })
            .configure(api_router::init)
    })
    .bind(server_url)?
    .run()
    .await
}

api_router/mod.rs

use crate::api_service::Data;
use actix_web::{delete, get, post, web, HttpResponse, Responder};

#[get("/get-all")]
async fn get_all_json(app_data: web::Data<crate::AppState>) -> impl Responder {
    let action = app_data.service_manager.api.get_json();
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[get("/get-by/{param}")]
async fn get_user_email(app_data: web::Data<crate::AppState>, param: web::Path<String>) -> impl Responder {
    let action = app_data.service_manager.api.get_by(&param);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[post("/add")]
async fn add_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>) -> impl Responder {
    let action = app_data.service_manager.api.create(&data);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.inserted_id),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[post("/update/{param}")]
async fn update_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>, param: web::Path<String>) -> impl Responder {
    let action = app_data.service_manager.api.update(&data, &param);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.modified_count),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[delete("/delete")]
async fn delete_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>) -> impl Responder {
    let action = app_data.service_manager.api.delete(&data.title);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.deleted_count),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

// function that will be called on new Application to configure routes for this module
pub fn init(cfg: &mut web::ServiceConfig) {
            // cfg.service(get_user_email);
            // cfg.service(add_user);
            // cfg.service(update_user);
            // cfg.service(delete_user);
    cfg.service(get_all_json);
    }

api_service/mod.rs

// External imports
use bson::{doc, Document};
use mongodb::results::{DeleteResult, UpdateResult, InsertOneResult};
use mongodb::{error::Error, Collection};
use serde::{Deserialize, Serialize};
// External constructors
extern crate serde;
extern crate serde_json;

// Estructure data for DB
#[derive(Debug, Serialize, Deserialize)]
pub struct Data {
    pub title: String,
    pub author: String,
}

// Reference colection clone
#[derive(Clone)]
pub struct ApiService {
    collection: Collection,
}

// Transform data to mongo db document
fn data_to_document(data: &Data) -> Document {
    let Data {
        title,
        author,
    } = data;
    doc! {
        "title": title,
        "author": author,
    }
}

// Functions with quieries to Mongo
impl ApiService {
    pub fn new(collection: Collection) -> ApiService {
        ApiService { collection }
    }

    // Insert data to Mongo DB
    pub fn create(&self, _data:&Data) -> Result<InsertOneResult, Error> {
        self.collection.insert_one(data_to_document(_data), None)
    }

    // Update an existing document 
    pub fn update(&self, _data:&Data, _param: &String) -> Result<UpdateResult, Error> {
        let object_param = bson::oid::ObjectId::with_string(_param).unwrap();
        self.collection.update_one(doc! { "_id": object_param }, data_to_document(_data), None)
    }

    // Delete some document
    pub fn delete(&self, _title: &String) -> Result<DeleteResult, Error> {
        self.collection.delete_one(doc! { "title": _title }, None)
    }

    // Get all documents
    pub fn get_json(&self) -> std::result::Result<std::vec::Vec<bson::ordered::OrderedDocument>, mongodb::error::Error> {
        let cursor = self.collection.find(None, None).ok().expect("Failed to execute find.");
        let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
        Ok(docs)
    }

    // Get documents with quiery
    pub fn get_by(&self, param: &String) -> std::result::Result<std::vec::Vec<bson::ordered::OrderedDocument>, mongodb::error::Error> {
        let cursor = self.collection.find(doc! { "author": { "$regex": param } }, None).ok().expect("Failed to execute find.");
        let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
        let _serialized = serde_json::to_string(&docs).unwrap();
        Ok(docs)
    }
}

CodePudding user response:

The error is due to the mongodb 0.9.2 depending on os_info 1.3.3, which doesn't compile on Linux. The correct way to resolve this problem would be to bump mongodb version - 0.9.2 is fairly old; the latest version at time of writing is 2.1.0, and it uses os_info 3, which doesn't have this problem.


If, however, for some reason this update is undesirable (e.g. if the changes due to the changed API would be too large), and the crate isn't going to be published to crates.io, it's possible to leverage the Cargo.toml [patch] section:

  • Copy the source code of the os_info into some local directory. It can be found in $USER_DIR/cargo/registry/src/github.com-$HASH/os_info-1.3.3 (the root component is the home directory, e.g. C:/Users/username; hash can be different).
  • Add the following entry to the Cargo.toml:
[patch.crates-io]
os_info = { path = "path/to/os_info" }
  • Following the original compiler error, change the file src/linux/file_release.rs inside the local copy, starting from the line 34. One possible change could be to use the compiler suggestion. Another (and possibly easier) is to inline the variable version_regex into the following expression; after that, the whole block would look like this:
let version = if !release_info.version_regex.is_empty() {
    Regex::new(release_info.version_regex)
        .unwrap()
        .captures_iter(&file_content)
        .next()
        .and_then(|c| c.get(1))
        .map(|v| v.as_str().trim_end().to_owned())
} else {
    Some(file_content.trim_end().to_string())
}

After that, your code should compile.

  • Related