1
\$\begingroup\$

I just started learning Rust, and try to implement a function like Django'sget_or_create.

Now my implementation looks too verbose[ I hope that rust can be neater.Therefore, how can I implement this function in less verbose way?There is probably a way to shorten nestedmatch constructions below?

use log::warn;use diesel::prelude::*;use diesel::result;use crate::db::{get_connection, PgPool};use crate::models::{NewUser, User};pub fn get_or_create_user(pool: &PgPool, email: &str) -> User {    use crate::schema::users;    let new_user = NewUser { email };    let mut conn = get_connection(pool);    let result = diesel::insert_into(users::table)        .values(&new_user)        .get_result(&mut conn);    match result {        Ok(user) => return user,        Err(err) => match err {            result::Error::DatabaseError(err_kind, info) => match err_kind {                result::DatabaseErrorKind::UniqueViolation => {                    warn!(                        "{:?} is already exists. Info: {:?}. Skipping.",                        new_user, info                    );                    // another query to DB to get existing user by email                    let user = user_by_email(pool, new_user.email);                    return user;                }                _ => {                    panic!("Database error: {:?}", info);                }            },            _ => {                // TODO: decide how to deal with unexpected errors                return User {                    id: 0,                    email: "".into(),                };            }        },    }}pub fn user_by_email(pool: &PgPool, user_email: &str) -> User {    use crate::schema::users::dsl::*;    let mut conn = get_connection(pool);    let user = crate::schema::users::dsl::users        .filter(email.eq(user_email))        .first(&mut conn)        .unwrap();    return user;}
Toby Speight's user avatar
Toby Speight
88.7k14 gold badges104 silver badges327 bronze badges
askedSep 9, 2022 at 10:45
Aleksey's user avatar
\$\endgroup\$

1 Answer1

1
\$\begingroup\$

welcome to the Rust community!

get or create

You may indeed make code readable by replacing multiple nested matches with a single match that has nested patterns.

For example, we match onErr(DatabaseError(UniqueViolation, info)) and that grabs all errors that contain aDatabaseError variant of DieselError enum, with innerUniqueViolation variant ofDatabaseErrorKind. We bind the second value withinDatabaseError toinfo, so we can print the info super easy too. If, for example, theError is something else thanUniqueViolation, we fall through to the next match arm.

The pattern sublanguage is like a language within a language -- you have to learn it and build your intuition about it.

The result of our effort is super readable:

match result {    Ok(user) => return user,    Err(DatabaseError(UniqueViolation, info)) => {        warn!(            "{:?} is already exists. Info: {:?}. Skipping.",            new_user, info        );        // another query to DB to get existing user by email        user_by_email(new_user.email)    }    Err(DatabaseError(_, info)) => {        panic!("Database error: {:?}", info);    }    _ => {        // TODO: decide how to deal with unexpected errors        User {            id: 0,            email: "".into(),        }    }}

I had an idea that you may only build one query, which would useON CONFLICT, and kill two birds with one stone. Unfortunately, Diesel dsl does not seem to supportON CONFLICT (...) DO NOTHING RETURNING *.

Other concerns

Syntax nitpick:

let user = schema::users::dsl::users    .filter(email.eq(user_email))    .first(&mut conn)    .unwrap();return user;

You may just return the user value directly, replacing the above code with this:

schema::users::dsl::users    .filter(email.eq(user_email))    .first(&mut conn)    .unwrap()

Result

The result is available on my github:https://github.com/pczarn/codereview/tree/81d3fcddd3921bf1b4df4bb347be5dcad3de743f/2022/9/get_or_create

I cleaned up your code, migrated to sqlite for local testing and this is what I got:

extern crate diesel;mod schema;use diesel::sqlite::SqliteConnection;use diesel::prelude::*;use dotenvy::dotenv;use std::env;use log::warn;use diesel::prelude::*;use schema::users;#[derive(Debug, Insertable)]#[diesel(table_name = users)]struct NewUser<'a> {    email: &'a str,}#[derive(Queryable)]pub struct User {    id: i32,    email: String,}pub fn get_or_create_user(email: &str) -> User {    use diesel::result::{Error::DatabaseError, DatabaseErrorKind::UniqueViolation};    let new_user = NewUser { email };    let mut conn = get_connection();    let result = diesel::insert_into(users::table)        .values(&new_user)        .get_result(&mut conn);    match result {        Ok(user) => return user,        Err(DatabaseError(UniqueViolation, info)) => {            warn!(                "{:?} is already exists. Info: {:?}. Skipping.",                new_user, info            );            // another query to DB to get existing user by email            user_by_email(new_user.email)        }        Err(DatabaseError(_, info)) => {            panic!("Database error: {:?}", info);        }        _ => {            // TODO: decide how to deal with unexpected errors            User {                id: 0,                email: "".into(),            }        }    }}pub fn user_by_email(user_email: &str) -> User {    use schema::users::dsl::*;    let mut conn = get_connection();    let user = schema::users::dsl::users        .filter(email.eq(user_email))        .first(&mut conn)        .unwrap();    return user;}pub fn get_connection() -> SqliteConnection {    dotenv().ok();    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");    SqliteConnection::establish(&database_url)        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))}fn main() {    get_or_create_user("[email protected]");    get_or_create_user("[email protected]");}
answeredSep 17, 2022 at 8:09
Peter Blackson's user avatar
\$\endgroup\$
1
  • \$\begingroup\$Thank you for so detailed explanation! I definitely need time to realize how to apply matches with all nested matches, unwrap*(), ?, ok() and so on ) Your answer helped me a lot.\$\endgroup\$CommentedSep 21, 2022 at 6:10

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.