Skip to content

Commit 5df19bf

Browse files
author
Bennett Hardwick
committed
Change to dynamic sort key, new attrs
1 parent c3a280c commit 5df19bf

File tree

10 files changed

+99
-25
lines changed

10 files changed

+99
-25
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ To delete a record, use the [`EncryptedTable::delete`] method:
253253

254254
```rust
255255
#
256-
table.delete::<User>("jane@smith.org").await?;
256+
table.delete::<User>("jane@smith.org", User::type_name()).await?;
257257
```
258258

259259
#### Querying Records

cryptonamo-derive/src/encryptable.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,24 @@ pub(crate) fn derive_encryptable(input: DeriveInput) -> Result<TokenStream, syn:
3333
}
3434
}));
3535

36+
let sort_key_impl = if let Some(sort_key_field) = &settings.sort_key_field {
37+
let sort_key_attr = format_ident!("{sort_key_field}");
38+
quote! { format!("{}#{}", Self::type_name(), self.#sort_key_attr) }
39+
} else {
40+
quote! { Self::type_name().into() }
41+
};
42+
3643
let expanded = quote! {
3744
#[automatically_derived]
3845
impl cryptonamo::traits::Encryptable for #ident {
3946
fn type_name() -> &'static str {
4047
#type_name
4148
}
4249

50+
fn sort_key(&self) -> String {
51+
#sort_key_impl
52+
}
53+
4354
fn partition_key(&self) -> String {
4455
self.#partition_key.to_string()
4556
}

cryptonamo-derive/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ mod settings;
1010
use proc_macro::TokenStream;
1111
use syn::{parse_macro_input, DeriveInput};
1212

13-
#[proc_macro_derive(Encryptable, attributes(cryptonamo))]
13+
#[proc_macro_derive(Encryptable, attributes(cryptonamo, sort_key, partition_key))]
1414
pub fn derive_encryptable(input: TokenStream) -> TokenStream {
1515
encryptable::derive_encryptable(parse_macro_input!(input as DeriveInput))
1616
.unwrap_or_else(syn::Error::into_compile_error)
1717
.into()
1818
}
1919

20-
#[proc_macro_derive(Decryptable, attributes(cryptonamo))]
20+
#[proc_macro_derive(Decryptable, attributes(cryptonamo, sort_key, partition_key))]
2121
pub fn derive_decryptable(input: TokenStream) -> TokenStream {
2222
decryptable::derive_decryptable(parse_macro_input!(input as DeriveInput))
2323
.unwrap_or_else(syn::Error::into_compile_error)
2424
.into()
2525
}
2626

27-
#[proc_macro_derive(Searchable, attributes(cryptonamo))]
27+
#[proc_macro_derive(Searchable, attributes(cryptonamo, sort_key, partition_key))]
2828
pub fn derive_searchable(input: TokenStream) -> TokenStream {
2929
searchable::derive_searchable(parse_macro_input!(input as DeriveInput))
3030
.unwrap_or_else(syn::Error::into_compile_error)

cryptonamo-derive/src/settings/builder.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ pub(crate) struct SettingsBuilder {
77
ident: Ident,
88
type_name: String,
99
sort_key_prefix: Option<String>,
10-
partition_key: Option<String>,
10+
sort_key_field: Option<String>,
11+
partition_key_field: Option<String>,
1112
protected_attributes: Vec<String>,
1213
unprotected_attributes: Vec<String>,
1314
skipped_attributes: Vec<String>,
@@ -33,7 +34,8 @@ impl SettingsBuilder {
3334
ident: input.ident.clone(),
3435
type_name,
3536
sort_key_prefix: None,
36-
partition_key: None,
37+
sort_key_field: None,
38+
partition_key_field: None,
3739
protected_attributes: Vec::new(),
3840
unprotected_attributes: Vec::new(),
3941
skipped_attributes: Vec::new(),
@@ -92,6 +94,48 @@ impl SettingsBuilder {
9294

9395
// Parse the meta for the field
9496
for attr in &field.attrs {
97+
if attr.path().is_ident("sort_key") {
98+
let field_name = ident
99+
.as_ref()
100+
.ok_or_else(|| {
101+
syn::Error::new_spanned(
102+
field,
103+
"internal error: identifier was not Some",
104+
)
105+
})?
106+
.to_string();
107+
108+
if let Some(f) = &self.sort_key_field {
109+
return Err(syn::Error::new_spanned(
110+
field,
111+
format!("sort key was already specified to be '{f}'"),
112+
));
113+
}
114+
115+
self.sort_key_field = Some(field_name);
116+
}
117+
118+
if attr.path().is_ident("partition_key") {
119+
let field_name = ident
120+
.as_ref()
121+
.ok_or_else(|| {
122+
syn::Error::new_spanned(
123+
field,
124+
"internal error: identifier was not Some",
125+
)
126+
})?
127+
.to_string();
128+
129+
if let Some(f) = &self.partition_key_field {
130+
return Err(syn::Error::new_spanned(
131+
field,
132+
format!("partition key was already specified to be '{f}'"),
133+
));
134+
}
135+
136+
self.partition_key_field = Some(field_name);
137+
}
138+
95139
if attr.path().is_ident("cryptonamo") {
96140
let mut query: Option<(String, String, Span)> = None;
97141
let mut compound_index_name: Option<(String, Span)> = None;
@@ -203,7 +247,8 @@ impl SettingsBuilder {
203247
ident,
204248
type_name,
205249
sort_key_prefix,
206-
partition_key,
250+
sort_key_field,
251+
partition_key_field: partition_key,
207252
protected_attributes,
208253
unprotected_attributes,
209254
skipped_attributes,
@@ -222,7 +267,8 @@ impl SettingsBuilder {
222267
Ok(Settings {
223268
ident,
224269
sort_key_prefix,
225-
partition_key: Some(partition_key), // TODO: Remove the Some
270+
sort_key_field,
271+
partition_key_field: Some(partition_key), // TODO: Remove the Some
226272
protected_attributes,
227273
unprotected_attributes,
228274
skipped_attributes,
@@ -236,7 +282,7 @@ impl SettingsBuilder {
236282
}
237283

238284
pub(crate) fn set_partition_key(&mut self, value: String) -> Result<(), syn::Error> {
239-
self.partition_key = Some(value);
285+
self.partition_key_field = Some(value);
240286
Ok(())
241287
}
242288

cryptonamo-derive/src/settings/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub(crate) enum AttributeMode {
1616
pub(crate) struct Settings {
1717
ident: Ident,
1818
pub(crate) sort_key_prefix: String,
19-
pub(crate) partition_key: Option<String>,
19+
pub(crate) sort_key_field: Option<String>,
20+
pub(crate) partition_key_field: Option<String>,
2021
protected_attributes: Vec<String>,
2122
unprotected_attributes: Vec<String>,
2223

@@ -70,7 +71,7 @@ impl Settings {
7071
}
7172

7273
pub(crate) fn get_partition_key(&self) -> Result<String, syn::Error> {
73-
self.partition_key.clone().ok_or_else(|| {
74+
self.partition_key_field.clone().ok_or_else(|| {
7475
syn::Error::new_spanned(
7576
&self.sort_key_prefix,
7677
"No partition key defined for this struct",

examples/delete_user.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod common;
22
use crate::common::User;
3-
use cryptonamo::EncryptedTable;
3+
use cryptonamo::{Encryptable, EncryptedTable};
44

55
#[tokio::main]
66
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -21,9 +21,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2121

2222
let table = EncryptedTable::init(client, "users").await?;
2323

24-
table.delete::<User>("jane@smith.org").await?;
25-
table.delete::<User>("dan@coderdan.co").await?;
26-
table.delete::<User>("daniel@example.com").await?;
24+
table
25+
.delete::<User>("jane@smith.org", User::type_name())
26+
.await?;
27+
table
28+
.delete::<User>("dan@coderdan.co", User::type_name())
29+
.await?;
30+
table
31+
.delete::<User>("daniel@example.com", User::type_name())
32+
.await?;
2733

2834
Ok(())
2935
}

src/crypto/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ pub enum CryptoError {
4444
Other(String),
4545
}
4646

47-
pub(crate) fn all_index_keys<E: Searchable + Encryptable>() -> Vec<String> {
47+
pub(crate) fn all_index_keys<E: Searchable + Encryptable>(sort_key: &str) -> Vec<String> {
4848
E::protected_indexes()
4949
.iter()
5050
.flat_map(|index_name| {
5151
(0..)
5252
.take(MAX_TERMS_PER_INDEX)
53-
.map(|i| format!("{}#{}#{}", E::type_name(), index_name, i))
53+
.map(|i| format!("{}#{}#{}", sort_key, index_name, i))
5454
.collect::<Vec<String>>()
5555
})
5656
.collect()

src/encrypted_table/mod.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,16 @@ impl EncryptedTable {
138138
}
139139
}
140140

141-
pub async fn delete<E: Searchable>(&self, pk: &str) -> Result<(), DeleteError> {
141+
// TODO: create PrimaryKey abstraction
142+
pub async fn delete<E: Searchable>(
143+
&self,
144+
pk: &str,
145+
sk: impl Into<String>,
146+
) -> Result<(), DeleteError> {
142147
let pk = AttributeValue::S(encrypt_partition_key(pk, &self.cipher)?);
148+
let sk: String = sk.into();
143149

144-
let sk_to_delete = [E::type_name().to_string()]
145-
.into_iter()
146-
.chain(all_index_keys::<E>().into_iter());
150+
let sk_to_delete = all_index_keys::<E>(&sk).into_iter().into_iter().chain([sk]);
147151

148152
let transact_items = sk_to_delete.map(|sk| {
149153
TransactWriteItem::builder()
@@ -177,6 +181,7 @@ impl EncryptedTable {
177181
{
178182
let mut seen_sk = HashSet::new();
179183

184+
let sk = record.sort_key();
180185
let sealer: Sealer<T> = record.into_sealer()?;
181186
let (pk, sealed) = sealer.seal(&self.cipher, 12).await?;
182187

@@ -198,7 +203,7 @@ impl EncryptedTable {
198203
);
199204
}
200205

201-
for index_sk in all_index_keys::<T>() {
206+
for index_sk in all_index_keys::<T>(&sk) {
202207
if seen_sk.contains(&index_sk) {
203208
continue;
204209
}

src/traits/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ pub enum WriteConversionError {
2626
pub trait Encryptable: Debug + Sized {
2727
// TODO: Add a function indicating that the root should be stored
2828
fn type_name() -> &'static str;
29+
30+
fn sort_key(&self) -> String {
31+
Self::type_name().into()
32+
}
33+
2934
fn partition_key(&self) -> String;
3035

3136
fn protected_attributes() -> Vec<&'static str>;

tests/query_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use serial_test::serial;
44
use std::future::Future;
55

66
#[derive(Encryptable, Decryptable, Searchable, Debug, PartialEq, Ord, PartialOrd, Eq)]
7-
#[cryptonamo(partition_key = "email")]
87
#[cryptonamo(sort_key_prefix = "user")]
98
pub struct User {
109
#[cryptonamo(query = "exact", compound = "email#name")]
1110
#[cryptonamo(query = "exact")]
11+
#[partition_key]
1212
pub email: String,
1313

1414
#[cryptonamo(query = "prefix", compound = "email#name")]
@@ -33,7 +33,7 @@ impl User {
3333
}
3434
}
3535

36-
async fn run_test<F: Future<Output = ()>>(f: impl FnOnce(EncryptedTable) -> F) {
36+
async fn run_test<F: Future<Output = ()>>(mut f: impl FnMut(EncryptedTable) -> F) {
3737
let config = aws_config::from_env()
3838
.endpoint_url("http://localhost:8000")
3939
.load()
@@ -145,7 +145,7 @@ async fn test_get_by_partition_key() {
145145
async fn test_delete() {
146146
run_test(|table| async move {
147147
table
148-
.delete::<User>("dan@coderdan.co")
148+
.delete::<User>("dan@coderdan.co", User::type_name())
149149
.await
150150
.expect("Failed to send");
151151

0 commit comments

Comments
 (0)