Skip to content

Commit

Permalink
better parsing and errors with attribute-derive
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed Feb 4, 2022
1 parent fc26b53 commit 382273a
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 99 deletions.
134 changes: 45 additions & 89 deletions crates/bonsaidb-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,37 @@

use attribute_derive::Attribute;
use proc_macro2::TokenStream;
use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt};
use proc_macro_error::{proc_macro_error, ResultExt};
use quote::quote;
use syn::{
parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Paren, DeriveInput, Lit,
LitStr, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, TypeTuple,
parse_macro_input, punctuated::Punctuated, token::Paren, DeriveInput, LitStr, Path, Type,
TypeTuple,
};

#[derive(Attribute)]
#[attribute(ident = "collection")]
#[attribute(
invalid_field = r#"Only `authority = "some-authority"`, `name = "some-name"`, `views = [SomeView, AnotherView]`, `serialization = Serialization` are supported attributes"#
)]
struct CollectionAttribute {
#[attribute(
missing = r#"You need to specify the collection authority via `#[collection(authority = "authority")]`"#
)]
authority: String,
#[attribute(
missing = r#"You need to specify the collection name via `#[collection(name = "name")]`"#
)]
name: String,
#[attribute(default)]
#[attribute(expected = r#"Specify the `views` like so: `view = [SomeView, AnotherView]`"#)]
views: Vec<Type>,
#[attribute(expected = r#"Specify the `serialization` like so: `serialization = Format` or `serialization = None` to disable deriving it"#)]
serialization: Option<Path>,
}

/// Derives the `bonsaidb::core::schema::Collection` trait.
#[proc_macro_error]
/// `#[collection(authority = "Authority", name = "Name", views(a, b, c))]`
/// `#[collection(authority = "Authority", name = "Name", views = [a, b, c])]`
#[proc_macro_derive(Collection, attributes(collection))]
pub fn collection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let DeriveInput {
Expand All @@ -33,86 +54,12 @@ pub fn collection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStr
..
} = parse_macro_input!(input as DeriveInput);

let mut name: Option<String> = None;
let mut authority: Option<String> = None;
let mut views: Vec<Path> = Vec::new();
let mut serialization: Option<Path> = None;

for attibute in attrs {
if attibute.path.is_ident("collection") {
if let Ok(Meta::List(MetaList { nested, .. })) = attibute.parse_meta() {
for item in nested {
let span = item.span();
match item {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(value),
..
})) if path.is_ident("name") => name = Some(value.value()),
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(value),
..
})) if path.is_ident("authority") => authority = Some(value.value()),
NestedMeta::Meta(Meta::List(MetaList { path, nested, .. }))
if path.is_ident("serialization") =>
{
match nested.len() {
0 => abort!(
span,
r#"You need to pass either a format type or `None` to `serialization`: `serialization(Format)`"#,
),
2.. => abort!(
span,
r#"You can only specify a single format with `serialization` like so: `serialization(Format)`"#,
),
_ => (),
}
serialization = nested
.into_iter()
.map(|meta| match meta {
NestedMeta::Meta(Meta::Path(path)) => path,
meta => abort!(
meta.span(),
r#"`{}` is not supported here, call `serialization` like so: `serialization(Format)`"#
),
}).next();
}
NestedMeta::Meta(Meta::List(MetaList { path, nested, .. }))
if path.is_ident("views") =>
{
views = nested
.into_iter()
.map(|meta| match meta {
NestedMeta::Meta(Meta::Path(path)) => path,
meta => abort!(
meta.span(),
r#"`{}` is not supported here, call `views` like so: `views(SomeView, AnotherView)`"#
),
})
.collect();
}
item => abort!(
item.span(),
r#"Only `authority="some-authority"`, `name="some-name"`, `views(SomeView, AnotherView)` are supported attributes"#
),
}
}
}
}
}

let authority = authority.unwrap_or_else(|| {
abort_call_site!(
r#"You need to specify the collection name via `#[collection(authority="authority")]`"#
)
});

let name = name.unwrap_or_else(|| {
abort_call_site!(
r#"You need to specify the collection authority via `#[collection(name="name")]`"#
)
});
let CollectionAttribute {
authority,
name,
views,
serialization,
} = CollectionAttribute::from_attributes(attrs).unwrap_or_abort();

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

Expand Down Expand Up @@ -148,20 +95,29 @@ pub fn collection_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStr
.into()
}

