1
1
//! Macros BonsaiDb.
2

            
3
#![forbid(unsafe_code)]
4
#![warn(
5
    clippy::cargo,
6
    missing_docs,
7
    // clippy::missing_docs_in_private_items,
8
    clippy::nursery,
9
    clippy::pedantic,
10
    future_incompatible,
11
    rust_2018_idioms,
12
)]
13
#![cfg_attr(doc, deny(rustdoc::all))]
14

            
15
use attribute_derive::Attribute;
16
use proc_macro2::{Span, TokenStream};
17
use proc_macro_crate::{crate_name, FoundCrate};
18
use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt};
19
use quote::{quote, ToTokens};
20
use syn::{
21
    parse_macro_input, parse_quote, punctuated::Punctuated, token::Paren, DeriveInput, Expr, Ident,
22
    LitStr, Path, Type, TypeTuple,
23
};
24

            
25
fn core_path() -> Path {
26
42
    match crate_name("bonsaidb")
27
42
        .or_else(|_| crate_name("bonsaidb_server"))
28
42
        .or_else(|_| crate_name("bonsaidb_local"))
29
42
        .or_else(|_| crate_name("bonsaidb_client"))
30
    {
31
42
        Ok(FoundCrate::Name(name)) => {
32
42
            let ident = Ident::new(&name, Span::call_site());
33
42
            parse_quote!(::#ident::core)
34
        }
35
        Ok(FoundCrate::Itself) => parse_quote!(crate::core),
36
        Err(_) => match crate_name("bonsaidb_core") {
37
            Ok(FoundCrate::Name(name)) => {
38
                let ident = Ident::new(&name, Span::call_site());
39
                parse_quote!(::#ident)
40
            }
41
            Ok(FoundCrate::Itself) => parse_quote!(crate),
42
            Err(_) => match () {
43
                () if cfg!(feature = "omnibus-path") => parse_quote!(::bonsaidb::core),
44
                () if cfg!(feature = "server-path") => parse_quote!(::bonsaidb_server::core),
45
                () if cfg!(feature = "local-path") => parse_quote!(::bonsaidb_local::core),
46
                () if cfg!(feature = "client-path") => parse_quote!(::bonsaidb_client::core),
47
                _ => parse_quote!(::bonsaidb_core),
48
            },
49
        },
50
    }
51
42
}
52

            
53
487
#[derive(Attribute)]
54
#[attribute(ident = "collection")]
55
#[attribute(
56
    invalid_field = r#"Only `authority = "some-authority"`, `name = "some-name"`, `views = [SomeView, AnotherView]`, `primary_key = u64`, `natural_id = |contents: &Self| Some(contents.id)`, serialization = SerializationFormat` and `core = bonsaidb::core` are supported attributes"#
57
)]
58
struct CollectionAttribute {
59
    authority: Option<String>,
60
    #[attribute(
61
        missing = r#"You need to specify the collection name via `#[collection(name = "name")]`"#
62
    )]
63
    name: String,
64
    #[attribute(default)]
65
    #[attribute(expected = r#"Specify the `views` like so: `view = [SomeView, AnotherView]`"#)]
66
    views: Vec<Type>,
67
    #[attribute(
68
        expected = r#"Specify the `serialization` like so: `serialization = Format` or `serialization = None` to disable deriving it"#
69
    )]
70
    serialization: Option<Path>,
71
    // TODO add some explanaition when it is possble to integrate the parse error in the printed
72
    // error message, for now the default error is probably more helpful
73
    encryption_key: Option<Expr>,
74
    encryption_required: bool,
75
    encryption_optional: bool,
76
    #[attribute(expected = r#"Specify the `primary_key` like so: `primary_key = u64`"#)]
77
    primary_key: Option<Type>,
78
    #[attribute(
79
        expected = r#"Specify the `natural_id` like so: `natural_id = function_name` or `natural_id = |doc| { .. }`"#
80
    )]
81
    natural_id: Option<Expr>,
82
    #[attribute(expected = r#"Specify the the path to `core` like so: `core = bosaidb::core`"#)]
83
    core: Option<Path>,
84
}
85

            
86
/// Derives the `bonsaidb::core::schema::Collection` trait.
87
#[proc_macro_error]
88
/// `#[collection(authority = "Authority", name = "Name", views = [a, b, c])]`
89
#[proc_macro_derive(Collection, attributes(collection))]
90
46
pub fn collection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
91
    let DeriveInput {
92
46
        attrs,
93
46
        ident,
94
46
        generics,
95
        ..
96
46
    } = parse_macro_input!(input as DeriveInput);
97

            
98
    let CollectionAttribute {
99
46
        authority,
100
46
        name,
101
46
        views,
102
46
        serialization,
103
46
        primary_key,
104
46
        natural_id,
105
46
        core,
106
46
        encryption_key,
107
46
        encryption_required,
108
46
        encryption_optional,
109
46
    } = CollectionAttribute::from_attributes(attrs).unwrap_or_abort();
