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 _m = crate::tests::config_mock::ConfigMockBuilder::new().build_and_setup();
80        let config = SignerPoolConfig {
81            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
82            signers: vec![SignerConfig {
83                name: "test_signer".to_string(),
84                weight: Some(10), // Weight specified for non-weighted strategy
85                config: SignerTypeConfig::Memory {
86                    config: MemorySignerConfig { private_key_env: "TEST_KEY".to_string() },
87                },
88            }],
89        };
90
91        std::env::set_var("TEST_KEY", "dummy");
92        let (warnings, errors) = SignerValidator::validate_with_result(&config);
93
94        assert!(errors.is_empty());
95        assert!(!warnings.is_empty());
96        assert!(warnings[0].contains("weight will be ignored"));
97        std::env::remove_var("TEST_KEY");
98    }
99
100    #[test]
101    fn test_validate_duplicate_names() {
102        let config = SignerPoolConfig {
103            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
104            signers: vec![
105                SignerConfig {
106                    name: "duplicate".to_string(),
107                    weight: None,
108                    config: SignerTypeConfig::Memory {
109                        config: MemorySignerConfig { private_key_env: "TEST_KEY_1".to_string() },
110                    },
111                },
112                SignerConfig {
113                    name: "duplicate".to_string(),
114                    weight: None,
115                    config: SignerTypeConfig::Memory {
116                        config: MemorySignerConfig { private_key_env: "TEST_KEY_2".to_string() },
117                    },
118                },
119            ],
120        };
121
122        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
123        assert!(!errors.is_empty());
124        assert!(errors.iter().any(|e| e.contains("Duplicate signer name")));
125    }
126
127    #[test]
128    fn test_validate_with_result_zero_weight() {
129        let config = SignerPoolConfig {
130            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::Weighted },
131            signers: vec![SignerConfig {
132                name: "test_signer".to_string(),
133                weight: Some(0),
134                config: SignerTypeConfig::Memory {
135                    config: MemorySignerConfig { private_key_env: "TEST_KEY".to_string() },
136                },
137            }],
138        };
139
140        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
141        assert!(!errors.is_empty());
142        assert!(errors.iter().any(|e| e.contains("weight of 0 in weighted strategy")));
143    }
144
145    #[test]
146    fn test_validate_with_result_empty_signers() {
147        let config = SignerPoolConfig {
148            signer_pool: SignerPoolSettings { strategy: SelectionStrategy::RoundRobin },
149            signers: vec![],
150        };
151
152        let (_warnings, errors) = SignerValidator::validate_with_result(&config);
153        assert!(!errors.is_empty());
154        assert!(errors.iter().any(|e| e.contains("At least one signer must be configured")));
155    }
156}