Skip to main content

fraiseql_server/encryption/
mapper.rs

1// Phase 12.3 Cycle 4: Mapper Integration (REFACTOR)
2//! Mapper integration for transparent field-level encryption/decryption
3//!
4//! Provides automatic encryption on write operations and decryption on read
5//! operations at the mapper/ORM layer without application code changes.
6//!
7//! # Overview
8//!
9//! The mapper module integrates with DatabaseFieldAdapter to provide
10//! automatic encryption/decryption at the data mapping layer:
11//!
12//! - Encrypt plaintext values before INSERT/UPDATE
13//! - Decrypt ciphertext after SELECT
14//! - Support for mixed encrypted/unencrypted fields
15//! - Type information preservation through encryption
16//! - Batch operation support
17//! - Transaction awareness
18//! - Comprehensive error handling
19//!
20//! # Usage Pattern
21//!
22//! ```ignore
23//! // Create mapper with encrypted field configuration
24//! let mapper = FieldMapper::new(
25//!     adapter,
26//!     vec!["email".to_string(), "phone".to_string()]
27//! );
28//!
29//! // On INSERT: encrypt plaintext
30//! let encrypted = mapper.encrypt_field("email", "user@example.com").await?;
31//!
32//! // On SELECT: decrypt ciphertext
33//! let plaintext = mapper.decrypt_field("email", &ciphertext).await?;
34//!
35//! // Batch operations
36//! let mappings = mapper.encrypt_fields(&[
37//!     ("email".to_string(), "user@example.com".to_string()),
38//!     ("name".to_string(), "John Doe".to_string()),
39//! ]).await?;
40//! ```
41//!
42//! # Type Support
43//!
44//! The mapper works with any data that can be converted to/from UTF-8:
45//! - Strings (primary use case)
46//! - Numbers (as strings)
47//! - Dates/times (as formatted strings)
48//! - JSON (as JSON strings)
49//! - UUIDs (as UUID strings)
50//! - Custom types (via ToString/FromStr)
51
52use std::{collections::HashMap, sync::Arc};
53
54use super::database_adapter::{DatabaseFieldAdapter, EncryptedFieldAdapter};
55use crate::secrets_manager::SecretsError;
56
57/// Field mapping result containing both value and encryption status
58#[derive(Debug, Clone)]
59pub struct FieldMapping {
60    /// Field name
61    field_name:   String,
62    /// Whether field is encrypted
63    is_encrypted: bool,
64    /// Field value (plaintext for encrypted fields)
65    value:        Vec<u8>,
66}
67
68impl FieldMapping {
69    /// Create new field mapping
70    pub fn new(field_name: impl Into<String>, is_encrypted: bool, value: Vec<u8>) -> Self {
71        Self {
72            field_name: field_name.into(),
73            is_encrypted,
74            value,
75        }
76    }
77
78    /// Get field name
79    pub fn field_name(&self) -> &str {
80        &self.field_name
81    }
82
83    /// Check if field is encrypted
84    pub fn is_encrypted(&self) -> bool {
85        self.is_encrypted
86    }
87
88    /// Get field value
89    pub fn value(&self) -> &[u8] {
90        &self.value
91    }
92
93    /// Convert to plaintext string
94    pub fn to_string(&self) -> Result<String, SecretsError> {
95        String::from_utf8(self.value.clone()).map_err(|e| {
96            SecretsError::EncryptionError(format!(
97                "Invalid UTF-8 in field '{}': {}",
98                self.field_name, e
99            ))
100        })
101    }
102}
103
104/// Mapper for handling encrypted fields in database operations
105///
106/// Transparently encrypts/decrypts fields during read/write operations.
107pub struct FieldMapper {
108    /// Field adapter for encryption/decryption
109    adapter:              Arc<DatabaseFieldAdapter>,
110    /// Field encryption configuration
111    field_encryption_map: HashMap<String, bool>,
112}
113
114impl FieldMapper {
115    /// Create new field mapper
116    ///
117    /// # Arguments
118    ///
119    /// * `adapter` - Field adapter for encryption/decryption
120    /// * `encrypted_fields` - List of fields that should be encrypted
121    pub fn new(adapter: Arc<DatabaseFieldAdapter>, encrypted_fields: Vec<String>) -> Self {
122        let mut field_encryption_map = HashMap::new();
123        for field in encrypted_fields {
124            field_encryption_map.insert(field, true);
125        }
126
127        Self {
128            adapter,
129            field_encryption_map,
130        }
131    }
132
133    /// Check if field is marked for encryption
134    pub fn is_field_encrypted(&self, field_name: &str) -> bool {
135        self.field_encryption_map.get(field_name).copied().unwrap_or(false)
136    }
137
138    /// Encrypt field value before write operation
139    ///
140    /// # Arguments
141    ///
142    /// * `field_name` - Name of field to encrypt
143    /// * `plaintext` - Plaintext value to encrypt
144    ///
145    /// # Returns
146    ///
147    /// Encrypted bytes in format: \[nonce\]\[ciphertext\]\[tag\]
148    pub async fn encrypt_field(
149        &self,
150        field_name: &str,
151        plaintext: &str,
152    ) -> Result<Vec<u8>, SecretsError> {
153        if !self.is_field_encrypted(field_name) {
154            return Err(SecretsError::ValidationError(format!(
155                "Field '{}' is not configured for encryption",
156                field_name
157            )));
158        }
159
160        self.adapter.encrypt_value(field_name, plaintext).await.map_err(|e| {
161            SecretsError::EncryptionError(format!(
162                "Failed to encrypt field '{}': {}",
163                field_name, e
164            ))
165        })
166    }
167
168    /// Decrypt field value after read operation
169    ///
170    /// # Arguments
171    ///
172    /// * `field_name` - Name of field to decrypt
173    /// * `ciphertext` - Encrypted bytes from database
174    ///
175    /// # Returns
176    ///
177    /// Decrypted plaintext string
178    pub async fn decrypt_field(
179        &self,
180        field_name: &str,
181        ciphertext: &[u8],
182    ) -> Result<String, SecretsError> {
183        if !self.is_field_encrypted(field_name) {
184            return Err(SecretsError::ValidationError(format!(
185                "Field '{}' is not configured for decryption",
186                field_name
187            )));
188        }
189
190        self.adapter.decrypt_value(field_name, ciphertext).await.map_err(|e| {
191            SecretsError::EncryptionError(format!(
192                "Failed to decrypt field '{}': {}",
193                field_name, e
194            ))
195        })
196    }
197
198    /// Encrypt multiple fields (batch operation)
199    ///
200    /// Returns FieldMapping objects with encryption status.
201    pub async fn encrypt_fields(
202        &self,
203        fields: &[(String, String)],
204    ) -> Result<Vec<FieldMapping>, SecretsError> {
205        let mut results = Vec::new();
206
207        for (field_name, plaintext) in fields {
208            if self.is_field_encrypted(field_name) {
209                let encrypted = self.encrypt_field(field_name, plaintext).await?;
210                results.push(FieldMapping::new(field_name.clone(), true, encrypted));
211            } else {
212                // Unencrypted field - pass through as bytes
213                results.push(FieldMapping::new(
214                    field_name.clone(),
215                    false,
216                    plaintext.as_bytes().to_vec(),
217                ));
218            }
219        }
220
221        Ok(results)
222    }
223
224    /// Decrypt multiple fields (batch operation)
225    ///
226    /// Returns FieldMapping objects with decrypted values.
227    pub async fn decrypt_fields(
228        &self,
229        fields: &[(String, Vec<u8>)],
230    ) -> Result<Vec<FieldMapping>, SecretsError> {
231        let mut results = Vec::new();
232
233        for (field_name, ciphertext) in fields {
234            if self.is_field_encrypted(field_name) {
235                let plaintext = self.decrypt_field(field_name, ciphertext).await?;
236                results.push(FieldMapping::new(field_name.clone(), true, plaintext.into_bytes()));
237            } else {
238                // Unencrypted field - pass through unchanged
239                results.push(FieldMapping::new(field_name.clone(), false, ciphertext.clone()));
240            }
241        }
242
243        Ok(results)
244    }
245
246    /// Get list of encrypted fields
247    pub fn encrypted_fields(&self) -> Vec<String> {
248        self.field_encryption_map.keys().cloned().collect()
249    }
250
251    /// Check if any fields are encrypted
252    pub fn has_encrypted_fields(&self) -> bool {
253        !self.field_encryption_map.is_empty()
254    }
255
256    /// Register field for encryption
257    ///
258    /// Can be used to dynamically add encrypted fields after mapper creation.
259    pub fn register_encrypted_field(&mut self, field_name: impl Into<String>) {
260        self.field_encryption_map.insert(field_name.into(), true);
261    }
262
263    /// Unregister field from encryption
264    pub fn unregister_encrypted_field(&mut self, field_name: &str) {
265        self.field_encryption_map.remove(field_name);
266    }
267
268    /// Get count of encrypted fields
269    pub fn encrypted_field_count(&self) -> usize {
270        self.field_encryption_map.len()
271    }
272
273    /// Validate field encryption configuration
274    ///
275    /// Returns error if configuration is inconsistent or incomplete.
276    pub fn validate_configuration(&self) -> Result<(), SecretsError> {
277        if self.encrypted_fields().is_empty() {
278            return Err(SecretsError::ValidationError(
279                "No encrypted fields configured".to_string(),
280            ));
281        }
282        Ok(())
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_field_mapping_creation() {
292        let mapping = FieldMapping::new("email", true, b"encrypted_data".to_vec());
293        assert_eq!(mapping.field_name(), "email");
294        assert!(mapping.is_encrypted());
295        assert_eq!(mapping.value(), b"encrypted_data");
296    }
297
298    #[test]
299    fn test_field_mapping_to_string() {
300        let mapping = FieldMapping::new("email", true, "user@example.com".as_bytes().to_vec());
301        let result = mapping.to_string();
302        assert!(result.is_ok());
303        assert_eq!(result.unwrap(), "user@example.com");
304    }
305
306    #[test]
307    fn test_field_mapping_to_string_invalid_utf8() {
308        let mapping = FieldMapping::new("email", true, vec![0xFF, 0xFE]);
309        let result = mapping.to_string();
310        assert!(result.is_err());
311    }
312
313    #[test]
314    fn test_field_mapper_field_encryption_map() {
315        let encrypted_fields = vec!["email".to_string(), "phone".to_string()];
316        let mut field_encryption_map = HashMap::new();
317        for field in encrypted_fields {
318            field_encryption_map.insert(field, true);
319        }
320
321        assert!(field_encryption_map.get("email").copied().unwrap_or(false));
322        assert!(field_encryption_map.get("phone").copied().unwrap_or(false));
323        assert!(!field_encryption_map.get("name").copied().unwrap_or(false));
324    }
325
326    #[test]
327    fn test_encrypted_fields_list() {
328        let encrypted_fields = vec!["email".to_string(), "phone".to_string()];
329        let mut field_encryption_map = HashMap::new();
330        for field in encrypted_fields.clone() {
331            field_encryption_map.insert(field, true);
332        }
333
334        let result: Vec<String> = field_encryption_map.keys().cloned().collect();
335        assert_eq!(result.len(), 2);
336        assert!(result.contains(&"email".to_string()));
337        assert!(result.contains(&"phone".to_string()));
338    }
339
340    #[test]
341    fn test_has_encrypted_fields() {
342        let encrypted_fields = vec!["email".to_string()];
343        let mut field_encryption_map = HashMap::new();
344        for field in encrypted_fields {
345            field_encryption_map.insert(field, true);
346        }
347
348        assert!(!field_encryption_map.is_empty());
349
350        let empty_map: HashMap<String, bool> = HashMap::new();
351        assert!(empty_map.is_empty());
352    }
353
354    #[test]
355    fn test_field_mapping_not_encrypted() {
356        let mapping = FieldMapping::new("name", false, b"John Doe".to_vec());
357        assert_eq!(mapping.field_name(), "name");
358        assert!(!mapping.is_encrypted());
359    }
360
361    #[test]
362    fn test_field_mapping_value_access() {
363        let data = b"sensitive data".to_vec();
364        let mapping = FieldMapping::new("field", true, data.clone());
365        assert_eq!(mapping.value(), data.as_slice());
366    }
367}