pipedash_core/domain/
validation.rs1use 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}