cipherstash_dynamodb/traits/
mod.rs1use crate::crypto::{SealError, Unsealed};
2pub use crate::encrypted_table::{TableAttribute, TryFromTableAttr};
3use cipherstash_client::encryption::EncryptionError;
4pub use cipherstash_client::{
5 credentials::{service_credentials::ServiceToken, Credentials},
6 encryption::{
7 compound_indexer::{
8 ComposableIndex, ComposablePlaintext, CompoundIndex, ExactIndex, PrefixIndex,
9 },
10 Encryption, Plaintext, PlaintextNullVariant, TryFromPlaintext,
11 },
12};
13
14mod primary_key;
15use miette::Diagnostic;
16pub use primary_key::*;
17
18use std::{
19 borrow::Cow,
20 fmt::{Debug, Display},
21};
22use thiserror::Error;
23
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum SingleIndex {
26 Exact,
27 Prefix,
28}
29
30impl Display for SingleIndex {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Exact => f.write_str("exact"),
34 Self::Prefix => f.write_str("prefix"),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq)]
40pub enum IndexType {
41 Single(SingleIndex),
42 Compound2((SingleIndex, SingleIndex)),
43}
44
45impl Display for IndexType {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::Single(index) => Display::fmt(index, f),
49 Self::Compound2((index_a, index_b)) => {
50 Display::fmt(index_a, f)?;
51 f.write_str(":")?;
52 Display::fmt(index_b, f)?;
53 Ok(())
54 }
55 }
56 }
57}
58
59#[derive(Debug, Error, Diagnostic)]
60pub enum ReadConversionError {
61 #[error("Missing attribute: {0}")]
62 NoSuchAttribute(String),
63 #[error("Invalid format: {0}")]
64 InvalidFormat(String),
65 #[error("Failed to convert attribute: {0} from Plaintext")]
66 ConversionFailed(String),
67}
68
69#[derive(Debug, Error)]
70pub enum WriteConversionError {
71 #[error("Failed to convert attribute: '{0}' to Plaintext")]
72 ConversionFailed(String),
73}
74
75#[derive(Error, Debug)]
76pub enum PrimaryKeyError {
77 #[error("EncryptionError: {0}")]
78 EncryptionError(#[from] EncryptionError),
79 #[error("PrimaryKeyError: {0}")]
80 Unknown(String),
81}
82
83pub trait Identifiable {
84 type PrimaryKey: PrimaryKey;
85
86 fn get_primary_key(&self) -> Self::PrimaryKey;
87
88 fn is_sk_encrypted() -> bool {
89 false
90 }
91
92 fn is_pk_encrypted() -> bool {
93 false
94 }
95
96 fn type_name() -> Cow<'static, str>;
97 fn sort_key_prefix() -> Option<Cow<'static, str>>;
98}
99
100pub trait Encryptable: Debug + Sized + Identifiable {
101 fn protected_attributes() -> Cow<'static, [Cow<'static, str>]>;
105
106 fn plaintext_attributes() -> Cow<'static, [Cow<'static, str>]>;
110
111 fn into_unsealed(self) -> Unsealed;
112}
113
114pub trait Searchable: Encryptable {
115 fn attribute_for_index(
116 &self,
117 _index_name: &str,
118 _index_type: IndexType,
119 ) -> Option<ComposablePlaintext> {
120 None
121 }
122
123 fn protected_indexes() -> Cow<'static, [(Cow<'static, str>, IndexType)]> {
126 Cow::Borrowed(&[])
127 }
128
129 fn index_by_name(
130 _index_name: &str,
131 _index_type: IndexType,
132 ) -> Option<Box<dyn ComposableIndex + Send>> {
133 None
134 }
135}
136
137pub trait Decryptable: Sized {
138 fn from_unsealed(unsealed: Unsealed) -> Result<Self, SealError>;
140
141 fn protected_attributes() -> Cow<'static, [Cow<'static, str>]>;
145
146 fn plaintext_attributes() -> Cow<'static, [Cow<'static, str>]>;
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use miette::IntoDiagnostic;
156 use std::collections::BTreeMap;
157
158 fn make_btree_map() -> BTreeMap<String, String> {
159 let mut map = BTreeMap::new();
160 map.insert("a".to_string(), "value-a".to_string());
161 map.insert("b".to_string(), "value-b".to_string());
162 map.insert("c".to_string(), "value-c".to_string());
163 map
164 }
165
166 #[derive(Debug, Clone, PartialEq)]
167 struct Test {
168 pub id: String,
169 pub name: String,
170 pub age: i16,
171 pub tag: String,
172 pub attrs: BTreeMap<String, String>,
173 }
174
175 impl Identifiable for Test {
176 type PrimaryKey = Pk;
177
178 fn get_primary_key(&self) -> Self::PrimaryKey {
179 Pk(self.id.to_string())
180 }
181 #[inline]
182 fn type_name() -> Cow<'static, str> {
183 std::borrow::Cow::Borrowed("test")
184 }
185 #[inline]
186 fn sort_key_prefix() -> Option<Cow<'static, str>> {
187 None
188 }
189 fn is_pk_encrypted() -> bool {
190 true
191 }
192 fn is_sk_encrypted() -> bool {
193 false
194 }
195 }
196
197 fn put_attrs(unsealed: &mut Unsealed, attrs: BTreeMap<String, String>) {
198 attrs.into_iter().for_each(|(k, v)| {
199 unsealed.add_protected_map_field("attrs", k, Plaintext::from(v));
200 })
201 }
202
203 impl Encryptable for Test {
204 fn protected_attributes() -> Cow<'static, [Cow<'static, str>]> {
205 Cow::Borrowed(&[Cow::Borrowed("name")])
206 }
207
208 fn plaintext_attributes() -> Cow<'static, [Cow<'static, str>]> {
209 Cow::Borrowed(&[Cow::Borrowed("age")])
210 }
211
212 fn into_unsealed(self) -> Unsealed {
213 let mut unsealed = Unsealed::new_with_descriptor(<Self as Identifiable>::type_name());
214 unsealed.add_protected("id", self.id);
215 unsealed.add_protected("name", self.name);
216 unsealed.add_protected("age", self.age);
217 unsealed.add_unprotected("tag", self.tag);
218 put_attrs(&mut unsealed, self.attrs);
219 unsealed
220 }
221 }
222
223 fn get_attrs<T>(unsealed: &mut Unsealed) -> Result<T, SealError>
225 where
226 T: FromIterator<(String, String)>,
227 {
228 unsealed
229 .take_protected_map("attrs")
230 .ok_or(SealError::MissingAttribute("attrs".to_string()))?
231 .into_iter()
232 .map(|(k, v)| {
233 TryFromPlaintext::try_from_plaintext(v)
234 .map(|v| (k, v))
235 .map_err(SealError::from)
236 })
237 .collect()
238 }
239
240 impl Decryptable for Test {
242 fn from_unsealed(mut unsealed: Unsealed) -> Result<Self, SealError> {
243 Ok(Self {
244 id: TryFromPlaintext::try_from_optional_plaintext(unsealed.take_protected("id"))?,
245 name: TryFromPlaintext::try_from_optional_plaintext(
246 unsealed.take_protected("name"),
247 )?,
248 age: TryFromPlaintext::try_from_optional_plaintext(unsealed.take_protected("age"))?,
249 tag: TryFromTableAttr::try_from_table_attr(unsealed.take_unprotected("tag"))?,
250 attrs: get_attrs(&mut unsealed)?,
251 })
252 }
253
254 fn protected_attributes() -> Cow<'static, [Cow<'static, str>]> {
257 Cow::Borrowed(&[
258 Cow::Borrowed("name"),
259 Cow::Borrowed("age"),
260 Cow::Borrowed("attrs"),
261 ])
262 }
263
264 fn plaintext_attributes() -> Cow<'static, [Cow<'static, str>]> {
265 Cow::Borrowed(&[Cow::Borrowed("tag")])
266 }
267 }
268
269 #[test]
270 fn test_encryptable() -> Result<(), Box<dyn std::error::Error>> {
271 let test = Test {
272 id: "id-100".to_string(),
273 name: "name".to_string(),
274 tag: "tag".to_string(),
275 age: 42,
276 attrs: make_btree_map(),
277 };
278
279 let unsealed = test.clone().into_unsealed();
280 assert_eq!(test, Test::from_unsealed(unsealed).into_diagnostic()?);
281
282 Ok(())
283 }
284}