1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*!
Derive macro that allows structs and enums to be populated by database
queries.

This derive can be used on structures with named fields (which correspond
to "shapes" in EdgeDB). Note that field order matters, so the struct below
corresponds to an EdgeDB `User` query with `first_name` followed by `age`.
A `DescriptorMismatch` will be returned if the fields in the Rust struct
are not in the same order as those in the query shape.

```rust
# use edgedb_derive::Queryable;
#[derive(Queryable)]
struct User {
    first_name: String,
    age: i32,
}
```

This allows a query to directly unpack into the type instead
of working with the [Value](https://docs.rs/edgedb-protocol/latest/edgedb_protocol/value/enum.Value.html) enum.

```rust,ignore
let query = "select User { first_name, age };";
// With Queryable:
let query_res: Vec<User> = client.query(query, &()).await?;
// Without Queryable:
let query_res: Vec<Value> = client.query(query, &()).await?;
```

# Field attributes

## JSON

The `#[edgedb(json)]` attribute decodes a field using `serde_json` instead
of the EdgeDB binary protocol. This is useful if some data is stored in
the database as JSON, but you need to process it. The underlying type must
implement `serde::Deserialize`.

```rust
# use std::collections::HashMap;
# use edgedb_derive::Queryable;

#[derive(Queryable)]
struct User {
    #[edgedb(json)]
    user_notes: HashMap<String, String>,
}
```

# Container attributes

## JSON

The `#[edgedb(json)]` attribute can be used to unpack the structure from
the returned JSON.  The underlying type must implement
`serde::Deserialize`.

```rust
# use edgedb_derive::Queryable;
#[derive(Queryable, serde::Deserialize)]
#[edgedb(json)]
struct JsonData {
    field1: String,
    field2: u32,
}
```

This allows a query to directly unpack into the type without an intermediate
step using [serde_json::from_str](https://docs.rs/serde_json/latest/serde_json/fn.from_str.html):

```rust,ignore
let query = "select <json>JsonData { field1, field2 };";
let query_res: Vec<JsonData> = client.query(query, &()).await?;
```

*/
extern crate proc_macro;

use proc_macro::TokenStream;
use syn::parse_macro_input;

mod attrib;
mod enums;
mod json;
mod shape;
mod variables;

#[proc_macro_derive(Queryable, attributes(edgedb))]
pub fn edgedb_queryable(input: TokenStream) -> TokenStream {
    let s = parse_macro_input!(input as syn::Item);
    match derive(&s) {
        Ok(stream) => stream.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

fn derive(item: &syn::Item) -> syn::Result<proc_macro2::TokenStream> {
    let attrs = match item {
        syn::Item::Struct(s) => &s.attrs,
        syn::Item::Enum(e) => &e.attrs,
        _ => {
            return Err(syn::Error::new_spanned(
                item,
                "can only derive Queryable for structs and enums",
            ));
        }
    };
    let attrs = attrib::ContainerAttrs::from_syn(attrs)?;
    if attrs.json {
        json::derive(item)
    } else {
        match item {
            syn::Item::Struct(s) => shape::derive_struct(s),
            syn::Item::Enum(s) => enums::derive_enum(s),
            _ => Err(syn::Error::new_spanned(
                item,
                "can only derive Queryable for a struct and enum \
                     in non-JSON mode",
            )),
        }
    }
}

#[proc_macro_derive(GlobalsDelta, attributes(edgedb))]
pub fn globals_delta(input: TokenStream) -> TokenStream {
    let s = parse_macro_input!(input as syn::ItemStruct);
    match variables::derive_globals(&s) {
        Ok(stream) => stream.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

#[proc_macro_derive(ConfigDelta, attributes(edgedb))]
pub fn config_delta(input: TokenStream) -> TokenStream {
    let s = parse_macro_input!(input as syn::ItemStruct);
    match variables::derive_config(&s) {
        Ok(stream) => stream.into(),
        Err(e) => e.to_compile_error().into(),
    }
}