1use crate::error::{Result, EthIdError};
2use crate::parser::ParsedDocument;
3use crate::claims::{ClaimQuery, ClaimType};
4use super::{DataMinimizer, DataVirtualizer, PrivacyMetadata};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
8pub enum FilterMode {
9 HashPartial,
10 Minimization,
11 Virtualization,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct FilteredData {
16 pub content: String,
17 pub metadata: PrivacyMetadata,
18 pub claim_context: String,
19}
20
21pub struct PrivacyFilter {
22 minimizer: DataMinimizer,
23 virtualizer: DataVirtualizer,
24}
25
26impl PrivacyFilter {
27 pub fn new() -> Self {
28 Self {
29 minimizer: DataMinimizer::new(),
30 virtualizer: DataVirtualizer::new(),
31 }
32 }
33
34 pub fn filter(
35 &self,
36 document: &ParsedDocument,
37 claim_query: &ClaimQuery,
38 ) -> Result<FilteredData> {
39 let mode = self.determine_filter_mode(claim_query);
40
41 let mut metadata = PrivacyMetadata::new(mode);
42 metadata.set_original_hash(document.raw_text());
43
44 let (content, claim_context) = match mode {
45 FilterMode::HashPartial => {
46 tracing::debug!("Using hash partial mode");
47 self.apply_hash_partial(document, claim_query, &mut metadata)?
48 }
49 FilterMode::Minimization => {
50 tracing::debug!("Using minimization mode");
51 self.apply_minimization(document, claim_query, &mut metadata)?
52 }
53 FilterMode::Virtualization => {
54 tracing::debug!("Using virtualization mode");
55 self.apply_virtualization(document, claim_query, &mut metadata)?
56 }
57 };
58
59 metadata.set_filtered_hash(&content);
60
61 Ok(FilteredData {
62 content,
63 metadata,
64 claim_context,
65 })
66 }
67
68 fn determine_filter_mode(&self, claim_query: &ClaimQuery) -> FilterMode {
69 match claim_query.claim_type() {
70 ClaimType::Date => FilterMode::Virtualization,
71 ClaimType::Identity => FilterMode::HashPartial,
72 ClaimType::Amount => FilterMode::Minimization,
73 ClaimType::Signature => FilterMode::Minimization,
74 ClaimType::Presence => FilterMode::Minimization,
75 ClaimType::Comparative => FilterMode::Minimization,
76 }
77 }
78
79 fn apply_hash_partial(
80 &self,
81 document: &ParsedDocument,
82 claim_query: &ClaimQuery,
83 metadata: &mut PrivacyMetadata,
84 ) -> Result<(String, String)> {
85 if let ClaimQuery::Identity(identity_claim) = claim_query {
86 if let Some(cpf) = document.get_field("cpf") {
87 let masked_cpf = self.mask_cpf(cpf);
88 metadata.fields_included.push("cpf".to_string());
89 metadata.fields_masked.push("cpf".to_string());
90
91 let content = format!("CPF: {}", masked_cpf);
92 let context = format!("Verify CPF identity claim");
93
94 return Ok((content, context));
95 }
96 }
97
98 Ok((String::new(), String::new()))
99 }
100
101 fn apply_minimization(
102 &self,
103 document: &ParsedDocument,
104 claim_query: &ClaimQuery,
105 metadata: &mut PrivacyMetadata,
106 ) -> Result<(String, String)> {
107 let relevant_fields = self.minimizer.extract_relevant_fields(document, claim_query)?;
108
109 for field in &relevant_fields {
110 metadata.fields_included.push(field.clone());
111 }
112
113 let content = self.minimizer.build_minimal_context(document, &relevant_fields)?;
114 let context = format!("Minimal context for {:?} claim", claim_query.claim_type());
115
116 Ok((content, context))
117 }
118
119 fn apply_virtualization(
120 &self,
121 document: &ParsedDocument,
122 claim_query: &ClaimQuery,
123 metadata: &mut PrivacyMetadata,
124 ) -> Result<(String, String)> {
125 if let ClaimQuery::Date(date_claim) = claim_query {
126 let virtual_result = self.virtualizer.virtualize_date_check(document, date_claim)?;
127
128 metadata.fields_included.push("date_calculation".to_string());
129
130 let content = format!("Age calculation result: {}", virtual_result);
131 let context = "Virtualized age verification".to_string();
132
133 return Ok((content, context));
134 }
135
136 Ok((String::new(), String::new()))
137 }
138
139 pub fn mask_cpf(&self, cpf: &str) -> String {
140 if cpf.len() >= 14 {
141 format!("{}.***.***-{}", &cpf[0..3], &cpf[12..14])
142 } else {
143 "***.***.***-**".to_string()
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_mask_cpf() {
154 let filter = PrivacyFilter::new();
155 let masked = filter.mask_cpf("123.456.789-00");
156 assert_eq!(masked, "123.***.***-00");
157 }
158}