Search code examples
databasepostgresqlrustrust-dieselrust-rocket

Return single record created in PostgreSQL using Rocket and Diesel (Rust)


I'm trying to build a simple web app based on tutorials: one, two and three. All of them uses slightly different approaches and were written for older versions of the used libraries, so current Diesel and Rocket API slightly differs. I want in response to POST-request return created record or record id. At this point I'm able to return only list of records - list of a single record (attempts to return single record, based on Vec.first() don't compile).

Cargo.toml:

[dependencies]
# chrono is needed with serialization/deserialisation options
chrono = { version = "0.4.10", features = ["serde"] }
diesel = { version = "1.4.3", features = ["postgres", "chrono", "network-address"] }
ipnetwork = "0.15.1"
dotenv = "0.9.0"

# Lazy static initialization
lazy_static = "1.3.0"

# Rocket Webserver
rocket = "0.4.2"
rocket_contrib = {version = "0.4.2", features = ["json", "diesel_postgres_pool"]}

# Connection pooling
r2d2 = "0.8.7"
r2d2-diesel = "1.0.0"

# Serialization/Deserialization
serde = "1.0.104"
serde_json = "1.0.44"
serde_derive = "1.0.104"

models.rs:

#![allow(proc_macro_derive_resolution_fallback)]
use crate::schema::bs_date_forecast;

use chrono::{NaiveDate, NaiveDateTime};
use ipnetwork::IpNetwork;
use diesel::prelude::*;

#[derive(Queryable, Insertable, Serialize, Deserialize)]
#[table_name="bs_date_forecast"]
pub struct BsDateForecast {
    pub id: Option<i32>,
    pub predicted_date: NaiveDate,
    pub seer_ip: IpNetwork,
    pub created_timestamp: NaiveDateTime,  // Defined automatically at the database level
}

impl BsDateForecast {

    // Would like to return the record (BsDateForecast) instead, but don't know how
    pub fn create(forecast: BsDateForecast,
                  conn: &PgConnection) -> QueryResult<Vec<BsDateForecast>> {
        diesel::insert_into(bs_date_forecast::table)
            .values(&forecast)
            .get_results(conn)
    }

}

schema.rs:

table! {
    bs_date_forecast (id) {
        id -> Nullable<Int4>,
        predicted_date -> Date,
        seer_ip -> Inet,
        created_timestamp -> Timestamptz,
    }
}

routes.rs:

use diesel::{self, prelude::*};

use rocket_contrib::json::{Json, JsonValue};
use rocket::response::status;

use crate::models::{BsDateForecast};
use crate::schema;
use crate::DbConn;
use serde_json::Value;



#[post("/predictions", format = "application/json", data = "<prediction>")]
pub fn create(prediction: Json<BsDateForecast>, conn: DbConn) -> Json<Vec<BsDateForecast>> {
    let insert = BsDateForecast{id: None, ..prediction.into_inner()};
    Json(BsDateForecast::create(insert, &conn))
}

Solution

  • It's as simple as changing the function that loads values from the database.

    impl BsDateForecast {
    
        // Would like to return the record (BsDateForecast) instead, but don't know how
        pub fn create(forecast: BsDateForecast,
                      conn: &PgConnection) -> QueryResult<BsDateForecast> {
            diesel::insert_into(bs_date_forecast::table)
                .values(&forecast)
                .get_result(conn)
        }
    
    }
    

    See the documentation of RunQueryDsl for details (and other available methods)