1#![doc = include_str!("../README.md")]
2
3extern crate core;
4
5mod implementation;
6
7use crate::implementation::*;
8use proc_macro::TokenStream;
9use proc_macro2::Ident;
10use quote::quote;
11use syn::Data::Enum;
12use syn::Data::Struct;
13use syn::Data::Union;
14use syn::DataStruct;
15use syn::Fields::Named;
16use syn::FieldsNamed;
17use syn::{parse_macro_input, DeriveInput, Error};
18
19#[proc_macro_derive(DynamoDb, attributes(partition, range, exclusion))]
20pub fn create_dynamodb_helper(item: TokenStream) -> TokenStream {
21 let ast = parse_macro_input!(item as DeriveInput);
22 let name = ast.ident;
23 let helper_name = format!("{name}Db");
24 let helper_ident = Ident::new(&helper_name, name.span());
25
26 let fields = match ast.data {
27 Struct(DataStruct {
28 fields: Named(FieldsNamed { ref named, .. }),
29 ..
30 }) => named,
31 Enum(_) => {
32 return Error::new(
33 name.span(),
34 "DynamoDB macro cannot be used with an enum - use a struct instead".to_string(),
35 )
36 .into_compile_error()
37 .into()
38 }
39 Union(_) => {
40 return Error::new(
41 name.span(),
42 "DynamoDB macro cannot be used with a union - use a struct instead".to_string(),
43 )
44 .into_compile_error()
45 .into()
46 }
47 _ => {
48 return Error::new(
49 name.span(),
50 "DynamoDB macro can only be used with a struct with named fields".to_string(),
51 )
52 .into_compile_error()
53 .into()
54 }
55 };
56
57 let exclusion_list = get_macro_attribute(&ast.attrs, EXCLUSION_ATTRIBUTE_NAME);
58 let exclusion_list_refs: Vec<&str> = exclusion_list.iter().map(|x| &**x).collect();
59
60 let (get_error, get_by_partition_error, batch_get_error, scan_error, parse_error) = generate_error_names(&helper_ident);
61 let errors = generate_helper_error(&helper_ident, &exclusion_list_refs);
62
63 let partition_key_ident_and_type = match get_ident_and_type_of_field_annotated_with(fields, PARTITION_KEY_ATTRIBUTE_NAME) {
64 Some(res) => res,
65 None => {
66 return Error::new(
67 name.span(),
68 "You need to define a partition key for your DynamoDB struct! Place the `#[partition]` attribute above the field that serves as your key.".to_string()
69 )
70 .into_compile_error()
71 .into();
72 }
73 };
74
75 match DynamoType::from(partition_key_ident_and_type.1) {
76 None => {
77 return Error::new(
78 partition_key_ident_and_type.0.span(),
79 "DynamoDB only supports strings, numbers and booleans as keys".to_string()
80 ).into_compile_error().into();
81 }
82 _ => {}
83 }
84
85 let range_key_ident_and_type = get_ident_and_type_of_field_annotated_with(fields, RANGE_KEY_ATTRIBUTE_NAME);
86
87 let from_struct_for_hashmap = tokenstream_or_empty_if_no_put_methods(from_struct_for_hashmap(&name, fields), &exclusion_list_refs);
88
89 let try_from_hashmap_for_struct =
90 tokenstream_or_empty_if_no_retrieval_methods(try_from_hashmap_to_struct(&name, &parse_error, fields), &exclusion_list_refs);
91
92 let new = tokenstream_or_empty_if_exclusion(new_method(&helper_ident), NEW_METHOD_NAME, &exclusion_list_refs);
93
94 let build = tokenstream_or_empty_if_exclusion(build_method(&helper_ident), BUILD_METHOD_NAME, &exclusion_list_refs);
95
96 let gets = tokenstream_or_empty_if_exclusion(
97 get_methods(
98 &name,
99 &get_error,
100 &get_by_partition_error,
101 partition_key_ident_and_type,
102 range_key_ident_and_type,
103 ),
104 GET_METHOD_NAME,
105 &exclusion_list_refs,
106 );
107
108 let batch_get = tokenstream_or_empty_if_exclusion(
109 batch_get(&name, &batch_get_error, partition_key_ident_and_type, range_key_ident_and_type),
110 BATCH_GET_METHOD_NAME,
111 &exclusion_list_refs,
112 );
113
114 let create_table = tokenstream_or_empty_if_exclusion(
115 create_table_method(partition_key_ident_and_type, range_key_ident_and_type),
116 CREATE_TABLE_METHOD_NAME,
117 &exclusion_list_refs,
118 );
119 let delete_table = tokenstream_or_empty_if_exclusion(delete_table_method(), DELETE_TABLE_METHOD_NAME, &exclusion_list_refs);
120 let put = tokenstream_or_empty_if_exclusion(put_method(&name), PUT_METHOD_NAME, &exclusion_list_refs);
121 let batch_put = tokenstream_or_empty_if_exclusion(batch_put_method(&name), BATCH_PUT_METHOD_NAME, &exclusion_list_refs);
122 let delete = tokenstream_or_empty_if_exclusion(
123 delete_method(&name, partition_key_ident_and_type, range_key_ident_and_type),
124 DELETE_METHOD_NAME,
125 &exclusion_list_refs,
126 );
127 let scan = tokenstream_or_empty_if_exclusion(scan_method(&name, &scan_error), SCAN_METHOD_NAME, &exclusion_list_refs);
128
129 let public_version = quote! {
130 #from_struct_for_hashmap
131 #try_from_hashmap_for_struct
132
133 pub struct #helper_ident {
134 pub client: aws_sdk_dynamodb::Client,
135 pub table: String,
136 }
137
138 impl #helper_ident {
139 #new
140 #build
141
142 #create_table
143 #delete_table
144
145 #put
146 #gets
147 #batch_get
148 #batch_put
149 #delete
150 #scan
151 }
152
153 #errors
154 };
155
156 public_version.into()
157}