#[derive(Debug, Clone, Attribute)]
#[attribute(view)]
#[derive(Attribute)]
#[attribute(ident = "view")]
#[attribute(
invalid_field = r#"Only `collection = CollectionType`, `key = KeyType`, `name = "by-name"`, `value = ValueType` are supported attributes"#
)]
struct ViewAttribute {
#[attribute(
missing = r#"You need to specify the collection type via `#[view(collection = CollectionType)]`"#
)]
#[attribute(expected = r#"Specify the collection type like so: `collection = CollectionType`"#)]
collection: Type,
#[attribute(missing = r#"You need to specify the key type via `#[view(key = KeyType)]`"#)]
#[attribute(expected = r#"Specify the key type like so: `key = KeyType`"#)]
key: Type,
#[attribute(default)]
name: Option<LitStr>,
#[attribute(default)]
#[attribute(expected = r#"Specify the value type like so: `value = ValueType`"#)]
value: Option<Type>,
}

/// Derives the `bonsaidb::core::schema::View` trait.
#[proc_macro_error]
/// `#[view(collection(CollectionType), key(KeyType), value(ValueType), name = "by-name", version = 0)]`
/// `#[view(collection=CollectionType, key=KeyType, value=ValueType, name = "by-name")]`
/// `name` and `value` are optional
#[proc_macro_derive(View, attributes(view))]
pub fn view_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let DeriveInput {
Expand Down
6 changes: 3 additions & 3 deletions crates/bonsaidb-macros/tests/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn views() {
use serde::{Deserialize, Serialize};

#[derive(Collection, Debug, Serialize, Deserialize)]
#[collection(name = "Name", authority = "Authority", views(ShapesByNumberOfSides))]
#[collection(name = "Name", authority = "Authority", views = [ShapesByNumberOfSides])]
struct Shape {
pub sides: u32,
}
Expand Down Expand Up @@ -62,7 +62,7 @@ fn serialization() {
#[collection(
name = "Name",
authority = "Authority",
serialization(transmog_bincode::Bincode)
serialization = transmog_bincode::Bincode
)]
struct Test;

Expand All @@ -80,7 +80,7 @@ fn serialization_none() {
use serde::{Deserialize, Serialize};

#[derive(Collection, Debug, Deserialize, Serialize)]
#[collection(name = "Name", authority = "Authority", serialization(None))]
#[collection(name = "Name", authority = "Authority", serialization = None)]
struct Test;

impl DefaultSerialization for Test {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ use bonsaidb::core::schema::Collection;
#[collection(name = "hi", authority = "hello", "hi")]
struct Test;

#[derive(Collection)]
#[collection(name = "hi", authority = "hello", field = 200)]
struct Test2;

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
error: Only `authority="some-authority"`, `name="some-name"`, `views(SomeView, AnotherView)` are supported attributes
error: expected identifier
--> tests/ui/collection/invalid_attribute.rs:4:48
|
4 | #[collection(name = "hi", authority = "hello", "hi")]
| ^^^^

error: Only `authority = \"some-authority\"`, `name = \"some-name\"`, `views = [SomeView, AnotherView]`, `serialization = Serialization` are supported attributes
--> tests/ui/collection/invalid_attribute.rs:8:48
|
8 | #[collection(name = "hi", authority = "hello", field = 200)]
| ^^^^^
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: You need to specify the collection name via `#[collection(authority="authority")]`
error: You need to specify the collection authority via `#[collection(authority = \"authority\")]`
--> tests/ui/collection/missing_authority.rs:3:10
|
3 | #[derive(Collection)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: You need to specify the collection authority via `#[collection(name="name")]`
error: You need to specify the collection name via `#[collection(name = \"name\")]`
--> tests/ui/collection/missing_name.rs:3:10
|
3 | #[derive(Collection)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Supported fields are `collection`, `key`, `name` and `value`
error: Only `collection = CollectionType`, `key = KeyType`, `name = \"by-name\"`, `value = ValueType` are supported attributes
--> tests/ui/view/invalid_attribute.rs:4:21
|
4 | #[view(name = "hi", authority = "hello", "hi")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Mandatory `collection` was not specified via the attributes.
error: You need to specify the collection type via `#[view(collection = CollectionType)]`
--> tests/ui/view/missing_collection.rs:3:10
|
3 | #[derive(View)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bonsaidb-macros/tests/ui/view/missing_key.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Mandatory `key` was not specified via the attributes.
error: You need to specify the key type via `#[view(key = KeyType)]`
--> tests/ui/view/missing_key.rs:3:10
|
3 | #[derive(View)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bonsaidb-macros/tests/ui/view/missing_name.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: You need to specify the collection authority via `#[collection(name="name")]`
error: You need to specify the collection name via `#[collection(name = \"name\")]`
--> tests/ui/view/missing_name.rs:3:10
|
3 | #[derive(Collection)]
Expand Down

0 comments on commit 382273a

Please sign in to comment.