#lookup-tables #enums #database-table #mapping #db #proc-macro #instance

plectrum

An easy way to represent lookup tables in a db as rust enums

1 unstable release

0.1.0 Aug 13, 2024

#581 in Rust patterns

MIT license

12KB
156 lines

Plectrum

plectrum[^1] is a rust crate that provides an easy way to represent lookup tables in a database (or any data source) as enums in rust.

Quick example

Suppose we have the following lookup table in an sqlite db:

CREATE TABLE item_states (
    id integer PRIMARY KEY,
    label text NOT NULL UNIQUE
);

And it contains the following data:

sqlite> SELECT * from item_states;
1|todo
2|in_progress
3|completed
4|parked
5|archived

Using the Plectrum procedural macro provided by the crate, we can define an enum to represent it as follows:

#[derive(Debug, Plectrum)]
#[plectrum(rename_all = "snake_case")]
enum ItemState {
    Todo,
    InProgress,
    Completed,
    Parked,
    Archived,
}

This will allow us to use the Mapping abstraction provided by the crate to easily convert an instance of the enum to the corresponding id or value (label in this case) and vice versa:

mapping.by_id(1)                     // Some(ItemState::Todo)
mapping.by_value("in_progress")      // Some(ItemState::InProgress)
mapping.get_id(&ItemState::Parked)   // Some(4)

To keep this example concise, I've glossed over one part here, which is to tell plectrum how to fetch the lookup table entries from the db[^2]. This is done by implementing the plectrum::DataSource trait for a struct and then use that struct instance to initialize plectrum::Mapping. Refer to the complete implementation of this example here.

Installation

cargo add plectrum --features derive

The derive feature provides a proc macro that takes care of implementing the plectrum::Enum trait for the enum.

If you are using the sqlx crate for interacting with your data source, then it's recommended to specify the sqlx feature.

cargo add plectrum --features derive,sqlx

This feature provides the plectrum::Error::Sqlx error variant that wraps over an sqlx::Error. Refer to the sqlite example to see the usage.

Case conversions

The Plectrum proc macro supports a rename_all attribute for specifying any case conversions while mapping the enum variant names in UpperCamel case (as per rust conventions) with the values in the lookup table.

Following case conversions are supported:

  • UPPER CASE
  • lower case
  • Title Case
  • camelCase
  • UpperCamelCase
  • snake_case
  • UPPER_SNAKE_CASE
  • kebab-case
  • UPPER-KEBAB-CASE
  • Train-Case
  • flatcase
  • UPPERFLATCASE

Note that the option names are autological.

If the rename_all attribute is not specified, the enum variant names themselves will be considered as values of the lookup table. In other words, the default case for values is UpperCamelCase, but only if the rust lang convention for enum variant names is followed.

Inconsistencies between enum and lookup table

Typically, an instance of the plectrum::Mapping struct would be created at the time of process initialization, which is when all the entries from the lookup table will be loaded in memory and mapped with the enum variants. At this time, it's possible to have inconsistencies between lookup table entries and enum variants. For example:

  1. There could be entries in the table for which an enum variants are not defined.

  2. There could enum variants for which entries don't exist in the lookup table

In such cases, the Mapping::load function would return plectrum::Error::NotDefinedInCode and plectrum::Error::NotFoundInDb errors respectively.

Because the mapping happens only once during process initialization, it implies that if an entry is added to or removed from the lookup table, the enum definition would also have to be modified accordingly (followed by restarting the process).

The above errors act as a safety net to catch any inconsistencies eagerly i.e. the process will fail to start, which in most cases is much better than errors at run time.

Examples

  1. simple: Simple example to understand the motivation behind this crate. It uses hard coded data instead of db based lookup table.

  2. sqlite: Using plectrum with an sqlite database using the sqlx

License

MIT (See LICENSE).

[^1]: Why the name plectrum? Earlier this lib was named lute, short for LookUp Tables mapped to Enums. But that name was already taken on crates.io. plectrum is a closely related and available name that came to my mind!

[^2]: Note that this crate DOES NOT provide an ORM-like functionality, so it's upto the user to write a query to fetch lookup table entries from the db.

Dependencies

~0–1.7MB
~29K SLoC