Skip to main content

pipedash_core/domain/
validation.rs

1use std::collections::HashMap;
2
3use super::{
4    DomainError,
5    DomainResult,
6};
7
8const MAX_KEY_LENGTH: usize = 256;
9
10const MAX_VALUE_LENGTH: usize = 4096;
11
12const MAX_NAME_LENGTH: usize = 128;
13
14const MAX_PIPELINE_ID_LENGTH: usize = 512;
15
16pub fn validate_config(config: &HashMap<String, String>) -> DomainResult<()> {
17    for (key, value) in config {
18        validate_config_key(key)?;
19        validate_config_value(key, value)?;
20    }
21    Ok(())
22}
23
24fn validate_config_key(key: &str) -> DomainResult<()> {
25    if key.is_empty() {
26        return Err(DomainError::InvalidConfig(
27            "Config key cannot be empty".to_string(),
28        ));
29    }
30
31    if key.len() > MAX_KEY_LENGTH {
32        return Err(DomainError::InvalidConfig(format!(
33            "Config key '{}...' exceeds maximum length of {} characters",
34            &key[..32.min(key.len())],
35            MAX_KEY_LENGTH
36        )));
37    }
38
39    if !key
40        .chars()
41        .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
42    {
43        return Err(DomainError::InvalidConfig(format!(
44            "Config key '{}' contains invalid characters (only alphanumeric, underscore, hyphen allowed)",
45            key
46        )));
47    }
48
49    Ok(())
50}
51
52fn validate_config_value(key: &str, value: &str) -> DomainResult<()> {
53    if value.len() > MAX_VALUE_LENGTH {
54        return Err(DomainError::InvalidConfig(format!(
55            "Value for key '{}' exceeds maximum length of {} characters",
56            key, MAX_VALUE_LENGTH
57        )));
58    }
59    Ok(())
60}
61
62pub fn validate_provider_name(name: &str) -> DomainResult<()> {
63    if name.is_empty() {
64        return Err(DomainError::InvalidConfig(
65            "Provider name cannot be empty".to_string(),
66        ));
67    }
68
69    if name.len() > MAX_NAME_LENGTH {
70        return Err(DomainError::InvalidConfig(format!(
71            "Provider name exceeds maximum length of {} characters",
72            MAX_NAME_LENGTH
73        )));
74    }
75
76    if !name
77        .chars()
78        .all(|c| c.is_alphanumeric() || c == ' ' || c == '_' || c == '-')
79    {
80        return Err(DomainError::InvalidConfig(
81            "Provider name contains invalid characters".to_string(),
82        ));
83    }
84
85    Ok(())
86}
87
88pub fn validate_provider_type(provider_type: &str) -> DomainResult<()> {
89    const VALID_TYPES: &[&str] = &["github", "gitlab", "bitbucket", "circleci", "azure"];
90
91    if !VALID_TYPES.contains(&provider_type.to_lowercase().as_str()) {
92        return Err(DomainError::InvalidProviderType(format!(
93            "Unknown provider type '{}'. Valid types: {:?}",
94            provider_type, VALID_TYPES
95        )));
96    }
97
98    Ok(())
99}
100
101pub fn validate_pipeline_id(pipeline_id: &str) -> DomainResult<()> {
102    if pipeline_id.is_empty() {
103        return Err(DomainError::InvalidConfig(
104            "Pipeline ID cannot be empty".to_string(),
105        ));
106    }
107
108    if pipeline_id.len() > MAX_PIPELINE_ID_LENGTH {
109        return Err(DomainError::InvalidConfig(format!(
110            "Pipeline ID exceeds maximum length of {} characters",
111            MAX_PIPELINE_ID_LENGTH
112        )));
113    }
114
115    Ok(())
116}
117
118pub fn validate_trigger_params(
119    workflow_id: &str, inputs: &Option<HashMap<String, String>>,
120) -> DomainResult<()> {
121    validate_pipeline_id(workflow_id)?;
122
123    if let Some(inputs) = inputs {
124        validate_config(inputs)?;
125    }
126
127    Ok(())
128}
129
130pub fn validate_pagination(page: usize, page_size: usize) -> DomainResult<()> {
131    if page == 0 {
132        return Err(DomainError::InvalidConfig(
133            "Page number must be at least 1".to_string(),
134        ));
135    }
136
137    if page_size == 0 {
138        return Err(DomainError::InvalidConfig(
139            "Page size must be at least 1".to_string(),
140        ));
141    }
142
143    if page_size > 1000 {
144        return Err(DomainError::InvalidConfig(
145            "Page size cannot exceed 1000".to_string(),
146        ));
147    }
148
149    Ok(())
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_validate_config_valid() {
158        let mut config = HashMap::new();
159        config.insert("owner".to_string(), "my-org".to_string());
160        config.insert("repo".to_string(), "my-repo".to_string());
161        config.insert("base_url".to_string(), "https://github.com".to_string());
162
163        assert!(validate_config(&config).is_ok());
164    }
165
166    #[test]
167    fn test_validate_config_empty_key() {
168        let mut config = HashMap::new();
169        config.insert("".to_string(), "value".to_string());
170
171        assert!(validate_config(&config).is_err());
172    }
173
174    #[test]
175    fn test_validate_config_key_too_long() {
176        let mut config = HashMap::new();
177        config.insert("a".repeat(300), "value".to_string());
178
179        assert!(validate_config(&config).is_err());
180    }
181
182    #[test]
183    fn test_validate_config_value_too_long() {
184        let mut config = HashMap::new();
185        config.insert("key".to_string(), "a".repeat(5000));
186
187        assert!(validate_config(&config).is_err());
188    }
189
190    #[test]
191    fn test_validate_config_invalid_key_chars() {
192        let mut config = HashMap::new();
193        config.insert("key!@#".to_string(), "value".to_string());
194
195        assert!(validate_config(&config).is_err());
196    }
197
198    #[test]
199    fn test_validate_provider_name_valid() {
200        assert!(validate_provider_name("My GitHub Provider").is_ok());
201        assert!(validate_provider_name("github-actions").is_ok());
202        assert!(validate_provider_name("gitlab_ci").is_ok());
203    }
204
205    #[test]
206    fn test_validate_provider_name_empty() {
207        assert!(validate_provider_name("").is_err());
208    }
209
210    #[test]
211    fn test_validate_provider_type_valid() {
212        assert!(validate_provider_type("github").is_ok());
213        assert!(validate_provider_type("GitHub").is_ok());
214        assert!(validate_provider_type("gitlab").is_ok());
215        assert!(validate_provider_type("bitbucket").is_ok());
216    }
217
218    #[test]
219    fn test_validate_provider_type_invalid() {
220        assert!(validate_provider_type("unknown").is_err());
221        assert!(validate_provider_type("jenkins").is_err());
222    }
223
224    #[test]
225    fn test_validate_pagination_valid() {
226        assert!(validate_pagination(1, 20).is_ok());
227        assert!(validate_pagination(100, 50).is_ok());
228    }
229
230    #[test]
231    fn test_validate_pagination_invalid() {
232        assert!(validate_pagination(0, 20).is_err());
233        assert!(validate_pagination(1, 0).is_err());
234        assert!(validate_pagination(1, 2000).is_err());
235    }
236}