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
54
    match crate_name("bonsaidb")
27
54
        .or_else(|_| crate_name("bonsaidb_server"))
28
54
        .or_else(|_| crate_name("bonsaidb_local"))
29
54
        .or_else(|_| crate_name("bonsaidb_client"))
30
    {
31
54
        Ok(FoundCrate::Name(name)) => {
32
54
            let ident = Ident::new(&name, Span::call_site());
33
54
            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
54
}
52

            
53
572
#[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
55
pub fn collection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
91
    let DeriveInput {
92
55
        attrs,
93
55
        ident,
94
55
        generics,
95
        ..
96
55
    } = parse_macro_input!(input as DeriveInput);
97

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

            
111
55
    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
55
    }
114
55

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

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

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

            
121
55
    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
53
            let natural_id = natural_id.map(|natural_id| {
155
6
                quote!(
156
6
                    fn natural_id(&self) -> Option<Self::PrimaryKey> {
157
6
                        (#natural_id)(self)
158
6
                    }
159
6
                )
160
53
            });
161
53
            quote! {
162
53
                impl #impl_generics #core::schema::DefaultSerialization for #ident #ty_generics #where_clause {
163
53
                    #natural_id
164
53
                }
165
53
            }
166
        }
167
    };
168

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

            
174
55
    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
55
    });
192
55

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

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

            
211
473
#[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
36
pub fn view_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
244
    let DeriveInput {
245
36
        attrs,
246
36
        ident,
247
36
        generics,
248
        ..
249
36
    } = parse_macro_input!(input as DeriveInput);
250

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

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

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

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

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

            
275
36
    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
33
        None => quote! {
287
33
            impl #impl_generics #core::schema::DefaultViewSerialization for #ident #ty_generics #where_clause {}
288
33
        },
289
    };
290

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

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

            
306
186
#[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
15
pub fn schema_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
330
    let DeriveInput {
331
15
        attrs,
332
15
        ident,
333
15
        generics,
334
        ..
335
15
    } = parse_macro_input!(input as DeriveInput);
336

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

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

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

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

            
358
15
            fn define_collections(
359
15
                schema: &mut #core::schema::Schematic
360
15
            ) -> ::core::result::Result<(), #core::Error> {
361
15
                #( schema.define_collection::<#collections>()?; )*
362
15
                ::core::result::Result::Ok(())
363
15
            }
364
15
        }
365
15
    }
366
15
    .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
}