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}