110
46

            
111
46
    if encryption_required && encryption_key.is_none() {
112
        abort_call_site!("If `collection(encryption_required)` is set you need to provide an encryption key via `collection(encryption_key = EncryptionKey)`")
113
46
    }
114
46

            
115
46
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
116
46

            
117
46
    let core = core.unwrap_or_else(core_path);
118
46

            
119
46
    let primary_key = primary_key.unwrap_or_else(|| parse_quote!(u64));
120

            
121
46
    let serialization = match serialization {
122
2
        Some(serialization) if serialization.is_ident("None") => {
123
1
            if let Some(natural_id) = natural_id {
124
                abort!(
125
                    natural_id,
126
                    "`natural_id` must be manually implemented when using `serialization = None`"
127
                );
128
1
            }
129
1

            
130
1
            TokenStream::new()
131
        }
132
1
        Some(serialization) => {
133
1
            let natural_id = natural_id.map(|natural_id| {
134
                quote!(
135
                    fn natural_id(contents: &Self::Contents) -> Option<Self::PrimaryKey> {
136
                        #natural_id(contents)
137
                    }
138
                )
139
1
            });
140
1
            quote! {
141
1
                impl #impl_generics #core::schema::SerializedCollection for #ident #ty_generics #where_clause {
142
1
                    type Contents = #ident #ty_generics;
143
1
                    type Format = #serialization;
144
1

            
145
1
                    fn format() -> Self::Format {
146
1
                        #serialization::default()
147
1
                    }
148
1

            
149
1
                    #natural_id
150
1
                }
151
1
            }
152
        }
153
        None => {
154
44
            let natural_id = natural_id.map(|natural_id| {
155
3
                quote!(
156
3
                    fn natural_id(&self) -> Option<Self::PrimaryKey> {
157
3
                        (#natural_id)(self)
158
3
                    }
159
3
                )
160
44
            });
161
44
            quote! {
162
44
                impl #impl_generics #core::schema::DefaultSerialization for #ident #ty_generics #where_clause {
163
44
                    #natural_id
164
44
                }
165
44
            }
166
        }
167
    };
168

            
169
46
    let name = authority.map_or_else(
170
46
        || quote!(#core::schema::CollectionName::private(#name)),
171
46
        |authority| quote!(#core::schema::CollectionName::new(#authority, #name)),
172
46
    );
173
46

            
174
46
    let encryption = encryption_key.map(|encryption_key| {
175
11
        let encryption = if encryption_required || !encryption_optional {
176
2
            encryption_key.into_token_stream()
177
        } else {
178
9
            quote! {
179
9
                if #core::ENCRYPTION_ENABLED {
180
9
                    #encryption_key
181
9
                } else {
182
9
                    ::core::option::Option::None
183
9
                }
184
9
            }
185
        };
186
11
        quote! {
187
11
            fn encryption_key() -> ::core::option::Option<#core::document::KeyId> {
188
11
                #encryption
189
11
            }
190
11
        }
191
46
    });
192
46

            
193
46
    quote! {
194
46
        impl #impl_generics #core::schema::Collection for #ident #ty_generics #where_clause {
195
46
            type PrimaryKey = #primary_key;
196
46

            
197
46
            fn collection_name() -> #core::schema::CollectionName {
198
46
                #name
199
46
            }
200
46
            fn define_views(schema: &mut #core::schema::Schematic) -> ::core::result::Result<(), #core::Error> {
201
46
                #( schema.define_view(#views)?; )*
202
46
                ::core::result::Result::Ok(())
203
46
            }
204
46
            #encryption
205
46
        }
206
46
        #serialization
207
46
    }
208
46
    .into()
209
}
210

            
211
403
#[derive(Attribute)]
212
#[attribute(ident = "view")]
213
#[attribute(
214
    invalid_field = r#"Only `collection = CollectionType`, `key = KeyType`, `name = "by-name"`, `value = ValueType` and `serialization = SerializationFormat` and `core = bonsaidb::core` are supported attributes"#
215
)]
216
struct ViewAttribute {
217
    #[attribute(
218
        missing = r#"You need to specify the collection type via `#[view(collection = CollectionType)]`"#
219
    )]
220
    #[attribute(
221
        expected = r#"Specify the collection type like so: `collection = CollectionType`"#
222
    )]
223
    collection: Type,
224
    #[attribute(missing = r#"You need to specify the key type via `#[view(key = KeyType)]`"#)]
225
    #[attribute(expected = r#"Specify the key type like so: `key = KeyType`"#)]
226
    key: Type,
227
    name: Option<LitStr>,
228
    #[attribute(expected = r#"Specify the value type like so: `value = ValueType`"#)]
229
    value: Option<Type>,
230
    #[attribute(expected = r#"Specify the the path to `core` like so: `core = bosaidb::core`"#)]
231
    core: Option<Path>,
232
    #[attribute(
233
        expected = r#"Specify the `serialization` like so: `serialization = Format` or `serialization = None` to disable deriving it"#
234
    )]
235
    serialization: Option<Path>,
