Skip to content

Commit 000a2ae

Browse files
author
Bennett Hardwick
authored
Merge pull request #58 from cipherstash/feat/add-new-sealer-api
[WIP] Add Identifiable and new API
2 parents 9ba22fb + bd8838c commit 000a2ae

35 files changed

+1612
-601
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defaults:
1414

1515
env:
1616
CARGO_REGISTRIES_CIPHERSTASH_TOKEN: "Token ${{ secrets.CLOUDSMITH_CARGO_TOKEN }}"
17+
RUSTFLAGS: "-D warnings"
1718

1819
jobs:
1920
test:
@@ -35,7 +36,7 @@ jobs:
3536
toolchain: stable
3637

3738
- name: "Run tests"
38-
run: cargo test --all-targets
39+
run: cargo test --all-targets -- --ignored
3940
env:
4041
CS_WORKSPACE_ID: ${{ secrets.CS_WORKSPACE_ID }}
4142
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

cipherstash-dynamodb-derive/src/decryptable.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ pub(crate) fn derive_decryptable(input: DeriveInput) -> Result<TokenStream, syn:
1111

1212
let protected_attributes = settings.protected_attributes();
1313
let plaintext_attributes = settings.plaintext_attributes();
14+
15+
let protected_attributes_cow = settings
16+
.protected_attributes()
17+
.into_iter()
18+
.map(|x| quote! { std::borrow::Cow::Borrowed(#x) });
19+
20+
let plaintext_attributes_cow = settings
21+
.plaintext_attributes()
22+
.into_iter()
23+
.map(|x| quote! { std::borrow::Cow::Borrowed(#x) });
24+
1425
let skipped_attributes = settings.skipped_attributes();
1526
let ident = settings.ident();
1627

@@ -41,6 +52,14 @@ pub(crate) fn derive_decryptable(input: DeriveInput) -> Result<TokenStream, syn:
4152
let expanded = quote! {
4253
#[automatically_derived]
4354
impl cipherstash_dynamodb::traits::Decryptable for #ident {
55+
fn protected_attributes() -> std::borrow::Cow<'static, [std::borrow::Cow<'static, str>]> {
56+
std::borrow::Cow::Borrowed(&[#(#protected_attributes_cow,)*])
57+
}
58+
59+
fn plaintext_attributes() -> std::borrow::Cow<'static, [std::borrow::Cow<'static, str>]> {
60+
std::borrow::Cow::Borrowed(&[#(#plaintext_attributes_cow,)*])
61+
}
62+
4463
fn from_unsealed(unsealed: cipherstash_dynamodb::crypto::Unsealed) -> Result<Self, cipherstash_dynamodb::crypto::SealError> {
4564
Ok(Self {
4665
#(#from_unsealed_impl,)*

cipherstash-dynamodb-derive/src/encryptable.rs

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,109 +9,56 @@ pub(crate) fn derive_encryptable(input: DeriveInput) -> Result<TokenStream, syn:
99
.field_attributes(&input)?
1010
.build()?;
1111

12-
let partition_key_field = settings.get_partition_key();
13-
let partition_key = format_ident!("{partition_key_field}");
14-
let type_name = settings.type_name.clone();
15-
16-
let sort_key_prefix = settings
17-
.sort_key_prefix
18-
.as_ref()
19-
.map(|x| quote! { Some(#x) })
20-
.unwrap_or_else(|| quote! { None });
21-
2212
let protected_attributes = settings.protected_attributes();
2313
let plaintext_attributes = settings.plaintext_attributes();
24-
let ident = settings.ident();
2514

26-
let is_partition_key_encrypted = protected_attributes.contains(&partition_key_field.as_str());
27-
let is_sort_key_encrypted = settings
28-
.sort_key_field
29-
.as_ref()
30-
.map(|x| protected_attributes.contains(&x.as_str()))
31-
.unwrap_or(true);
15+
let protected_attributes_cow = settings
16+
.protected_attributes()
17+
.into_iter()
18+
.map(|x| quote! { std::borrow::Cow::Borrowed(#x) });
19+
20+
let plaintext_attributes_cow = settings
21+
.plaintext_attributes()
22+
.into_iter()
23+
.map(|x| quote! { std::borrow::Cow::Borrowed(#x) });
24+
25+
let ident = settings.ident();
3226

3327
let into_unsealed_impl = protected_attributes
3428
.iter()
3529
.map(|attr| {
3630
let attr_ident = format_ident!("{attr}");
3731

3832
quote! {
39-
.add_protected(#attr, |x| cipherstash_dynamodb::traits::Plaintext::from(x.#attr_ident.to_owned()))
33+
unsealed.add_protected(#attr, cipherstash_dynamodb::traits::Plaintext::from(self.#attr_ident.to_owned()));
4034
}
4135
})
4236
.chain(plaintext_attributes.iter().map(|attr| {
4337
let attr_ident = format_ident!("{attr}");
4438

4539
quote! {
46-
.add_plaintext(#attr, |x| cipherstash_dynamodb::traits::TableAttribute::from(x.#attr_ident.clone()))
40+
unsealed.add_unprotected(#attr, cipherstash_dynamodb::traits::TableAttribute::from(self.#attr_ident.clone()));
4741
}
4842
}));
4943

50-
let sort_key_impl = if let Some(sort_key_field) = &settings.sort_key_field {
51-
let sort_key_attr = format_ident!("{sort_key_field}");
52-
53-
quote! {
54-
if let Some(prefix) = Self::sort_key_prefix() {
55-
format!("{}#{}", prefix, self.#sort_key_attr)
56-
} else {
57-
self.#sort_key_attr.to_string()
58-
}
59-
}
60-
} else {
61-
quote! { Self::type_name().into() }
62-
};
63-
64-
let primary_key_impl = if settings.sort_key_field.is_some() {
65-
quote! { type PrimaryKey = cipherstash_dynamodb::PkSk; }
66-
} else {
67-
quote! { type PrimaryKey = cipherstash_dynamodb::Pk; }
68-
};
69-
7044
let expanded = quote! {
7145
#[automatically_derived]
7246
impl cipherstash_dynamodb::traits::Encryptable for #ident {
73-
#primary_key_impl
74-
75-
#[inline]
76-
fn type_name() -> &'static str {
77-
#type_name
78-
}
79-
80-
fn sort_key(&self) -> String {
81-
#sort_key_impl
82-
}
83-
84-
#[inline]
85-
fn sort_key_prefix() -> Option<&'static str> {
86-
#sort_key_prefix
87-
}
88-
89-
#[inline]
90-
fn is_partition_key_encrypted() -> bool {
91-
#is_partition_key_encrypted
47+
fn protected_attributes() -> std::borrow::Cow<'static, [std::borrow::Cow<'static, str>]> {
48+
std::borrow::Cow::Borrowed(&[#(#protected_attributes_cow,)*])
9249
}
9350

94-
#[inline]
95-
fn is_sort_key_encrypted() -> bool {
96-
#is_sort_key_encrypted
51+
fn plaintext_attributes() -> std::borrow::Cow<'static, [std::borrow::Cow<'static, str>]> {
52+
std::borrow::Cow::Borrowed(&[#(#plaintext_attributes_cow,)*])
9753
}
9854

99-
fn partition_key(&self) -> String {
100-
self.#partition_key.to_string()
101-
}
102-
103-
fn protected_attributes() -> Vec<&'static str> {
104-
vec![#(#protected_attributes,)*]
105-
}
55+
#[allow(clippy::needless_question_mark)]
56+
fn into_unsealed(self) -> cipherstash_dynamodb::crypto::Unsealed {
57+
let mut unsealed = cipherstash_dynamodb::crypto::Unsealed::new_with_descriptor(<Self as cipherstash_dynamodb::traits::Identifiable>::type_name());
10658

107-
fn plaintext_attributes() -> Vec<&'static str> {
108-
vec![#(#plaintext_attributes,)*]
109-
}
59+
#(#into_unsealed_impl)*
11060

111-
#[allow(clippy::needless_question_mark)]
112-
fn into_sealer(self) -> Result<cipherstash_dynamodb::crypto::Sealer<Self>, cipherstash_dynamodb::crypto::SealError> {
113-
Ok(cipherstash_dynamodb::crypto::Sealer::new_with_descriptor(self, Self::type_name())
114-
#(#into_unsealed_impl?)*)
61+
unsealed
11562
}
11663
}
11764
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::settings::Settings;
2+
use proc_macro2::TokenStream;
3+
use quote::{format_ident, quote};
4+
use syn::DeriveInput;
5+
6+
pub(crate) fn derive_identifiable(input: DeriveInput) -> Result<TokenStream, syn::Error> {
7+
let settings = Settings::builder(&input)
8+
.container_attributes(&input)?
9+
.field_attributes(&input)?
10+
.build()?;
11+
12+
let Some(partition_key_field) = settings.get_partition_key() else {
13+
return Err(syn::Error::new(
14+
proc_macro2::Span::call_site(),
15+
"Missing required attribute for Identifiable: #[partition_key]",
16+
));
17+
};
18+
19+
let partition_key_attr = format_ident!("{partition_key_field}");
20+
21+
let protected_attributes = settings.protected_attributes();
22+
let ident = settings.ident();
23+
24+
let is_partition_key_encrypted = protected_attributes.contains(&partition_key_field.as_str());
25+
26+
let is_sort_key_encrypted = settings
27+
.sort_key_field
28+
.as_ref()
29+
.map(|x| protected_attributes.contains(&x.as_str()))
30+
.unwrap_or(true);
31+
32+
let primary_key_impl = if let Some(sort_key_field) = &settings.sort_key_field {
33+
let sort_key_attr = format_ident!("{sort_key_field}");
34+
35+
quote! {
36+
type PrimaryKey = cipherstash_dynamodb::PkSk;
37+
38+
fn get_primary_key(&self) -> Self::PrimaryKey {
39+
cipherstash_dynamodb::PkSk(
40+
self.#partition_key_attr.to_string(),
41+
self.#sort_key_attr.to_string()
42+
)
43+
}
44+
}
45+
} else {
46+
quote! {
47+
type PrimaryKey = cipherstash_dynamodb::Pk;
48+
49+
fn get_primary_key(&self) -> Self::PrimaryKey {
50+
cipherstash_dynamodb::Pk(
51+
self.#partition_key_attr.to_string()
52+
)
53+
}
54+
}
55+
};
56+
let type_name = &settings.type_name;
57+
58+
let sort_key_prefix_impl = if let Some(prefix) = &settings.sort_key_prefix {
59+
quote! { Some(std::borrow::Cow::Borrowed(#prefix)) }
60+
} else {
61+
quote! { None }
62+
};
63+
64+
let expanded = quote! {
65+
impl cipherstash_dynamodb::traits::Identifiable for #ident {
66+
#primary_key_impl
67+
68+
#[inline]
69+
fn type_name() -> std::borrow::Cow<'static, str> {
70+
std::borrow::Cow::Borrowed(#type_name)
71+
}
72+
73+
#[inline]
74+
fn sort_key_prefix() -> Option<std::borrow::Cow<'static, str>> {
75+
#sort_key_prefix_impl
76+
}
77+
78+
fn is_pk_encrypted() -> bool {
79+
#is_partition_key_encrypted
80+
}
81+
82+
fn is_sk_encrypted() -> bool {
83+
#is_sort_key_encrypted
84+
}
85+
}
86+
};
87+
88+
Ok(expanded)
89+
}

cipherstash-dynamodb-derive/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate syn;
44

55
mod decryptable;
66
mod encryptable;
7+
mod identifiable;
78
mod searchable;
89
mod settings;
910

@@ -17,6 +18,13 @@ pub fn derive_encryptable(input: TokenStream) -> TokenStream {
1718
.into()
1819
}
1920

21+
#[proc_macro_derive(Identifiable, attributes(cipherstash, sort_key, partition_key))]
22+
pub fn derive_identifiable(input: TokenStream) -> TokenStream {
23+
identifiable::derive_identifiable(parse_macro_input!(input as DeriveInput))
24+
.unwrap_or_else(syn::Error::into_compile_error)
25+
.into()
26+
}
27+
2028
#[proc_macro_derive(Decryptable, attributes(cipherstash, sort_key, partition_key))]
2129
pub fn derive_decryptable(input: TokenStream) -> TokenStream {
2230
decryptable::derive_decryptable(parse_macro_input!(input as DeriveInput))

cipherstash-dynamodb-derive/src/searchable.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub(crate) fn derive_searchable(input: DeriveInput) -> Result<TokenStream, syn::
1919
let index_type = index.to_cipherstash_dynamodb_type()?;
2020

2121
Ok::<_, syn::Error>(quote! {
22-
( #index_name, #index_type )
22+
( std::borrow::Cow::Borrowed(#index_name), #index_type )
2323
})
2424
})
2525
.collect::<Result<Vec<_>, _>>()?;
@@ -53,8 +53,8 @@ pub(crate) fn derive_searchable(input: DeriveInput) -> Result<TokenStream, syn::
5353
let expanded = quote! {
5454
#[automatically_derived]
5555
impl cipherstash_dynamodb::traits::Searchable for #ident {
56-
fn protected_indexes() -> Vec<( &'static str, cipherstash_dynamodb::IndexType )> {
57-
vec![#(#protected_indexes_impl,)*]
56+
fn protected_indexes() -> std::borrow::Cow<'static, [( std::borrow::Cow<'static, str>, cipherstash_dynamodb::IndexType )]> {
57+
std::borrow::Cow::Borrowed(&[#(#protected_indexes_impl,)*])
5858
}
5959

6060
fn index_by_name(index_name: &str, index_type: cipherstash_dynamodb::IndexType) -> Option<Box<dyn cipherstash_dynamodb::traits::ComposableIndex>> {

cipherstash-dynamodb-derive/src/settings/builder.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -349,13 +349,6 @@ impl SettingsBuilder {
349349

350350
let sort_key_prefix = sort_key_prefix.into_prefix(&type_name);
351351

352-
let partition_key_field = partition_key_field.ok_or_else(|| {
353-
syn::Error::new(
354-
proc_macro2::Span::call_site(),
355-
"Missing required attribute: #[partition_key]",
356-
)
357-
})?;
358-
359352
Ok(Settings {
360353
ident,
361354
sort_key_prefix,

cipherstash-dynamodb-derive/src/settings/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub(crate) struct Settings {
1616
pub(crate) sort_key_prefix: Option<String>,
1717
pub(crate) type_name: String,
1818
pub(crate) sort_key_field: Option<String>,
19-
pub(crate) partition_key_field: String,
19+
pub(crate) partition_key_field: Option<String>,
2020
protected_attributes: Vec<String>,
2121
unprotected_attributes: Vec<String>,
2222

@@ -69,7 +69,7 @@ impl Settings {
6969
.collect()
7070
}
7171

72-
pub(crate) fn get_partition_key(&self) -> String {
72+
pub(crate) fn get_partition_key(&self) -> Option<String> {
7373
self.partition_key_field.clone()
7474
}
7575
}

examples/common/license.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use cipherstash_dynamodb::{Decryptable, Encryptable, Searchable};
1+
use cipherstash_dynamodb::{Decryptable, Encryptable, Identifiable, Searchable};
22

3-
#[derive(Debug, Encryptable, Decryptable, Searchable)]
3+
#[derive(Debug, Identifiable, Encryptable, Decryptable, Searchable)]
44
pub struct License {
55
#[partition_key]
66
email: String,
@@ -35,15 +35,4 @@ mod tests {
3535
fn test_cipherstash_typename() {
3636
assert_eq!(License::type_name(), "license");
3737
}
38-
39-
#[test]
40-
fn test_cipherstash_instance() {
41-
let license = License::new("[email protected]", "1234", "2020-01-01");
42-
assert_eq!(license.partition_key(), "[email protected]");
43-
assert_eq!(
44-
License::protected_attributes(),
45-
vec!["email", "expires", "number"]
46-
);
47-
assert!(License::plaintext_attributes().is_empty());
48-
}
4938
}

0 commit comments

Comments
 (0)