Skip to main content

Crate diesel_enums

Crate diesel_enums 

Source
Expand description

§Diesel-enums

diesel-enums can be used to create mappings between Rust enums and database tables with fixed values, as well as custom postgres enums.

It creates a seamless interface with the diesel API, and generates the logic to enforce the correctness of the mapping.

Refer to the documentation for DbEnum and PgEnum to learn more about usage.

§Full Example With Sqlite

use diesel_enums::db_enum;
use std::{error::Error, time::Duration};

use deadpool_diesel::{
	Runtime,
	sqlite::{Manager, Object, Pool},
};
use diesel::{SqliteConnection, connection::SimpleConnection, prelude::*};
use diesel_enums::AsyncTestRunner;
use tokio::sync::OnceCell;

// Normally these would be in the `schema.rs` file...
diesel::table! {
	statuses (id) {
		id -> Integer,
		name -> Text
	}
}

diesel::table! {
	users (id) {
		id -> Integer,
		name -> Text,
		status_id -> Integer,
	}
}

diesel::joinable!(users -> statuses (status_id));
diesel::allow_tables_to_appear_in_same_query!(statuses, users,);

// We set up a custom runner to use in the generated tests
pub struct SqliteRunner;

impl AsyncTestRunner<SqliteConnection> for SqliteRunner {
	async fn run_check<F>(f: F) -> diesel_enums::DbEnumCheck
	where
		F: FnOnce(&mut SqliteConnection) -> diesel_enums::DbEnumCheck + Send + 'static,
	{
		get_or_init_pool()
			.await
			.interact(f)
			.await
			.expect("Failed to interact with the database")
	}
}

#[db_enum]
// Using our custom test runner
#[db(async_runner = SqliteRunner, table = statuses)]
pub enum Status {
	Offline,
	Active,
	Busy,
}

#[derive(
	Clone, Debug, PartialEq, Eq, Queryable, Selectable, Insertable, Associations, Identifiable,
)]
#[diesel(belongs_to(Status))]
pub struct User {
	#[diesel(skip_insertion)]
	pub id: i32,
	pub name: String,
	pub status_id: Status,
}

static SQLITE_POOL: OnceCell<Pool> = OnceCell::const_new();

pub async fn get_or_init_pool() -> Object {
	SQLITE_POOL
		.get_or_init(create_sqlite_pool)
		.await
		.get()
		.await
		.expect("Could not get a connection")
}

pub async fn create_sqlite_pool() -> deadpool_diesel::sqlite::Pool {
	let db_url = "file:example_db?mode=memory&cache=shared";
	let manager = Manager::new(db_url, Runtime::Tokio1);

	let pool = Pool::builder(manager)
		.max_size(1)
		.runtime(Runtime::Tokio1)
		.wait_timeout(Some(Duration::from_secs(5)))
		.create_timeout(Some(Duration::from_secs(5)))
		.build()
		.expect("could not build the connection pool");

	pool.get()
		.await
		.expect("Failed to get connection")
		.interact(|conn| {
			conn.batch_execute(
				r"
			PRAGMA foreign_keys = ON;

			CREATE TABLE statuses (
				id INTEGER NOT NULL PRIMARY KEY autoincrement,
				name TEXT NOT NULL
			);

			INSERT INTO statuses (name) VALUES
				('offline'), ('active'), ('busy');

			CREATE TABLE users (
				id INTEGER NOT NULL PRIMARY KEY autoincrement,
				name TEXT NOT NULL,
				status_id INTEGER NOT NULL,
				FOREIGN KEY (status_id) REFERENCES statuses (id)
			);
		",
			)
		})
		.await
		.expect("Failed interaction")
		.expect("Failed initial query");

	pool
}

pub async fn run_sqlite_query<T: Send + 'static>(
	callback: impl FnOnce(&mut SqliteConnection) -> QueryResult<T> + Send + 'static,
) -> Result<T, Box<dyn Error>> {
	Ok(get_or_init_pool()
		.await
		.interact(callback)
		.await??)
}

#[tokio::test]
async fn example() {
	let tom = User {
		id: 1,
		name: "Tom Bombadil".to_string(),
		status_id: Status::Active,
	};
	let clone = tom.clone();

	let inserted = run_sqlite_query(move |conn| {
		diesel::insert_into(users::table)
			.values(&clone)
			.returning(User::as_select())
			.get_result(conn)
	})
	.await
	.expect("Failed insertion");

	assert_eq!(inserted, tom);

	let filtered_query = run_sqlite_query(|conn| {
		users::table
			.select(User::as_select())
			// We can filter with the enum directly!
			.filter(users::status_id.eq(Status::Active))
			.first(conn)
	})
	.await
	.expect("Failed filtered query");

	assert_eq!(filtered_query, tom);

	let all_active = run_sqlite_query(|conn| {
		// Join queries become very concise
		User::belonging_to(&Status::Active)
			.select(User::as_select())
			.get_results(conn)
	})
	.await
	.expect("Failed query");

	assert_eq!(all_active.first().unwrap(), &tom);
}

#[db_enum]
// Wrong mapping! The generated test would catch this
#[db(skip_test, table = statuses)]
pub enum WrongStatus {
	Offline,
	Active,
}

#[tokio::test]
async fn wrong_status() {
	assert!(
		SqliteRunner::check_enum::<WrongStatus>()
			.await
			.is_err()
	)
}

§Features

  • postgres — Enables support for custom postgres enums
  • sqlite-async-runner — Exports the test runner for sqlite
  • default-sqlite-runner — Indicates that the sqlite runner should be used by default
  • pg-async-runner — Exports the test runner for postgres
  • default-pg-runner — Indicates that the postgres runner should be used by default
  • crate-runner — Indicates that the default runner is located at crate::db_test_runner::DbTestRunner.
  • async-crate-runner — Indicates that the default runner is located at crate::db_test_runner::DbTestRunner, and that it is an async runner.

Structs§

AsyncPgRunnerpg-async-runner
The default (async) test runner for Postgres. It uses deadpool-diesel to create a connection pool that can be shared among tests, so that they can be executed faster.
AsyncSqliteRunnersqlite-async-runner
The default (async) test runner for SQLite. It uses deadpool-diesel to create a connection pool, and sets the journal mode to WAL to allow for concurrent reads and faster tests.
DbEnumError
An error that is produced when a rust enum does not match a database enum or table.
IdMismatch
Represents a mismatch between a Rust variant and a database variant.
UnknownIdError
An error that can occur when trying to create an instance of a DbEnum from a number.
UnknownVariantError
An error that can occur when trying to create an instance of a DbEnum or PgEnum from a string.

Traits§

AsyncTestRunner
Trait for running database checks for mapped enums, asynchronously.
DbEnum
Maps a Rust enum to a database table with fixed values.
PgEnumpostgres
Maps a Rust enum to a custom enum in postgres.
SyncTestRunner
Trait for running database checks for mapped enums, synchronously.

Type Aliases§

DbEnumCheck
The outcome of an operation that checks if a Rust enum and a database source are mapped correctly.

Attribute Macros§

db_enum
Shortcut for deriving DbEnum on the target enum, along with the other required derives for it.
pg_enum
Shortcut for deriving PgEnum on the target enum, along with the other required derives for it.

Derive Macros§

DbEnum
Implements DbEnum on an enum as well as FromSql and ToSql with the target SQL type.
PgEnum
Implements PgEnum on an enum as well as FromSql and ToSql with the target custom type.