Skip to main content

kora_lib/validator/
signer_validator.rs

1use crate::{
2    error::KoraError,
3    signer::{SelectionStrategy, SignerPoolConfig},
4};
5
6pub struct SignerValidator {}
7
8impl SignerValidator {
9    /// Validate signer configuration with detailed results
10    pub fn validate_with_result(config: &SignerPoolConfig) -> (Vec<String>, Vec<String>) {
11        let mut errors = Vec::new();
12        let mut warnings = Vec::new();
13
14        // Check if signers list is empty
15        Self::try_result(config.validate_signer_not_empty(), &mut errors);
16
17        // Validate each signer configuration - delegate to existing method
18        for (index, signer) in config.signers.iter().enumerate() {
19            Self::try_result(signer.validate_individual_signer_config(index), &mut errors);
20        }
21
22        // Check for duplicate names - delegate to existing method
23        Self::try_result(config.validate_signer_names(), &mut errors);
24
25        // Validate strategy weights - delegate to existing method
26        Self::try_result(config.validate_strategy_weights(), &mut errors);
27
28        // Generate strategy-specific warnings
29        Self::validate_strategy_warnings(config, &mut warnings);
30
31        (warnings, errors)
32    }
33
34    /// Helper method to convert Result to error string and add to errors vec
35    fn try_result(result: Result<(), KoraError>, errors: &mut Vec<String>) {
36        if let Err(KoraError::ValidationError(msg)) = result {
37            errors.push(msg);
38        }
39    }
40
41    /// Generate strategy-specific warnings (warnings don't fail fast)
42    fn validate_strategy_warnings(config: &SignerPoolConfig, warnings: &mut Vec<String>) {
43        match config.signer_pool.strategy {
44            SelectionStrategy::Weighted => {
45                for signer in &config.signers {
46                    if signer.weight.is_none() {
47                        warnings.push(format!(
48                            "Signer '{}' has no weight specified for weighted strategy",
49                            signer.name
50                        ));
51                    }
52                }
53            }
54            _ => {
55                // For non-weighted strategies, warn if weights are specified
56                for signer in &config.signers {
57                    if signer.weight.is_some() {
58                        warnings.push(format!(
59                            "Signer '{}' has weight specified but using {} strategy - weight will be ignored",
60                            signer.name,
61                            config.signer_pool.strategy
62                        ));
63                    }
64                }
65            }
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::signer::config::{
74        MemorySignerConfig, SignerConfig, SignerPoolSettings, SignerTypeConfig,
75    };
76
77    #[test]
78    fn test_validate_with_result_warnings() {
79        let config = SignerPoolConfig {
80            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
81            signers: vec![SignerConfig {
82                name: "test_signer".to_string(),
83                weight: Some(10), // Weight specified for non-weighted strategy
84                config: SignerTypeConfig::Memory {
85                    config: MemorySignerConfig { private_key_env: "TEST_KEY".to_string() },
86                },
87            }],
88        };
89
90        let (warnings, errors) = SignerValidator::validate_with_result(&config);
91        assert!(errors.is_empty());
92        assert!(!warnings.is_empty());
93        assert!(warnings[0].contains("weight will be ignored"));
94    }
95
96    #[test]
97    fn test_validate_duplicate_names() {
98        let config = SignerPoolConfig {
99            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
100            signers: vec![
101                SignerConfig {
102                    name: "duplicate".to_string(),
103                    weight: None,
104                    config: SignerTypeConfig::Memory {
105                        config: MemorySignerConfig { private_key_env: "TEST_KEY_1".to_string() },
106                    },
107                },
108                SignerConfig {
109                    name: "duplicate".to_string(),
110                    weight: None,
111                    config: SignerTypeConfig::Memory {
112                        config: MemorySignerConfig { private_key_env: "TEST_KEY_2".to_string() },
113                    },
114                },
115            ],
116        };
117
118        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
119        assert!(!errors.is_empty());
120        assert!(errors.iter().any(|e| e.contains("Duplicate signer name")));
121    }
122
123    #[test]
124    fn test_validate_with_result_zero_weight() {
125        let config = SignerPoolConfig {
126            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::Weighted },
127            signers: vec![SignerConfig {
128                name: "test_signer".to_string(),
129                weight: Some(0),
130                config: SignerTypeConfig::Memory {
131                    config: MemorySignerConfig { private_key_env: "TEST_KEY".to_string() },
132                },
133            }],
134        };
135
136        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
137        assert!(!errors.is_empty());
138        assert!(errors.iter().any(|e| e.contains("weight of 0 in weighted strategy")));
139    }
140
141    #[test]
142    fn test_validate_with_result_empty_signers() {
143        let config = SignerPoolConfig {
144            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
145            signers: vec![],
146        };
147
148        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
149        assert!(!errors.is_empty());
150        assert!(errors.iter().any(|e| e.contains("At least one signer must be configured")));
151    }
152}