diesel_json_derive/
lib.rs

1//! __NOTE:__ This is under active development. No guarantees for stability or usability. You probably want [diesel_json](https://crates.io/crates/diesel_json) instead. Please also note that this currently expects postgres. Pull requests to support other backends are welcome.
2//!
3//! ## diesel_json_derive
4//!
5//! What's this? This is a procedural macro that automatically derives `ToSql` and `FromSql` for Diesel's `Jsonb` type.
6//!
7//! Consider a table like
8//!
9//! ```sql
10//! CREATE TABLE foo (
11//!   id TEXT PRIMARY KEY,
12//!   bar JSONB NOT NULL
13//! );
14//! ```
15//!
16//! which is in Rust can be represented as as (does not compile!):
17//!
18//! ```rust
19//! #[derive(Debug, Queryable, Identifiable, Insertable, AsChangeset, Selectable)]
20//! #[diesel(table_name = crate::schema::foo)]
21//! #[diesel(check_for_backend(diesel::pg::Pg))]
22//! #[diesel(primary_key(id))]
23//! struct Foo {
24//!     id: String,
25//!     bar: Bar,
26//! }
27//!
28//! struct Bar {
29//!     x: i32,
30//! }
31//! ```
32//!
33//! In order to make `Bar` be represented as a jsonb blob you will need to implement the `diesel::deserialize::FromSql` and `diesel::deserialize::FromSql` traits, e.g. like this:
34//!
35//! ```rust
36//! impl ToSql<Jsonb, Pg> for Foo {
37//!     fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
38//!         out.write_all(&[1])?;
39//!         serde_json::to_writer(out, &self)?;
40//!         Ok(serialize::IsNull::No)
41//!     }
42//! }
43//!
44//! impl FromSql<Jsonb, Pg> for Foo {
45//!     fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
46//!         let bytes = bytes.as_bytes();
47//!         if bytes[0] != 1 {
48//!             return Err("Unsupported JSONB encoding version".into());
49//!         }
50//!         serde_json::from_slice(&bytes[1..]).map_err(|_| "Invalid Json".into())
51//!     }
52//! }
53//!
54//! ```
55//!
56//! This gets tedious quickly so this create does it for you. So with this crate you can write:
57//!
58//! ```rust
59//! use diesel::sql_types::Jsonb;
60//! use diesel::{FromSqlRow, AsExpression};
61//! use diesel_json_derive::DieselJsonb;
62//! use serde::{Deserialize, Serialize};
63//!
64//! #[derive(Debug, Serialize, Deserialize, AsExpression, FromSqlRow, DieselJsonb)]
65//! #[diesel(sql_type = Jsonb)]
66//! struct Bar {
67//!     x: i32,
68//! }
69//! ```
70//!
71//! ### diesel_json_derive vs ## diesel_json
72//!
73//! The [diesel_json](https://crates.io/crates/diesel_json) crate solves the
74//! same problem but uses a wrapper type for it. This has the disadvantage that
75//! this type needs to be used when matching for example. This crate does not
76//! have this disadvantage.
77
78
79
80use heck::ToSnakeCase;
81use quote::quote;
82use syn::{parse_macro_input, DeriveInput, Ident};
83
84#[proc_macro_derive(DieselJsonb)]
85pub fn diesel_jsonb_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
86    let input = parse_macro_input!(input as DeriveInput);
87
88    let type_name = input.ident;
89    let mod_name = format!("{}_diesel_jsonb", type_name.to_string().to_snake_case());
90    let mod_name = Ident::new(&mod_name, type_name.span());
91
92    (quote! {
93        mod #mod_name {
94            use super::#type_name;
95
96            use diesel::deserialize::{self, FromSql};
97            use diesel::pg::{Pg, PgValue};
98            use diesel::serialize::{self, ToSql};
99            use diesel::sql_types::*;
100            use std::io::Write;
101
102            impl ToSql<Jsonb, Pg> for #type_name {
103                fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
104                    out.write_all(&[1])?;
105                    serde_json::to_writer(out, &self)?;
106                    Ok(serialize::IsNull::No)
107                }
108            }
109
110            impl FromSql<Jsonb, Pg> for #type_name {
111                fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
112                    let bytes = bytes.as_bytes();
113                    if bytes[0] != 1 {
114                        return Err("Unsupported JSONB encoding version".into());
115                    }
116                    serde_json::from_slice(&bytes[1..]).map_err(|_| "Invalid Json".into())
117                }
118            }
119        }
120    }).into()
121}