# `diesel-derive-newtype`

Easy-peasy support of newtypes inside of Diesel.

[![Rust](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml/badge.svg?branch=diesel-2)](https://github.com/quodlibetor/diesel-derive-newtype/actions/workflows/test.yml) [![Crates.io Version](https://img.shields.io/crates/v/diesel-derive-newtype.svg)](https://crates.io/crates/diesel-derive-newtype)

### Status

diesel-derive-newtype is small and basically complete -- except for some features
that aren't supported by Diesel (see "limitations", below). Additionally, I don't
use diesel on a daily basis any more, so most maintenance comes from bug reports
or PRs, please open them!

## Installation

diesel-derive-newtype supports Diesel according to its major version -- 0.x
through 1.x support the corresponding diesel versions, 2.0 supports diesel 2.0,
and 2.1 supports 2.1. Generally new versions of diesel-derive-newtype are
released when compilation or tests are observed to fail with a newer version
of diesel, so newer versions of diesel than the released version of
diesel-derive-newtype *may* work. Bug reports and PRs welcome.

New features are only developed for the currently supported version of Diesel.


```toml
[dependencies]
diesel-derive-newtype = "2.1.0"
```

for Diesel 2.0.x you have to tell cargo not to upgrade diesel-derive-newtype:

```toml
[dependencies]
diesel-derive-newtype = "~ 2.0.0"
```

And for Diesel 1.x:

```toml
[dependencies]
diesel-derive-newtype = "1.0"
```



## `#[derive(DieselNewType)]`

This crate exposes a single custom-derive macro `DieselNewType` which
implements `ToSql`, `FromSql`, `FromSqlRow`, `Queryable`, `AsExpression`
and `QueryId` for the single-field tuple struct ([NewType][]) it is applied
to.

The goal of this project is that:

* `derive(DieselNewType)` should be enough for you to use newtypes anywhere you
  would use their underlying types within Diesel. (plausibly successful)
* Should get the same compile-time guarantees when using your newtypes as
  expression elements in Diesel as you do in other rust code (depends on
  your desires, see [Limitations][], below.)

[NewType]: https://aturon.github.io/features/types/newtype.html

## Example

This implementation:

```rust
#[macro_use]
extern crate diesel_derive_newtype;

#[derive(DieselNewType)] // Doesn't need to be on its own line
#[derive(Debug, Hash, PartialEq, Eq)] // required by diesel
struct MyId(i64);
```

Allows you to use the `MyId` struct inside your entities as though they were
the underlying type:

```rust
table! {
    my_items {
        id -> Integer,
        val -> Integer,
    }
}

#[derive(Debug, PartialEq, Identifiable, Queryable)]
struct MyItem {
    id: MyId,
    val: u8,
}
```

Oooohhh. Ahhhh.

See [the tests][] for a more complete example.

[the tests]: https://github.com/quodlibetor/diesel-derive-newtype/blob/master/tests/db-roundtrips.rs

## Upholding invariants when reading (`try_from`)

By default the derive builds your newtype by wrapping the value read from
the database directly, which means an invalid value in the database becomes
an invalid instance of your type. If your newtype has invariants (for
example a private field that only accepts some values), add
`#[diesel_newtype(try_from = InnerType)]`. The read path (`FromSql` and
`Queryable`) will then deserialize `InnerType` and call `.try_into()` to
build your newtype, so construction can fail:

```rust
#[derive(Debug, PartialEq, Eq, Hash, DieselNewType)]
#[diesel_newtype(try_from = i32)]
pub struct Even(i32); // private inner field upholds an invariant; try_from applies it on DB reads

#[derive(Debug)]
pub struct NotEven(i32);
impl std::error::Error for NotEven {}

impl TryFrom<i32> for Even {
    type Error = NotEven;
    fn try_from(v: i32) -> Result<Self, NotEven> {
        if v % 2 == 0 { Ok(Even(v)) } else { Err(NotEven(v)) }
    }
}
```

Notes:

* The conversion accepts any type reachable via `.try_into()`, so an
  infallible `From<InnerType>` works too (its `TryFrom` error is
  `Infallible`).
* The `TryFrom` error is preserved, not stringified: it must be convertible
  into `Box<dyn std::error::Error + Send + Sync>` (i.e. implement
  `std::error::Error + Send + Sync + 'static`).
* Only the read path is affected. The write path (`ToSql`, `AsExpression`)
  always serializes the inner field directly, so no `Clone`/`Into` bound is
  required and borrowed inserts keep working.
* This does *not* close the [type hole](#limitations) below: you can still
  filter a column by the raw underlying SQL type.

## Limitations
[limitations]: #limitations

The `DieselNewtype` derive does not create new _database_ types, or Diesel
serialization types. That is, if you have a `MyId(i64)`, this will use
Diesel's underlying `BigInt` type, which means that even though your
newtypes can be used anywhere the underlying type can be used, *the
underlying types, or any other newtypes of the same underlying type, can be
used as well*.

At a certain point everything does become bits on the wire, so if we didn't
do it this way then Diesel would have to do it somewhere else, and this is
reasonable default behavior (it's pretty debuggable), but I'm investigating
auto-generating new proxy types as well to make it impossible to construct
an insert statement using a tuple or a mis-typed struct.

Here's an example of that this type-hole looks like:

```rust
#[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
struct OneId(i64);

#[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
struct OtherId(i64);

#[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)]
#[diesel(table_name = my_entities)]
pub struct MyEntity {
    id: OneId,
    val: i32,
}

fn darn(conn: &Connection) {
    // shouldn't allow constructing the wrong type, but does
    let OtherId: Vec<OtherId> = my_entities
        .select(id)
        .filter(id.eq(OtherId(1)))  // shouldn't allow filtering by wrong type
        .execute(conn).unwrap();
}
```

See [`tests/should-not-compile.rs`](tests/should-not-compile.rs) for the
things I think should fail to compile.

I believe that the root cause of this is that Diesel implements the various
expression methods for types that implement `AsExpression`, based on the
_SQL_ type, not caring about `self` and `other`'s Rust type matching. That
seems like a pretty good decision in general, but it is a bit unfortunate
here.

I hope to find a solution that doesn't involve implementing every
`*Expression` trait manually with an extra bound, but for now you have to
keep in mind that the Diesel methods basically auto-transmute your data into
the underlying SQL type.


## Releasing

This workflow uses:

* [cargo-readme](https://crates.io/crates/cargo-readme)
* [cargo-release](https://crates.io/crates/cargo-release)

Run, note that we always release patch releases unless diesel has got a new
release:

```
cargo readme > README.md
git diff --exit-code --quiet README.* || (git add README.* && git commit -m "chore: Update README")
cargo release patch
```

## License

diesel-derive-newtype is licensed under either of

 * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
   http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or
   http://opensource.org/licenses/MIT)

at your option.

Patches and bug reports welcome!
