Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support deriving Attribute trait for newtype structs #145

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 59 additions & 11 deletions dynomite-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use proc_macro2::Span;
use proc_macro_error::{abort, ResultExt};
use quote::{quote, ToTokens};
use syn::{
parse::Parse, punctuated::Punctuated, Attribute, DataStruct, DeriveInput, Field, Fields, Ident,
Token, Visibility,
parse::Parse, punctuated::Punctuated, Attribute, DataStruct, DeriveInput, Field, Fields,
FieldsUnnamed, Ident, Token, Visibility,
};

struct Variant {
Expand Down Expand Up @@ -324,7 +324,8 @@ pub fn derive_attributes(input: TokenStream) -> TokenStream {
expand_attributes(ast).unwrap_or_else(|e| e.to_compile_error().into())
}

/// Derives `dynomite::Attribute` for enum types
/// Derives `dynomite::Attribute` for enum and single-field tuple struct (newtype) types that wrap
/// other `dynomite::Attribute` types.
///
/// # Panics
///
Expand All @@ -333,17 +334,33 @@ pub fn derive_attributes(input: TokenStream) -> TokenStream {
#[proc_macro_derive(Attribute)]
pub fn derive_attribute(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input);
let gen = expand_attribute(ast);
gen.into_token_stream().into()
expand_attribute(ast).unwrap_or_else(|e| e.to_compile_error().into())
}

fn expand_attribute(ast: DeriveInput) -> impl ToTokens {
fn expand_attribute(ast: DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;

match ast.data {
syn::Data::Enum(variants) => {
make_dynomite_attr(name, &variants.variants.into_iter().collect::<Vec<_>>())
}
_ => panic!("Dynomite Attributes can only be generated for enum types"),
syn::Data::Enum(variants) => Ok(make_dynomite_attr_for_enum(
name,
&variants.variants.into_iter().collect::<Vec<_>>(),
)
.to_token_stream()
.into()),
syn::Data::Struct(DataStruct {
fields: Fields::Unnamed(FieldsUnnamed { unnamed, .. }),
..
}) if unnamed.len() == 1 => Ok(make_dynomite_attr_for_newtype_struct(
name,
unnamed.first().expect("first field should exist"),
)
.to_token_stream()
.into()),
_ => Err(syn::Error::new(
ast.ident.span(),
"Dynomite Attributes can only be generated for enum and single-field tuple struct \
(newtype) types",
)),
}
}

Expand All @@ -367,7 +384,7 @@ fn expand_attribute(ast: DeriveInput) -> impl ToTokens {
/// }
/// }
/// ```
fn make_dynomite_attr(
fn make_dynomite_attr_for_enum(
name: &Ident,
variants: &[syn::Variant],
) -> impl ToTokens {
Expand Down Expand Up @@ -408,6 +425,37 @@ fn make_dynomite_attr(
}
}

/// ```rust,ignore
/// impl ::dynomite::Attribute for Name {
/// fn into_attr(self) -> ::dynomite::dynamodb::AttributeValue {
/// self.0.into_attr()
/// }
/// fn from_attr(
/// value: ::dynomite::dynamodb::AttributeValue,
/// ) -> ::std::result::Result<Self, ::dynomite::AttributeError> {
/// FieldType::from_attr(value).map(Self)
/// }
/// }
/// ```
fn make_dynomite_attr_for_newtype_struct(
name: &Ident,
field: &Field,
) -> impl ToTokens {
let ty = field.ty.to_token_stream();
quote! {
impl ::dynomite::Attribute for #name {
fn into_attr(self) -> ::dynomite::dynamodb::AttributeValue {
self.0.into_attr()
}
fn from_attr(
value: ::dynomite::dynamodb::AttributeValue,
) -> ::std::result::Result<Self, ::dynomite::AttributeError> {
#ty::from_attr(value).map(Self)
}
}
}
}

fn expand_attributes(ast: DeriveInput) -> syn::Result<TokenStream> {
use syn::spanned::Spanned as _;
let name = ast.ident;
Expand Down
18 changes: 18 additions & 0 deletions dynomite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,24 @@
//!
//! `role` field here may be any of `Admin`, `Moderator`, or `Regular` strings.
//!
//! #### Newtype Structs
//!
//! Types that implement the [dynomite::Attribute](trait.Attribute.html) trait can be wrapped in
//! single-field tuple (newtype) structs. For example, a `String` newtype can be used as follows:
//! ```rust
//! use dynomite::{Attribute, Item};
//!
//! #[derive(Attribute)]
//! struct Author(String);
//!
//! #[derive(Item)]
//! struct Book {
//! #[dynomite(partition_key)]
//! id: String,
//! author: Author,
//! }
//! ```
//!
//! ## Rusoto extensions
//!
//! By importing the [dynomite::DynamoDbExt](trait.DynamoDbExt.html) trait, dynomite
Expand Down
8 changes: 7 additions & 1 deletion dynomite/tests/derived.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use dynomite::{Attribute, Attributes, Item};
use serde::{Deserialize, Serialize};

#[derive(Attribute, Default, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct AuthorName(String);

#[derive(Item, Default, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct Author {
#[dynomite(partition_key)]
// Test that the serde attr is not propagated to the generated key
// Issue: https://github.com/softprops/dynomite/issues/121
#[serde(rename = "Name")]
name: String,
name: AuthorName,
}

#[derive(Attribute, Default, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct NewAuthor(Author);

#[derive(Attribute, PartialEq, Debug, Clone)]
pub enum Category {
Foo,
Expand Down