1use super::captcha_error::CaptchaError;
2
3#[derive(Debug)]
5pub struct CaptchaResult {
6 pub text: String,
8 pub image_data: Vec<u8>,
10 pub width: u32,
12 pub height: u32,
14 pub length: usize,
16 pub complexity: u32,
18}
19
20#[derive(Debug, Default, Clone)]
22pub(crate) struct CaptchaBuilder {
23 text: Option<String>, length: usize, complexity: u32, width: u32, height: u32, }
29
30impl CaptchaBuilder {
31 pub fn builder() -> Self {
33 Self {
34 length: 6, complexity: 1, width: 150, height: 50, text: None, }
40 }
41
42 pub fn length(mut self, length: usize) -> Self {
44 self.length = length;
45 self
46 }
47
48 pub fn complexity(mut self, complexity: u32) -> Self {
50 self.complexity = complexity;
51 self
52 }
53
54 pub fn size(mut self, width: u32, height: u32) -> Self {
56 self.width = width;
57 self.height = height;
58 self
59 }
60
61 pub fn text(mut self, text: impl Into<String>) -> Self {
63 self.text = Some(text.into());
64 self
65 }
66
67 pub fn build(self) -> CaptchaGen {
69 CaptchaGen { config: self }
70 }
71}
72
73pub struct CaptchaGen {
75 config: CaptchaBuilder, }
77
78impl CaptchaGen {
79 fn _gen(&self, base64: bool) -> Result<(String, Vec<u8>), CaptchaError> {
81 if self.config.width <= 0 {
83 return Err(CaptchaError::WidthNotApplicable);
84 }
85 if self.config.height <= 0 {
87 return Err(CaptchaError::HeightNotApplicable);
88 }
89
90 let mut captcha = captcha_rs::CaptchaBuilder::new()
92 .length(self.config.length)
93 .width(self.config.width)
94 .height(self.config.height)
95 .dark_mode(false)
96 .complexity(self.config.complexity)
97 .compression(40);
98
99 if let Some(text) = self.config.text.as_deref() {
101 captcha = captcha.text(text.into());
102 }
103
104 let captcha = captcha.build();
106
107 let text = if let Some(text) = self.config.text.as_ref() {
109 text.into()
110 } else {
111 captcha.text.clone()
112 };
113
114 if base64 {
115 Ok((text, captcha.to_base64().into_bytes()))
116 } else {
117 let image_data = captcha.image.into_bytes();
119 Ok((text, image_data))
120 }
121 }
122
123 pub fn gen(self) -> Result<CaptchaResult, CaptchaError> {
124 let result = self._gen(false)?;
125 Ok(CaptchaResult {
126 text: result.0,
127 image_data: result.1,
128 width: self.config.width,
129 height: self.config.height,
130 length: self.config.length,
131 complexity: self.config.complexity,
132 })
133 }
134
135 pub fn gen_to_base64(self) -> Result<CaptchaResult, CaptchaError> {
136 let result = self._gen(true)?;
137
138 Ok(CaptchaResult {
139 text: result.0,
140 image_data: result.1,
141 width: self.config.width,
142 height: self.config.height,
143 length: self.config.length,
144 complexity: self.config.complexity,
145 })
146 }
147
148 pub fn builder() -> CaptchaBuilder {
150 CaptchaBuilder::builder()
151 }
152}
153
154#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
161 fn test_default_values() {
162 let builder = CaptchaBuilder::builder();
163 assert_eq!(builder.length, 6, "Default length should be 6");
164 assert_eq!(builder.complexity, 1, "Default complexity should be 1");
165 assert_eq!(builder.width, 150, "Default width should be 150");
166 assert_eq!(builder.height, 50, "Default height should be 50");
167 assert!(builder.text.is_none(), "Default text should be None");
168 }
169
170 #[test]
172 fn test_length_config() {
173 let builder = CaptchaBuilder::builder().length(8);
174 assert_eq!(builder.length, 8, "Length should be updated to 8");
175 }
176
177 #[test]
179 fn test_complexity_config() {
180 let builder = CaptchaBuilder::builder().complexity(5);
181 assert_eq!(builder.complexity, 5, "Complexity should be updated to 5");
182 }
183
184 #[test]
186 fn test_size_config() {
187 let builder = CaptchaBuilder::builder().size(200, 100);
188 assert_eq!(builder.width, 200, "Width should be updated to 200");
189 assert_eq!(builder.height, 100, "Height should be updated to 100");
190 }
191
192 #[test]
194 fn test_text_config() {
195 let builder = CaptchaBuilder::builder().text("CUSTOMTEXT");
196 assert_eq!(
197 builder.text,
198 Some("CUSTOMTEXT".to_string()),
199 "Text should be set to CUSTOMTEXT"
200 );
201 }
202
203 #[test]
205 fn test_chained_config() {
206 let builder = CaptchaBuilder::builder()
207 .length(8)
208 .complexity(3)
209 .size(300, 150)
210 .text("CHAINTEST");
211
212 assert_eq!(builder.length, 8, "Length should be updated to 8");
213 assert_eq!(builder.complexity, 3, "Complexity should be updated to 3");
214 assert_eq!(builder.width, 300, "Width should be updated to 300");
215 assert_eq!(builder.height, 150, "Height should be updated to 150");
216 assert_eq!(
217 builder.text,
218 Some("CHAINTEST".to_string()),
219 "Text should be set to CHAINTEST"
220 );
221 }
222
223 #[test]
225 fn test_captcha_gen_with_default_config() {
226 let captcha_gen = CaptchaBuilder::builder().build();
227 let result = captcha_gen.gen();
228 assert!(result.is_ok(), "Captcha generation should succeed");
229
230 let captcha_result = result.unwrap();
231 assert_eq!(
232 captcha_result.text.len(),
233 6,
234 "Generated text length should match default length of 6"
235 );
236 assert_eq!(captcha_result.width, 150);
237 assert_eq!(captcha_result.height, 50);
238 assert_eq!(captcha_result.length, 6);
239 assert_eq!(captcha_result.complexity, 1);
240 assert!(!captcha_result.image_data.is_empty());
241 }
242
243 #[test]
245 fn test_captcha_gen_with_custom_config() {
246 let captcha_gen = CaptchaBuilder::builder()
247 .length(10)
248 .complexity(5)
249 .size(400, 200)
250 .text("CUSTOM")
251 .build();
252
253 let result = captcha_gen.gen();
254 assert!(result.is_ok(), "Captcha generation should succeed");
255
256 let captcha_result = result.unwrap();
257 assert_eq!(captcha_result.text, "CUSTOM");
258 assert_eq!(captcha_result.width, 400);
259 assert_eq!(captcha_result.height, 200);
260 assert_eq!(captcha_result.length, 10);
261 assert_eq!(captcha_result.complexity, 5);
262 assert!(!captcha_result.image_data.is_empty());
263 }
264
265 #[test]
267 fn test_captcha_gen_without_custom_text() {
268 let captcha_gen = CaptchaBuilder::builder().length(8).build();
269
270 let result = captcha_gen.gen();
271 assert!(result.is_ok(), "Captcha generation should succeed");
272
273 let captcha_result = result.unwrap();
274 assert_eq!(
275 captcha_result.text.len(),
276 8,
277 "Generated text length should match configured length of 8"
278 );
279 assert_eq!(captcha_result.width, 150);
280 assert_eq!(captcha_result.height, 50);
281 assert_eq!(captcha_result.length, 8);
282 assert_eq!(captcha_result.complexity, 1);
283 assert!(!captcha_result.image_data.is_empty());
284 }
285
286 #[test]
288 fn gen_code() -> Result<(), Box<dyn std::error::Error>> {
289 let result = CaptchaGen::builder()
291 .size(100, 40)
292 .text("ABCD")
293 .build()
294 .gen();
295
296 result.map(|captcha_result| {
298 println!("code: {}", captcha_result.text); println!("width: {}", captcha_result.width); println!("height: {}", captcha_result.height); println!("length: {}", captcha_result.length); println!("complexity: {}", captcha_result.complexity); std::fs::write("test.png", &captcha_result.image_data)?;
306 Ok(())
307 })?
308 }
309
310 #[test]
311 fn test_gen() {
312 let captcha_gen = CaptchaBuilder::builder()
313 .length(6)
314 .complexity(3)
315 .size(200, 100)
316 .text("TEST123")
317 .build();
318
319 let result = captcha_gen.gen();
320 assert!(result.is_ok(), "Captcha generation should succeed");
321
322 let captcha_result = result.unwrap();
323 assert_eq!(captcha_result.text, "TEST123");
324 assert_eq!(captcha_result.width, 200);
325 assert_eq!(captcha_result.height, 100);
326 assert_eq!(captcha_result.length, 6);
327 assert_eq!(captcha_result.complexity, 3);
328 assert!(!captcha_result.image_data.is_empty());
329 }
330}