gel_derive/lib.rs
1/*!
2Derive macro that allows structs and enums to be populated by database
3queries.
4
5This derive can be used on structures with named fields (which correspond
6to "shapes" in Gel). Note that field order matters, so the struct below
7corresponds to an Gel `User` query with `first_name` followed by `age`.
8A `DescriptorMismatch` will be returned if the fields in the Rust struct
9are not in the same order as those in the query shape.
10
11```rust
12# use gel_derive::Queryable;
13#[derive(Queryable)]
14struct User {
15 first_name: String,
16 age: i32,
17}
18```
19
20This allows a query to directly unpack into the type instead
21of working with the [Value](https://docs.rs/gel-protocol/latest/gel_protocol/value/enum.Value.html) enum.
22
23```rust,ignore
24let query = "select User { first_name, age };";
25// With Queryable:
26let query_res: Vec<User> = client.query(query, &()).await?;
27// Without Queryable:
28let query_res: Vec<Value> = client.query(query, &()).await?;
29```
30
31# Field attributes
32
33## JSON
34
35The `#[gel(json)]` attribute decodes a field using `serde_json` instead
36of the Gel binary protocol. This is useful if some data is stored in
37the database as JSON, but you need to process it. The underlying type must
38implement `serde::Deserialize`.
39
40```rust
41# use std::collections::HashMap;
42# use gel_derive::Queryable;
43
44#[derive(Queryable)]
45struct User {
46 #[gel(json)]
47 user_notes: HashMap<String, String>,
48}
49```
50
51# Container attributes
52
53## JSON
54
55The `#[gel(json)]` attribute can be used to unpack the structure from
56the returned JSON. The underlying type must implement
57`serde::Deserialize`.
58
59```rust
60# use gel_derive::Queryable;
61#[derive(Queryable, serde::Deserialize)]
62#[gel(json)]
63struct JsonData {
64 field1: String,
65 field2: u32,
66}
67```
68
69This allows a query to directly unpack into the type without an intermediate
70step using [serde_json::from_str](https://docs.rs/serde_json/latest/serde_json/fn.from_str.html):
71
72```rust,ignore
73let query = "select <json>JsonData { field1, field2 };";
74let query_res: Vec<JsonData> = client.query(query, &()).await?;
75```
76
77*/
78extern crate proc_macro;
79
80use proc_macro::TokenStream;
81use syn::parse_macro_input;
82
83mod attrib;
84mod enums;
85mod json;
86mod shape;
87mod variables;
88
89#[proc_macro_derive(Queryable, attributes(gel))]
90pub fn queryable(input: TokenStream) -> TokenStream {
91 let s = parse_macro_input!(input as syn::Item);
92 match derive(&s) {
93 Ok(stream) => stream.into(),
94 Err(e) => e.to_compile_error().into(),
95 }
96}
97
98fn derive(item: &syn::Item) -> syn::Result<proc_macro2::TokenStream> {
99 let attrs = match item {
100 syn::Item::Struct(s) => &s.attrs,
101 syn::Item::Enum(e) => &e.attrs,
102 _ => {
103 return Err(syn::Error::new_spanned(
104 item,
105 "can only derive Queryable for structs and enums",
106 ));
107 }
108 };
109 let attrs = attrib::ContainerAttrs::from_syn(attrs)?;
110 if attrs.json {
111 json::derive(item, &attrs)
112 } else {
113 match item {
114 syn::Item::Struct(s) => shape::derive_struct(s, &attrs),
115 syn::Item::Enum(s) => enums::derive_enum(s, &attrs),
116 _ => Err(syn::Error::new_spanned(
117 item,
118 "can only derive Queryable for a struct and enum \
119 in non-JSON mode",
120 )),
121 }
122 }
123}
124
125#[proc_macro_derive(GlobalsDelta, attributes(gel))]
126pub fn globals_delta(input: TokenStream) -> TokenStream {
127 let s = parse_macro_input!(input as syn::ItemStruct);
128 match variables::derive_globals(&s) {
129 Ok(stream) => stream.into(),
130 Err(e) => e.to_compile_error().into(),
131 }
132}
133
134#[proc_macro_derive(ConfigDelta, attributes(gel))]
135pub fn config_delta(input: TokenStream) -> TokenStream {
136 let s = parse_macro_input!(input as syn::ItemStruct);
137 match variables::derive_config(&s) {
138 Ok(stream) => stream.into(),
139 Err(e) => e.to_compile_error().into(),
140 }
141}