Skip to content

Commit 65830a6

Browse files
author
Bennett Hardwick
committed
Update to primary key abstraction
1 parent 7216d14 commit 65830a6

File tree

13 files changed

+146
-31
lines changed

13 files changed

+146
-31
lines changed

cryptonamo-derive/src/encryptable.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,17 @@ pub(crate) fn derive_encryptable(input: DeriveInput) -> Result<TokenStream, syn:
4040
quote! { Self::type_name().into() }
4141
};
4242

43+
let primary_key_impl = if settings.sort_key_field.is_some() {
44+
quote! { type PrimaryKey = cryptonamo::PkSk; }
45+
} else {
46+
quote! { type PrimaryKey = cryptonamo::Pk; }
47+
};
48+
4349
let expanded = quote! {
4450
#[automatically_derived]
4551
impl cryptonamo::traits::Encryptable for #ident {
52+
#primary_key_impl
53+
4654
fn type_name() -> &'static str {
4755
#type_name
4856
}

examples/delete_user.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ 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", None).await?;
25-
table.delete::<User>("dan@coderdan.co", None).await?;
26-
table.delete::<User>("daniel@example.com", None).await?;
24+
table.delete::<User>("jane@smith.org").await?;
25+
table.delete::<User>("dan@coderdan.co").await?;
26+
table.delete::<User>("daniel@example.com").await?;
2727

2828
Ok(())
2929
}

examples/get_license.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2020
let client = aws_sdk_dynamodb::Client::new(&config);
2121

2222
let table = EncryptedTable::init(client, "users").await?;
23-
let license: Option<License> = table.get("dan@coderdan.co", None).await?;
23+
let license: Option<License> = table.get("dan@coderdan.co").await?;
2424

2525
dbg!(license);
2626

examples/get_user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2020
let client = aws_sdk_dynamodb::Client::new(&config);
2121

2222
let table = EncryptedTable::init(client, "users").await?;
23-
let user: Option<User> = table.get("dan@coderdan.co", None).await?;
23+
let user: Option<User> = table.get("dan@coderdan.co").await?;
2424

2525
dbg!(user);
2626

