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}