Diesel with custom wrapper types

Viewed 1126

I have custom types that serve as a wrapper of other types than can be safely used in Diesel:

use uuid::Uuid;

pub schema Post {
  id: PostId,
  title: String,
  body: String
}

pub schema PostId {value: Uuid}

I can't use these custom wrappers with Diesel. The error message I get is the following:

#[derive(Insertable)]
the trait `diesel::Expression` is not implemented for `models::PostId`

I've tried to look for examples on converting custom types and so far the two approaches I've seen are to either implement the AsExpression trait or the FromSql and ToSql traits, but the examples I've seen so far are for enum types and I can't infer what's the difference between the two approaches other than the former seems to be an older way of doing it, nor what the expected implementation of those traits are.

2 Answers

Based on weiznich's answer, I came up with the following solution for serializand/deserializing my custom wrapper type:

use uuid::Uuid;
use super::schema::posts;
use diesel::serialize::{self, IsNull, Output, ToSql};
use std::io::Write;
use diesel::deserialize::{self, FromSql};
use diesel::backend::Backend;
use diesel::pg::Pg;

#[derive(Queryable, Debug)]
pub struct Post {
    pub id: PostId,
    pub body: String,
    pub title: String
}

#[derive(Insertable)]
#[table_name="posts"]
pub struct NewPost<'a> {
    pub id: PostId,
    pub body: &'a str,
    pub title: &'a str
}

#[derive(AsExpression, FromSqlRow, Debug)]
#[sql_type = "diesel::sql_types::Uuid"]
pub struct PostId{pub value: Uuid}

impl<DB: Backend<RawValue=[u8]>> FromSql<diesel::sql_types::Uuid, DB> for PostId {
  fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
    <uuid::Uuid as FromSql<diesel::sql_types::Uuid, Pg>>::from_sql(bytes).map(|value| PostId{ value })
  }
}

impl ToSql<diesel::sql_types::Uuid, Pg> for PostId {
  fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
    <uuid::Uuid as ToSql<diesel::sql_types::Uuid, Pg>>::to_sql(&self.value, out)
  }
}

You need to implement those three mentioned traits + a few more. AsExpression + FromSqlRow are implemented by the corresponding derives. FromSql and ToSql require a manual impl.

use diesel::deserialize::{self, FromSql};
use diesel::pg::Pg;
use diesel::serialize::{self, ToSql};
use std::io::Write;

#[derive(AsExpression, FromSqlRow)]
#[sql_type = "diesel::sql_types::Uuid"]
pub schema PostId {
    value: uuid::Uuid,
}

impl FromSql<diesel::sql_types::Uuid, Pg> for PostId {
    fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
        <uuid::Uuid as FromSql<diesel::sql_types::Uuid, Pg>>::from_sql(bytes)
            .map(|value| PostId { value })
    }
}

impl ToSql<diesel::sql_types::Uuid, Pg> for PostId {
    fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
        <uuid::Uuid as ToSql<diesel::sql_types::Uuid, Pg>>::to_sql(&self.0, out)
    }
}
Related