jwt_verify/claims/
validators_impl.rs

1use regex::Regex;
2use serde_json::Value;
3use std::collections::HashSet;
4use std::fmt;
5use std::ops::RangeInclusive;
6
7use crate::claims::CognitoJwtClaims;
8
9/// Trait for custom claim validators
10pub trait ClaimValidator: Send + Sync {
11    /// Validate a claim
12    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String>;
13}
14
15/// Validator that checks if a claim exists
16pub struct ExistenceValidator {
17    /// Claim name
18    claim_name: String,
19}
20
21impl ExistenceValidator {
22    /// Create a new existence validator
23    pub fn new(claim_name: &str) -> Self {
24        Self {
25            claim_name: claim_name.to_string(),
26        }
27    }
28}
29
30impl ClaimValidator for ExistenceValidator {
31    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
32        if claims.custom_claims.contains_key(&self.claim_name) {
33            Ok(())
34        } else {
35            Err(format!("Claim '{}' is required", self.claim_name))
36        }
37    }
38}
39
40impl fmt::Debug for ExistenceValidator {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.debug_struct("ExistenceValidator")
43            .field("claim_name", &self.claim_name)
44            .finish()
45    }
46}
47
48/// Validator that checks if a string claim has a specific value
49pub struct StringValueValidator {
50    /// Claim name
51    claim_name: String,
52    /// Expected value
53    expected_value: String,
54}
55
56impl StringValueValidator {
57    /// Create a new string value validator
58    pub fn new(claim_name: &str, expected_value: &str) -> Self {
59        Self {
60            claim_name: claim_name.to_string(),
61            expected_value: expected_value.to_string(),
62        }
63    }
64}
65
66impl ClaimValidator for StringValueValidator {
67    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
68        if let Some(Value::String(value)) = claims.custom_claims.get(&self.claim_name) {
69            if value == &self.expected_value {
70                Ok(())
71            } else {
72                Err(format!(
73                    "Claim '{}' has invalid value: expected '{}', got '{}'",
74                    self.claim_name, self.expected_value, value
75                ))
76            }
77        } else {
78            Err(format!(
79                "Claim '{}' is missing or not a string",
80                self.claim_name
81            ))
82        }
83    }
84}
85
86impl fmt::Debug for StringValueValidator {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.debug_struct("StringValueValidator")
89            .field("claim_name", &self.claim_name)
90            .field("expected_value", &self.expected_value)
91            .finish()
92    }
93}
94
95/// Validator that checks if a string claim is one of the allowed values
96pub struct AllowedValuesValidator {
97    /// Claim name
98    claim_name: String,
99    /// Allowed values
100    allowed_values: HashSet<String>,
101}
102
103impl AllowedValuesValidator {
104    /// Create a new allowed values validator
105    pub fn new(claim_name: &str, allowed_values: &[&str]) -> Self {
106        Self {
107            claim_name: claim_name.to_string(),
108            allowed_values: allowed_values.iter().map(|s| s.to_string()).collect(),
109        }
110    }
111}
112
113impl ClaimValidator for AllowedValuesValidator {
114    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
115        if let Some(Value::String(value)) = claims.custom_claims.get(&self.claim_name) {
116            if self.allowed_values.contains(value) {
117                Ok(())
118            } else {
119                Err(format!(
120                    "Claim '{}' has invalid value: '{}' is not one of the allowed values",
121                    self.claim_name, value
122                ))
123            }
124        } else {
125            Err(format!(
126                "Claim '{}' is missing or not a string",
127                self.claim_name
128            ))
129        }
130    }
131}
132
133impl fmt::Debug for AllowedValuesValidator {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.debug_struct("AllowedValuesValidator")
136            .field("claim_name", &self.claim_name)
137            .field("allowed_values", &self.allowed_values)
138            .finish()
139    }
140}
141
142/// Validator that checks if a numeric claim is within a range
143pub struct NumericRangeValidator {
144    /// Claim name
145    claim_name: String,
146    /// Allowed range
147    range: RangeInclusive<f64>,
148}
149
150impl NumericRangeValidator {
151    /// Create a new numeric range validator
152    pub fn new(claim_name: &str, min: f64, max: f64) -> Self {
153        Self {
154            claim_name: claim_name.to_string(),
155            range: min..=max,
156        }
157    }
158}
159
160impl ClaimValidator for NumericRangeValidator {
161    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
162        if let Some(value) = claims.custom_claims.get(&self.claim_name) {
163            let num = match value {
164                Value::Number(n) => n.as_f64().unwrap_or(0.0),
165                Value::String(s) => s.parse::<f64>().unwrap_or(0.0),
166                _ => {
167                    return Err(format!("Claim '{}' is not a number", self.claim_name));
168                }
169            };
170
171            if self.range.contains(&num) {
172                Ok(())
173            } else {
174                Err(format!(
175                    "Claim '{}' is outside the allowed range: {} not in {:?}",
176                    self.claim_name, num, self.range
177                ))
178            }
179        } else {
180            Err(format!("Claim '{}' is missing", self.claim_name))
181        }
182    }
183}
184
185impl fmt::Debug for NumericRangeValidator {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        f.debug_struct("NumericRangeValidator")
188            .field("claim_name", &self.claim_name)
189            .field("range", &format!("{:?}", self.range))
190            .finish()
191    }
192}
193
194/// Validator that checks if a boolean claim has a specific value
195pub struct BooleanValidator {
196    /// Claim name
197    claim_name: String,
198    /// Expected value
199    expected_value: bool,
200}
201
202impl BooleanValidator {
203    /// Create a new boolean validator
204    pub fn new(claim_name: &str, expected_value: bool) -> Self {
205        Self {
206            claim_name: claim_name.to_string(),
207            expected_value,
208        }
209    }
210}
211
212impl ClaimValidator for BooleanValidator {
213    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
214        if let Some(Value::Bool(value)) = claims.custom_claims.get(&self.claim_name) {
215            if *value == self.expected_value {
216                Ok(())
217            } else {
218                Err(format!(
219                    "Claim '{}' has invalid value: expected {}, got {}",
220                    self.claim_name, self.expected_value, value
221                ))
222            }
223        } else {
224            Err(format!(
225                "Claim '{}' is missing or not a boolean",
226                self.claim_name
227            ))
228        }
229    }
230}
231
232impl fmt::Debug for BooleanValidator {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("BooleanValidator")
235            .field("claim_name", &self.claim_name)
236            .field("expected_value", &self.expected_value)
237            .finish()
238    }
239}
240
241/// Validator that checks if a string claim matches a regex pattern
242pub struct RegexValidator {
243    /// Claim name
244    claim_name: String,
245    /// Regex pattern
246    pattern: Regex,
247    /// Pattern string for debug
248    pattern_str: String,
249}
250
251impl RegexValidator {
252    /// Create a new regex validator
253    pub fn new(claim_name: &str, pattern: &str) -> Result<Self, regex::Error> {
254        Ok(Self {
255            claim_name: claim_name.to_string(),
256            pattern: Regex::new(pattern)?,
257            pattern_str: pattern.to_string(),
258        })
259    }
260}
261
262impl ClaimValidator for RegexValidator {
263    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
264        if let Some(Value::String(value)) = claims.custom_claims.get(&self.claim_name) {
265            if self.pattern.is_match(value) {
266                Ok(())
267            } else {
268                Err(format!(
269                    "Claim '{}' does not match pattern: '{}' does not match '{}'",
270                    self.claim_name, value, self.pattern_str
271                ))
272            }
273        } else {
274            Err(format!(
275                "Claim '{}' is missing or not a string",
276                self.claim_name
277            ))
278        }
279    }
280}
281
282impl fmt::Debug for RegexValidator {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        f.debug_struct("RegexValidator")
285            .field("claim_name", &self.claim_name)
286            .field("pattern", &self.pattern_str)
287            .finish()
288    }
289}
290
291/// Validator that checks if an array claim contains a specific value
292pub struct ArrayContainsValidator {
293    /// Claim name
294    claim_name: String,
295    /// Value to check for
296    value: Value,
297}
298
299impl ArrayContainsValidator {
300    /// Create a new array contains validator for a string value
301    pub fn new_string(claim_name: &str, value: &str) -> Self {
302        Self {
303            claim_name: claim_name.to_string(),
304            value: Value::String(value.to_string()),
305        }
306    }
307
308    /// Create a new array contains validator for a numeric value
309    pub fn new_number(claim_name: &str, value: f64) -> Self {
310        Self {
311            claim_name: claim_name.to_string(),
312            value: Value::Number(serde_json::Number::from_f64(value).unwrap()),
313        }
314    }
315
316    /// Create a new array contains validator for a boolean value
317    pub fn new_bool(claim_name: &str, value: bool) -> Self {
318        Self {
319            claim_name: claim_name.to_string(),
320            value: Value::Bool(value),
321        }
322    }
323}
324
325impl ClaimValidator for ArrayContainsValidator {
326    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
327        if let Some(Value::Array(array)) = claims.custom_claims.get(&self.claim_name) {
328            if array.contains(&self.value) {
329                Ok(())
330            } else {
331                Err(format!(
332                    "Claim '{}' does not contain expected value: {:?}",
333                    self.claim_name, self.value
334                ))
335            }
336        } else {
337            Err(format!(
338                "Claim '{}' is missing or not an array",
339                self.claim_name
340            ))
341        }
342    }
343}
344
345impl fmt::Debug for ArrayContainsValidator {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        f.debug_struct("ArrayContainsValidator")
348            .field("claim_name", &self.claim_name)
349            .field("value", &self.value)
350            .finish()
351    }
352}
353
354/// Validator that combines multiple validators with AND logic
355pub struct AndValidator {
356    /// Validators
357    validators: Vec<Box<dyn ClaimValidator>>,
358}
359
360impl AndValidator {
361    /// Create a new AND validator
362    pub fn new(validators: Vec<Box<dyn ClaimValidator>>) -> Self {
363        Self { validators }
364    }
365}
366
367impl ClaimValidator for AndValidator {
368    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
369        for validator in &self.validators {
370            if let Err(reason) = validator.validate(claims) {
371                return Err(reason);
372            }
373        }
374        Ok(())
375    }
376}
377
378impl fmt::Debug for AndValidator {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        f.debug_struct("AndValidator")
381            .field("validators_count", &self.validators.len())
382            .finish()
383    }
384}
385
386/// Validator that combines multiple validators with OR logic
387pub struct OrValidator {
388    /// Validators
389    validators: Vec<Box<dyn ClaimValidator>>,
390}
391
392impl OrValidator {
393    /// Create a new OR validator
394    pub fn new(validators: Vec<Box<dyn ClaimValidator>>) -> Self {
395        Self { validators }
396    }
397}
398
399impl ClaimValidator for OrValidator {
400    fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
401        let mut errors = Vec::new();
402        for validator in &self.validators {
403            match validator.validate(claims) {
404                Ok(()) => return Ok(()),
405                Err(reason) => errors.push(reason),
406            }
407        }
408        Err(format!(
409            "None of the validators passed: {}",
410            errors.join(", ")
411        ))
412    }
413}
414
415impl fmt::Debug for OrValidator {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        f.debug_struct("OrValidator")
418            .field("validators_count", &self.validators.len())
419            .finish()
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use std::collections::HashMap;
427
428    #[test]
429    fn test_existence_validator() {
430        let validator = ExistenceValidator::new("test_claim");
431
432        // Test with existing claim
433        let mut claims = CognitoJwtClaims {
434            sub: "user123".to_string(),
435            iss: "https://example.com".to_string(),
436            client_id: "client123".to_string(),
437            origin_jti: None,
438            event_id: None,
439            token_use: "id".to_string(),
440            scope: None,
441            auth_time: 0,
442            exp: 0,
443            iat: 0,
444            jti: "jti123".to_string(),
445            username: None,
446            custom_claims: HashMap::new(),
447        };
448
449        claims
450            .custom_claims
451            .insert("test_claim".to_string(), Value::String("value".to_string()));
452
453        assert!(validator.validate(&claims).is_ok());
454
455        // Test with missing claim
456        let claims = CognitoJwtClaims {
457            sub: "user123".to_string(),
458            iss: "https://example.com".to_string(),
459            client_id: "client123".to_string(),
460            origin_jti: None,
461            event_id: None,
462            token_use: "id".to_string(),
463            scope: None,
464            auth_time: 0,
465            exp: 0,
466            iat: 0,
467            jti: "jti123".to_string(),
468            username: None,
469            custom_claims: HashMap::new(),
470        };
471
472        assert!(validator.validate(&claims).is_err());
473    }
474
475    #[test]
476    fn test_string_value_validator() {
477        let validator = StringValueValidator::new("test_claim", "expected_value");
478
479        // Test with matching value
480        let mut claims = CognitoJwtClaims {
481            sub: "user123".to_string(),
482            iss: "https://example.com".to_string(),
483            client_id: "client123".to_string(),
484            origin_jti: None,
485            event_id: None,
486            token_use: "id".to_string(),
487            scope: None,
488            auth_time: 0,
489            exp: 0,
490            iat: 0,
491            jti: "jti123".to_string(),
492            username: None,
493            custom_claims: HashMap::new(),
494        };
495
496        claims.custom_claims.insert(
497            "test_claim".to_string(),
498            Value::String("expected_value".to_string()),
499        );
500
501        assert!(validator.validate(&claims).is_ok());
502
503        // Test with non-matching value
504        let mut claims = CognitoJwtClaims {
505            sub: "user123".to_string(),
506            iss: "https://example.com".to_string(),
507            client_id: "client123".to_string(),
508            origin_jti: None,
509            event_id: None,
510            token_use: "id".to_string(),
511            scope: None,
512            auth_time: 0,
513            exp: 0,
514            iat: 0,
515            jti: "jti123".to_string(),
516            username: None,
517            custom_claims: HashMap::new(),
518        };
519
520        claims.custom_claims.insert(
521            "test_claim".to_string(),
522            Value::String("wrong_value".to_string()),
523        );
524
525        assert!(validator.validate(&claims).is_err());
526
527        // Test with missing claim
528        let claims = CognitoJwtClaims {
529            sub: "user123".to_string(),
530            iss: "https://example.com".to_string(),
531            client_id: "client123".to_string(),
532            origin_jti: None,
533            event_id: None,
534            token_use: "id".to_string(),
535            scope: None,
536            auth_time: 0,
537            exp: 0,
538            iat: 0,
539            jti: "jti123".to_string(),
540            username: None,
541            custom_claims: HashMap::new(),
542        };
543
544        assert!(validator.validate(&claims).is_err());
545    }
546
547    #[test]
548    fn test_allowed_values_validator() {
549        let validator = AllowedValuesValidator::new("test_claim", &["value1", "value2", "value3"]);
550
551        // Test with allowed value
552        let mut claims = CognitoJwtClaims {
553            sub: "user123".to_string(),
554            iss: "https://example.com".to_string(),
555            client_id: "client123".to_string(),
556            origin_jti: None,
557            event_id: None,
558            token_use: "id".to_string(),
559            scope: None,
560            auth_time: 0,
561            exp: 0,
562            iat: 0,
563            jti: "jti123".to_string(),
564            username: None,
565            custom_claims: HashMap::new(),
566        };
567
568        claims.custom_claims.insert(
569            "test_claim".to_string(),
570            Value::String("value2".to_string()),
571        );
572
573        assert!(validator.validate(&claims).is_ok());
574
575        // Test with non-allowed value
576        let mut claims = CognitoJwtClaims {
577            sub: "user123".to_string(),
578            iss: "https://example.com".to_string(),
579            client_id: "client123".to_string(),
580            origin_jti: None,
581            event_id: None,
582            token_use: "id".to_string(),
583            scope: None,
584            auth_time: 0,
585            exp: 0,
586            iat: 0,
587            jti: "jti123".to_string(),
588            username: None,
589            custom_claims: HashMap::new(),
590        };
591
592        claims.custom_claims.insert(
593            "test_claim".to_string(),
594            Value::String("value4".to_string()),
595        );
596
597        assert!(validator.validate(&claims).is_err());
598    }
599
600    #[test]
601    fn test_numeric_range_validator() {
602        let validator = NumericRangeValidator::new("test_claim", 10.0, 20.0);
603
604        // Test with value in range
605        let mut claims = CognitoJwtClaims {
606            sub: "user123".to_string(),
607            iss: "https://example.com".to_string(),
608            client_id: "client123".to_string(),
609            origin_jti: None,
610            event_id: None,
611            token_use: "id".to_string(),
612            scope: None,
613            auth_time: 0,
614            exp: 0,
615            iat: 0,
616            jti: "jti123".to_string(),
617            username: None,
618            custom_claims: HashMap::new(),
619        };
620
621        claims.custom_claims.insert(
622            "test_claim".to_string(),
623            Value::Number(serde_json::Number::from_f64(15.0).unwrap()),
624        );
625
626        assert!(validator.validate(&claims).is_ok());
627
628        // Test with value out of range
629        let mut claims = CognitoJwtClaims {
630            sub: "user123".to_string(),
631            iss: "https://example.com".to_string(),
632            client_id: "client123".to_string(),
633            origin_jti: None,
634            event_id: None,
635            token_use: "id".to_string(),
636            scope: None,
637            auth_time: 0,
638            exp: 0,
639            iat: 0,
640            jti: "jti123".to_string(),
641            username: None,
642            custom_claims: HashMap::new(),
643        };
644
645        claims.custom_claims.insert(
646            "test_claim".to_string(),
647            Value::Number(serde_json::Number::from_f64(25.0).unwrap()),
648        );
649
650        assert!(validator.validate(&claims).is_err());
651    }
652
653    #[test]
654    fn test_boolean_validator() {
655        let validator = BooleanValidator::new("test_claim", true);
656
657        // Test with matching value
658        let mut claims = CognitoJwtClaims {
659            sub: "user123".to_string(),
660            iss: "https://example.com".to_string(),
661            client_id: "client123".to_string(),
662            origin_jti: None,
663            event_id: None,
664            token_use: "id".to_string(),
665            scope: None,
666            auth_time: 0,
667            exp: 0,
668            iat: 0,
669            jti: "jti123".to_string(),
670            username: None,
671            custom_claims: HashMap::new(),
672        };
673
674        claims
675            .custom_claims
676            .insert("test_claim".to_string(), Value::Bool(true));
677
678        assert!(validator.validate(&claims).is_ok());
679
680        // Test with non-matching value
681        let mut claims = CognitoJwtClaims {
682            sub: "user123".to_string(),
683            iss: "https://example.com".to_string(),
684            client_id: "client123".to_string(),
685            origin_jti: None,
686            event_id: None,
687            token_use: "id".to_string(),
688            scope: None,
689            auth_time: 0,
690            exp: 0,
691            iat: 0,
692            jti: "jti123".to_string(),
693            username: None,
694            custom_claims: HashMap::new(),
695        };
696
697        claims
698            .custom_claims
699            .insert("test_claim".to_string(), Value::Bool(false));
700
701        assert!(validator.validate(&claims).is_err());
702    }
703
704    #[test]
705    fn test_regex_validator() {
706        let validator = RegexValidator::new("test_claim", r"^[a-z]{3}\d{2}$").unwrap();
707
708        // Test with matching value
709        let mut claims = CognitoJwtClaims {
710            sub: "user123".to_string(),
711            iss: "https://example.com".to_string(),
712            client_id: "client123".to_string(),
713            origin_jti: None,
714            event_id: None,
715            token_use: "id".to_string(),
716            scope: None,
717            auth_time: 0,
718            exp: 0,
719            iat: 0,
720            jti: "jti123".to_string(),
721            username: None,
722            custom_claims: HashMap::new(),
723        };
724
725        claims
726            .custom_claims
727            .insert("test_claim".to_string(), Value::String("abc12".to_string()));
728
729        assert!(validator.validate(&claims).is_ok());
730
731        // Test with non-matching value
732        let mut claims = CognitoJwtClaims {
733            sub: "user123".to_string(),
734            iss: "https://example.com".to_string(),
735            client_id: "client123".to_string(),
736            origin_jti: None,
737            event_id: None,
738            token_use: "id".to_string(),
739            scope: None,
740            auth_time: 0,
741            exp: 0,
742            iat: 0,
743            jti: "jti123".to_string(),
744            username: None,
745            custom_claims: HashMap::new(),
746        };
747
748        claims
749            .custom_claims
750            .insert("test_claim".to_string(), Value::String("ABC12".to_string()));
751
752        assert!(validator.validate(&claims).is_err());
753    }
754
755    #[test]
756    fn test_array_contains_validator() {
757        let validator = ArrayContainsValidator::new_string("test_claim", "value2");
758
759        // Test with array containing the value
760        let mut claims = CognitoJwtClaims {
761            sub: "user123".to_string(),
762            iss: "https://example.com".to_string(),
763            client_id: "client123".to_string(),
764            origin_jti: None,
765            event_id: None,
766            token_use: "id".to_string(),
767            scope: None,
768            auth_time: 0,
769            exp: 0,
770            iat: 0,
771            jti: "jti123".to_string(),
772            username: None,
773            custom_claims: HashMap::new(),
774        };
775
776        let array = vec![
777            Value::String("value1".to_string()),
778            Value::String("value2".to_string()),
779            Value::String("value3".to_string()),
780        ];
781
782        claims
783            .custom_claims
784            .insert("test_claim".to_string(), Value::Array(array));
785
786        assert!(validator.validate(&claims).is_ok());
787
788        // Test with array not containing the value
789        let mut claims = CognitoJwtClaims {
790            sub: "user123".to_string(),
791            iss: "https://example.com".to_string(),
792            client_id: "client123".to_string(),
793            origin_jti: None,
794            event_id: None,
795            token_use: "id".to_string(),
796            scope: None,
797            auth_time: 0,
798            exp: 0,
799            iat: 0,
800            jti: "jti123".to_string(),
801            username: None,
802            custom_claims: HashMap::new(),
803        };
804
805        let array = vec![
806            Value::String("value1".to_string()),
807            Value::String("value3".to_string()),
808            Value::String("value4".to_string()),
809        ];
810
811        claims
812            .custom_claims
813            .insert("test_claim".to_string(), Value::Array(array));
814
815        assert!(validator.validate(&claims).is_err());
816    }
817
818    #[test]
819    fn test_and_validator() {
820        let validator1 = Box::new(ExistenceValidator::new("claim1"));
821        let validator2 = Box::new(ExistenceValidator::new("claim2"));
822
823        let and_validator = AndValidator::new(vec![validator1, validator2]);
824
825        // Test with both claims present
826        let mut claims = CognitoJwtClaims {
827            sub: "user123".to_string(),
828            iss: "https://example.com".to_string(),
829            client_id: "client123".to_string(),
830            origin_jti: None,
831            event_id: None,
832            token_use: "id".to_string(),
833            scope: None,
834            auth_time: 0,
835            exp: 0,
836            iat: 0,
837            jti: "jti123".to_string(),
838            username: None,
839            custom_claims: HashMap::new(),
840        };
841
842        claims
843            .custom_claims
844            .insert("claim1".to_string(), Value::String("value1".to_string()));
845        claims
846            .custom_claims
847            .insert("claim2".to_string(), Value::String("value2".to_string()));
848
849        assert!(and_validator.validate(&claims).is_ok());
850
851        // Test with one claim missing
852        let mut claims = CognitoJwtClaims {
853            sub: "user123".to_string(),
854            iss: "https://example.com".to_string(),
855            client_id: "client123".to_string(),
856            origin_jti: None,
857            event_id: None,
858            token_use: "id".to_string(),
859            scope: None,
860            auth_time: 0,
861            exp: 0,
862            iat: 0,
863            jti: "jti123".to_string(),
864            username: None,
865            custom_claims: HashMap::new(),
866        };
867
868        claims
869            .custom_claims
870            .insert("claim1".to_string(), Value::String("value1".to_string()));
871
872        assert!(and_validator.validate(&claims).is_err());
873    }
874
875    #[test]
876    fn test_or_validator() {
877        let validator1 = Box::new(ExistenceValidator::new("claim1"));
878        let validator2 = Box::new(ExistenceValidator::new("claim2"));
879
880        let or_validator = OrValidator::new(vec![validator1, validator2]);
881
882        // Test with both claims present
883        let mut claims = CognitoJwtClaims {
884            sub: "user123".to_string(),
885            iss: "https://example.com".to_string(),
886            client_id: "client123".to_string(),
887            origin_jti: None,
888            event_id: None,
889            token_use: "id".to_string(),
890            scope: None,
891            auth_time: 0,
892            exp: 0,
893            iat: 0,
894            jti: "jti123".to_string(),
895            username: None,
896            custom_claims: HashMap::new(),
897        };
898
899        claims
900            .custom_claims
901            .insert("claim1".to_string(), Value::String("value1".to_string()));
902        claims
903            .custom_claims
904            .insert("claim2".to_string(), Value::String("value2".to_string()));
905
906        assert!(or_validator.validate(&claims).is_ok());
907
908        // Test with only one claim present
909        let mut claims = CognitoJwtClaims {
910            sub: "user123".to_string(),
911            iss: "https://example.com".to_string(),
912            client_id: "client123".to_string(),
913            origin_jti: None,
914            event_id: None,
915            token_use: "id".to_string(),
916            scope: None,
917            auth_time: 0,
918            exp: 0,
919            iat: 0,
920            jti: "jti123".to_string(),
921            username: None,
922            custom_claims: HashMap::new(),
923        };
924
925        claims
926            .custom_claims
927            .insert("claim2".to_string(), Value::String("value2".to_string()));
928
929        assert!(or_validator.validate(&claims).is_ok());
930
931        // Test with no claims present
932        let claims = CognitoJwtClaims {
933            sub: "user123".to_string(),
934            iss: "https://example.com".to_string(),
935            client_id: "client123".to_string(),
936            origin_jti: None,
937            event_id: None,
938            token_use: "id".to_string(),
939            scope: None,
940            auth_time: 0,
941            exp: 0,
942            iat: 0,
943            jti: "jti123".to_string(),
944            username: None,
945            custom_claims: HashMap::new(),
946        };
947
948        assert!(or_validator.validate(&claims).is_err());
949    }
950}