src/encrypted_table/mod.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ pub use self::{
66
};
77
use crate::{
88
crypto::*,
9-
traits::{Decryptable, ReadConversionError, Searchable, WriteConversionError},
9+
traits::{
10+
Decryptable, PrimaryKey, PrimaryKeyParts, ReadConversionError, Searchable,
11+
WriteConversionError,
12+
},
1013
};
1114
use aws_sdk_dynamodb::{
1215
types::{AttributeValue, Delete, Put, TransactWriteItem},
@@ -112,14 +115,13 @@ impl EncryptedTable {
112115
QueryBuilder::new(self)
113116
}
114117

115-
pub async fn get<T>(&self, pk: &str, sk: Option<&str>) -> Result<Option<T>, GetError>
118+
pub async fn get<T>(&self, k: impl Into<T::PrimaryKey>) -> Result<Option<T>, GetError>
116119
where
117120
T: Decryptable,
118121
{
119-
let pk = encrypt_partition_key(pk, &self.cipher)?;
120-
let sk = sk
121-
.map(|sk| format!("{}#{}", T::type_name(), sk))
122-
.unwrap_or_else(|| T::type_name().to_string());
122+
let PrimaryKeyParts { pk, sk } = k.into().into_parts(T::type_name());
123+
124+
let pk = encrypt_partition_key(&pk, &self.cipher)?;
123125

124126
let result = self
125127
.db
@@ -141,15 +143,10 @@ impl EncryptedTable {
141143
}
142144

143145
// TODO: create PrimaryKey abstraction
144-
pub async fn delete<E: Searchable>(
145-
&self,
146-
pk: &str,
147-
sk: Option<&str>,
148-
) -> Result<(), DeleteError> {
149-
let pk = AttributeValue::S(encrypt_partition_key(pk, &self.cipher)?);
150-
let sk = sk
151-
.map(|sk| format!("{}#{}", E::type_name(), sk))
152-
.unwrap_or_else(|| E::type_name().to_string());
146+
pub async fn delete<E: Searchable>(&self, k: impl Into<E::PrimaryKey>) -> Result<(), DeleteError> {
147+
let PrimaryKeyParts { pk, sk } = k.into().into_parts(E::type_name());
148+
149+
let pk = AttributeValue::S(encrypt_partition_key(&pk, &self.cipher)?);
153150

154151
let sk_to_delete = all_index_keys::<E>(&sk).into_iter().into_iter().chain([sk]);
155152

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ mod error;
403403
pub mod traits;
404404
pub use encrypted_table::{EncryptedTable, QueryBuilder};
405405
pub use error::Error;
406-
pub use traits::{Decryptable, Encryptable, Searchable};
406+
pub use traits::{Decryptable, Encryptable, Searchable, PrimaryKey, PkSk, Pk};
407407

408408
#[doc(hidden)]
409409
pub use cryptonamo_derive::{Decryptable, Encryptable, Searchable};

src/traits/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ pub use cipherstash_client::encryption::{
66
},
77
Plaintext,
88
};
9+
10+
mod primary_key;
11+
pub use primary_key::*;
12+
913
use std::fmt::Debug;
1014
use thiserror::Error;
1115

@@ -24,6 +28,8 @@ pub enum WriteConversionError {
2428
}
2529

2630
pub trait Encryptable: Debug + Sized {
31+
type PrimaryKey: PrimaryKey;
32+
2733
// TODO: Add a function indicating that the root should be stored
2834
fn type_name() -> &'static str;
2935

@@ -61,6 +67,7 @@ pub trait Searchable: Encryptable {
6167

6268
pub trait Decryptable: Encryptable {
6369
/// Convert an `Unsealed` into a `Self`.
70+
6471
fn from_unsealed(unsealed: Unsealed) -> Result<Self, SealError>;
6572

6673
/// Defines which attributes are decryptable for this type.

src/traits/primary_key.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
pub struct PrimaryKeyParts {
2+
pub(crate) pk: String,
3+
pub(crate) sk: String,
4+
}
5+
6+
pub trait PrimaryKey: private::Sealed {
7+
fn into_parts(self, sk_prefix: &str) -> PrimaryKeyParts;
8+
}
9+
10+
pub struct Pk(String);
11+
12+
impl Pk {
13+
pub fn new(pk: impl Into<String>) -> Self {
14+
Self(pk.into())
15+
}
16+
}
17+
18+
impl From<String> for Pk {
19+
fn from(value: String) -> Self {
20+
Self(value)
21+
}
22+
}
23+
24+
impl From<&str> for Pk {
25+
fn from(value: &str) -> Self {
26+
Self::new(value)
27+
}
28+
}
29+
30+
pub struct PkSk(String, String);
31+
32+
impl PkSk {
33+
pub fn new(pk: impl Into<String>, sk: impl Into<String>) -> Self {
34+
Self(pk.into(), sk.into())
35+
}
36+
}
37+
38+
mod private {
39+
use super::*;
40+
41+
pub trait Sealed {}
42+
43+
impl Sealed for Pk {}
44+
impl Sealed for PkSk {}
45+
}
46+
47+
impl PrimaryKey for Pk {
48+
fn into_parts(self, sk_prefix: &str) -> PrimaryKeyParts {
49+
PrimaryKeyParts {
50+
pk: self.0,
51+
sk: sk_prefix.to_string(),
52+
}
53+
}
54+
}
55+
56+
impl PrimaryKey for PkSk {
57+
fn into_parts(self, sk_prefix: &str) -> PrimaryKeyParts {
58+
PrimaryKeyParts {
59+
pk: self.0,
60+
sk: format!("{}#{}", sk_prefix, self.1),
61+
}
62+
}
63+
}

tests/compile_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ fn ui_tests() {
77
t.compile_fail("tests/ui/compound-index-too-many-fields.rs");
88
t.compile_fail("tests/ui/index-unsupported.rs");
99
t.compile_fail("tests/ui/compound-index-unsupported.rs");
10+
t.compile_fail("tests/ui/using-pk-instead-of-pk-sk.rs");
1011

1112
t.pass("tests/ui/pass.rs");
1213
}

tests/query_tests.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,7 @@ async fn test_query_compound() {
138138
#[serial]
139139
async fn test_get_by_partition_key() {
140140
run_test(|table| async move {
141-
let res: Option<User> = table
142-
.get("dan@coderdan.co", None)
143-
.await
144-
.expect("Failed to send");
141+
let res: Option<User> = table.get("dan@coderdan.co").await.expect("Failed to send");
145142
assert_eq!(
146143
res,
147144
Some(User::new("dan@coderdan.co", "Dan Draper", "blue"))
@@ -155,12 +152,12 @@ async fn test_get_by_partition_key() {
155152
async fn test_delete() {
156153
run_test(|table| async move {
157154
table
158-
.delete::<User>("dan@coderdan.co", None)
155+
.delete::<User>("dan@coderdan.co")
159156
.await
160157
.expect("Failed to send");
161158

162159
let res = table
163-
.get::<User>("dan@coderdan.co", None)
160+
.get::<User>("dan@coderdan.co")
164161
.await
165162
.expect("Failed to send");
166163
assert_eq!(res, None);

0 commit comments

Comments
 (0)