236
}
237

            
238
/// Derives the `bonsaidb::core::schema::View` trait.
239
#[proc_macro_error]
240
/// `#[view(collection=CollectionType, key=KeyType, value=ValueType, name = "by-name")]`
241
/// `name` and `value` are optional
242
#[proc_macro_derive(View, attributes(view))]
243
30
pub fn view_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
244
    let DeriveInput {
245
30
        attrs,
246
30
        ident,
247
30
        generics,
248
        ..
249
30
    } = parse_macro_input!(input as DeriveInput);
250

            
251
    let ViewAttribute {
252
30
        collection,
253
30
        key,
254
30
        name,
255
30
        value,
256
30
        core,
257
30
        serialization,
258
30
    } = ViewAttribute::from_attributes(attrs).unwrap_or_abort();
259
30

            
260
30
    let core = core.unwrap_or_else(core_path);
261
30

            
262
30
    let value = value.unwrap_or_else(|| {
263
3
        Type::Tuple(TypeTuple {
264
3
            paren_token: Paren::default(),
265
3
            elems: Punctuated::new(),
266
3
        })
267
30
    });
268
30

            
269
30
    let name = name
270
30
        .as_ref()
271
30
        .map_or_else(|| ident.to_string(), LitStr::value);
272
30

            
273
30
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
274

            
275
30
    let serialization = match serialization {
276
3
        Some(serialization) if serialization.is_ident("None") => TokenStream::new(),
277
1
        Some(serialization) => quote! {
278
1
            impl #impl_generics #core::schema::SerializedView for #ident #ty_generics #where_clause {
279
1
                type Format = #serialization;
280
1

            
281
1
                fn format() -> Self::Format {
282
1
                    #serialization::default()
283
1
                }
284
1
            }
285
1
        },
286
27
        None => quote! {
287
27
            impl #impl_generics #core::schema::DefaultViewSerialization for #ident #ty_generics #where_clause {}
288
27
        },
289
    };
290

            
291
30
    quote! {
292
30
        impl #impl_generics #core::schema::View for #ident #ty_generics #where_clause {
293
30
            type Collection = #collection;
294
30
            type Key = #key;
295
30
            type Value = #value;
296
30

            
297
30
            fn name(&self) -> #core::schema::Name {
298
30
                #core::schema::Name::new(#name)
299
30
            }
300
30
        }
301
30
        #serialization
302
30
    }
303
30
    .into()
304
}
305

            
306
163
#[derive(Attribute)]
307
#[attribute(ident = "schema")]
308
#[attribute(
309
    invalid_field = r#"Only `name = "name""`, `authority = "authority"`, `collections = [SomeCollection, AnotherCollection]` and `core = bonsaidb::core` are supported attributes"#
310
)]
311
struct SchemaAttribute {
312
    #[attribute(missing = r#"You need to specify the schema name via `#[schema(name = "name")]`"#)]
313
    name: String,
314
    authority: Option<String>,
315
    #[attribute(default)]
316
    #[attribute(
317
        expected = r#"Specify the `collections` like so: `collections = [SomeCollection, AnotherCollection]`"#
318
    )]
319
    collections: Vec<Type>,
320
    #[attribute(expected = r#"Specify the the path to `core` like so: `core = bosaidb::core`"#)]
321
    core: Option<Path>,
322
}
323

            
324
/// Derives the `bonsaidb::core::schema::Schema` trait.
325
#[proc_macro_error]
326
/// `#[schema(name = "Name", authority = "Authority", collections = [A, B, C]), core = bonsaidb::core]`
327
/// `authority`, `collections` and `core` are optional
328
#[proc_macro_derive(Schema, attributes(schema))]
329
12
pub fn schema_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
330
    let DeriveInput {
331
12
        attrs,
332
12
        ident,
333
12
        generics,
334
        ..
335
12
    } = parse_macro_input!(input as DeriveInput);
336

            
337
    let SchemaAttribute {
338
12
        name,
339
12
        authority,
340
12
        collections,
341
12
        core,
342
12
    } = SchemaAttribute::from_attributes(attrs).unwrap_or_abort();
343
12

            
344
12
    let core = core.unwrap_or_else(core_path);
345
12
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
346
12

            
347
12
    let name = authority.map_or_else(
348
12
        || quote!(#core::schema::SchemaName::private(#name)),
349
12
        |authority| quote!(#core::schema::SchemaName::new(#authority, #name)),
350
12
    );
351
12

            
352
12
    quote! {
353
12
        impl #impl_generics #core::schema::Schema for #ident #ty_generics #where_clause {
354
12
            fn schema_name() -> #core::schema::SchemaName {
355
12
                #name
356
12
            }
357
12

            
358
12
            fn define_collections(
359
12
                schema: &mut #core::schema::Schematic
360
12
            ) -> ::core::result::Result<(), #core::Error> {
361
12
                #( schema.define_collection::<#collections>()?; )*
362
12
                ::core::result::Result::Ok(())
363
12
            }
364
12
        }
365
12
    }
366
12
    .into()
367
}
368

            
369
1
#[test]
370
1
fn ui() {
371
1
    use trybuild::TestCases;
372
1

            
373
1
    TestCases::new().compile_fail("tests/ui/*/*.rs");
374
1
}