Skip to main content

eth_id/claims/
validator.rs

1use 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}