eth_id/claims/
validator.rs1use super::types::*;
2use crate::error::{Result, EthIdError};
3
4pub struct ClaimValidator;
5
6impl ClaimValidator {
7 pub fn validate_claim_query(query: &ClaimQuery) -> Result<()> {
8 match query {
9 ClaimQuery::Date(claim) => Self::validate_date_claim(claim),
10 ClaimQuery::Identity(claim) => Self::validate_identity_claim(claim),
11 ClaimQuery::Amount(claim) => Self::validate_amount_claim(claim),
12 ClaimQuery::Signature(claim) => Self::validate_signature_claim(claim),
13 ClaimQuery::Presence(claim) => Self::validate_presence_claim(claim),
14 ClaimQuery::Comparative(claim) => Self::validate_comparative_claim(claim),
15 }
16 }
17
18 fn validate_date_claim(claim: &DateClaim) -> Result<()> {
19 match &claim.operation {
20 DateOperation::AgeGreaterThan | DateOperation::AgeLessThan => {
21 if claim.age_threshold.is_none() {
22 return Err(EthIdError::ClaimValidation(
23 "Age threshold required for age operations".to_string()
24 ));
25 }
26
27 let age = claim.age_threshold.unwrap();
28 if age > 150 {
29 return Err(EthIdError::ClaimValidation(
30 "Age threshold too high (max 150)".to_string()
31 ));
32 }
33 }
34 DateOperation::IssuedWithinDays => {
35 if claim.days_threshold.is_none() {
36 return Err(EthIdError::ClaimValidation(
37 "Days threshold required for issued within operation".to_string()
38 ));
39 }
40 }
41 DateOperation::ExpiresAfter => {
42 if claim.threshold.is_none() {
43 return Err(EthIdError::ClaimValidation(
44 "Date threshold required for expires after operation".to_string()
45 ));
46 }
47 }
48 DateOperation::IsBetween { .. } => {}
49 }
50
51 Ok(())
52 }
53
54 fn validate_identity_claim(claim: &IdentityClaim) -> Result<()> {
55 if matches!(claim.operation, IdentityOperation::Matches | IdentityOperation::HashMatches) {
56 if claim.expected_value.is_none() {
57 return Err(EthIdError::ClaimValidation(
58 "Expected value required for match operations".to_string()
59 ));
60 }
61 }
62
63 if matches!(claim.field, IdentityField::CPF) {
64 if let Some(cpf) = &claim.expected_value {
65 Self::validate_cpf_format(cpf)?;
66 }
67 }
68
69 Ok(())
70 }
71
72 fn validate_amount_claim(claim: &AmountClaim) -> Result<()> {
73 if claim.field.is_empty() {
74 return Err(EthIdError::ClaimValidation(
75 "Field name required for amount claim".to_string()
76 ));
77 }
78
79 if claim.threshold < 0.0 {
80 return Err(EthIdError::ClaimValidation(
81 "Amount threshold cannot be negative".to_string()
82 ));
83 }
84
85 if let AmountOperation::Between { min, max } = &claim.operation {
86 if min >= max {
87 return Err(EthIdError::ClaimValidation(
88 "Min must be less than max for between operation".to_string()
89 ));
90 }
91 }
92
93 Ok(())
94 }
95
96 fn validate_signature_claim(_claim: &SignatureClaim) -> Result<()> {
97 Ok(())
98 }
99
100 fn validate_presence_claim(claim: &PresenceClaim) -> Result<()> {
101 if claim.field.is_empty() {
102 return Err(EthIdError::ClaimValidation(
103 "Field name required for presence claim".to_string()
104 ));
105 }
106
107 Ok(())
108 }
109
110 fn validate_comparative_claim(claim: &ComparativeClaim) -> Result<()> {
111 if claim.field1.is_empty() || claim.field2.is_empty() {
112 return Err(EthIdError::ClaimValidation(
113 "Both field names required for comparative claim".to_string()
114 ));
115 }
116
117 Ok(())
118 }
119
120 fn validate_cpf_format(cpf: &str) -> Result<()> {
121 let cpf_clean = cpf.replace(['.', '-'], "");
122
123 if cpf_clean.len() != 11 {
124 return Err(EthIdError::ClaimValidation(
125 "CPF must have 11 digits".to_string()
126 ));
127 }
128
129 if !cpf_clean.chars().all(|c| c.is_ascii_digit()) {
130 return Err(EthIdError::ClaimValidation(
131 "CPF must contain only digits".to_string()
132 ));
133 }
134
135 Ok(())
136 }
137}