Derive Macro exemplar::Model

source ·
#[derive(Model)]
{
    // Attributes available to this derive:
    #[table]
    #[check]
    #[bind]
    #[extr]
    #[column]
}
Expand description

Derive macro for the Model trait.

Requirements

Model can only be derived for non-generic structs with suitable named fields.

TypeExampleSupported?
Standard structstruct Person { name: String }
Generic structstruct Person<'a, T, const N: usize> { ... }
Tuple structstruct Point(i64, i64)
Unit/ZST structstruct Unit; or struct Unit {}
enumsenum Direction { Up, Down }
unionsunion Number { i: i32, f: f32 }

(Note, however, that any non-supported type can be used in a Model assuming it meets the below requirements.)

All fields in a Model derivee must either:

  • Implement ToSql and FromSql. Most common types will meet this requirement.
  • Have #[bind] and #[extr] attributes on fields that do not meet the first requirement.
    • This escape hatch is designed to enable compatibility with certain std types like PathBuf and third-party crate types.

Usage

Most of the time, deriving Model is easy. The only thing you need to specify is the table name:

#[derive(Model)]
#[table("people")] // <-- Required
pub struct Person {
    pub name: String,
    pub age: u16,
}

For more complicated types and schemas, you may need to make use of some of the attributes recognized by the macro:

#[derive(Model)]
#[table("users")]
#[check("schema.sql")]
struct User {
   username: String,
   #[bind(bind_path)]
   #[extr(extr_path)]
   home_dir: PathBuf,
   #[column("pwd")]
   password: Vec<u8>,
}

Attributes

The Model derive macro recognizes several attributes.

#[check]

Usage:

#[check("path_to_schema")]
pub struct T { ... }

The check attribute automatically generates a test that checks the derived Model implementation against a provided schema.

More specifically, the generated test verifies that:

  • The specified table exists.
  • The columns in the schema match up with those specified by the model type. If the model has fields not present in the schema (or vice versa) the test will fail.

It does not verify the validity of column types, nor does it test actual insertion/retrieval.

#[bind]/#[extr]

Usage:

#[bind(path::to::fn)]
#[extr(path::to::fn)]
field: T,

The bind and extr attributes specify functions used to convert the annotated field to and from an SQL-friendly representation. This is primarily intended as an escape hatch for when you can’t implement ToSql and FromSql yourself.

Both attributes take as their argument a path to a free function used to do the conversion.

In both cases T is the type of the field being annotated. For some types (e.g. PathBuf) you may also be able to use a type it derefs to, like Path.

Example implementations for PathBuf:

pub fn bind_path(value: &Path) -> BindResult {
    use rusqlite::types::Value;
    use rusqlite::types::ToSqlOutput;
     
    // Depending on your program, it may make more sense
    // to error if a lossless conversion isn't possible.
    let str = value.to_string_lossy().into_owned();
 
    Ok(ToSqlOutput::Owned(
        Value::Text(str)
    ))
}
 
pub fn extr_path(value: &ValueRef) -> ExtrResult<PathBuf> {
    let path = value.as_str()?;
    let path = PathBuf::from(path);
 
    Ok(path)
}

#[column]

Usage:

#[column("name")]
field: T,

The column attribute overrides the column name Exemplar maps the annotated field to. By default, the field name is assumed to directly map to the underlying schema - #[column] is how you alter this behavior.

Notes

Any type that derives Model also has an implementation of TryFrom<Row> derived, making models usable in some generic contexts.