#struct-fields #diesel #orm #table-column #sql

macro diesel-sort-struct-fields

Macro to sort struct fields and table! columns to avoid subtle bugs

4 releases

0.1.3 Jul 1, 2019
0.1.2 Jul 1, 2019
0.1.1 Jul 1, 2019
0.1.0 Jul 1, 2019

#20 in #table-column

MIT license

15KB
270 lines

diesel-sort-struct-fields

Macro to sort struct fields and table! columns to avoid subtle bugs with Diesel.

See the crate documentation for a usage examples and more info.


License: MIT


lib.rs:

Macro to sort struct fields and table! columns to avoid subtle bugs.

The way Diesel maps a response from a query into a struct is by treating a row as a tuple and assigning the fields in the order of the fields in code. Something like (not real code):

struct User {
    id: i32,
    name: String,
}

fn user_from_row(row: (i32, String)) -> User {
    User {
        id: row.0,
        name: row.1,
    }
}

This works well, but it will break in subtle ways if the order of id and name aren't the same in table! and struct User { ... }. So this code doesn't compile:

#[macro_use]
extern crate diesel;

use diesel::prelude::*;

table! {
    users {
        // order here doesn't match order in the struct
        name -> VarChar,
        id -> Integer,
    }
}

#[derive(Queryable)]
struct User {
    id: i32,
    name: String,
}

fn main() {
    let db = connect_to_db();

    users::table
        .select(users::all_columns)
        .load::<User>(&db)
        .unwrap();
}

fn connect_to_db() -> PgConnection {
    PgConnection::establish("postgres://localhost/diesel-sort-struct-fields").unwrap()
}

Luckily you get a type error, so Diesel is clearly telling you that something is wrong. However if the types of id and name were the same you wouldn't get a type error. You would just have subtle bugs that could take hours to track down (it did for me).

This crate prevents that with a simple procedural macro that sorts the fields of your model struct and table! such that you can define them in any order, but once the code gets to the compiler the order will always be the same.

Example:

#[macro_use]
extern crate diesel;

use diesel_sort_struct_fields::{sort_columns, sort_fields};
use diesel::prelude::*;

#[sort_columns]
table! {
    users {
        name -> VarChar,
        id -> Integer,
    }
}

#[sort_fields]
#[derive(Queryable)]
struct User {
    id: i32,
    name: String,
}

fn main() {
    let db = connect_to_db();

    let users = users::table
        .select(users::all_columns)
        .load::<User>(&db)
        .unwrap();

    assert_eq!(0, users.len());
}

fn connect_to_db() -> PgConnection {
    PgConnection::establish("postgres://localhost/diesel-sort-struct-fields").unwrap()
}

Dependencies

~2MB
~48K SLoC