dynamodb_helper/
lib.rs

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}