capmonstercloud_client/requests/
geetest.rs1use serde::ser::{Serialize, SerializeMap, Serializer};
2use serde_json::Value;
3
4use super::{require_non_empty, CaptchaRequest};
5use crate::proxy::ProxyInfo;
6use crate::timeouts::GetResultTimeouts;
7
8#[derive(Debug, Clone, PartialEq)]
13pub enum GeetestVersion {
14 V3 { challenge: String },
17 V4 { init_parameters: Option<Value> },
20}
21
22impl Serialize for GeetestVersion {
23 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
24 match self {
25 GeetestVersion::V3 { challenge } => {
26 let mut map = serializer.serialize_map(Some(2))?;
27 map.serialize_entry("version", &3)?;
28 map.serialize_entry("challenge", challenge)?;
29 map.end()
30 }
31 GeetestVersion::V4 { init_parameters } => {
32 let mut map = serializer.serialize_map(None)?;
33 map.serialize_entry("version", &4)?;
34 if let Some(params) = init_parameters {
35 map.serialize_entry("initParameters", params)?;
36 }
37 map.end()
38 }
39 }
40 }
41}
42
43#[derive(Debug, Clone, serde::Serialize)]
45pub struct GeetestRequest {
46 #[serde(rename = "websiteURL")]
49 pub website_url: String,
50 pub gt: String,
52 #[serde(flatten)]
54 pub version: GeetestVersion,
55 #[serde(flatten, skip_serializing_if = "Option::is_none")]
56 pub proxy: Option<ProxyInfo>,
57 #[serde(
60 rename = "geetestApiServerSubdomain",
61 skip_serializing_if = "Option::is_none"
62 )]
63 pub geetest_api_server_subdomain: Option<String>,
64 #[serde(rename = "geetestGetLib", skip_serializing_if = "Option::is_none")]
67 pub geetest_get_lib: Option<String>,
68 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
70 pub user_agent: Option<String>,
71}
72
73impl GeetestRequest {
74 fn with_version(
75 website_url: impl Into<String>,
76 gt: impl Into<String>,
77 version: GeetestVersion,
78 ) -> Self {
79 Self {
80 website_url: website_url.into(),
81 gt: gt.into(),
82 version,
83 proxy: None,
84 geetest_api_server_subdomain: None,
85 geetest_get_lib: None,
86 user_agent: None,
87 }
88 }
89
90 pub fn new_v3(
92 website_url: impl Into<String>,
93 gt: impl Into<String>,
94 challenge: impl Into<String>,
95 ) -> Self {
96 Self::with_version(
97 website_url,
98 gt,
99 GeetestVersion::V3 {
100 challenge: challenge.into(),
101 },
102 )
103 }
104
105 pub fn new_v4(website_url: impl Into<String>, gt: impl Into<String>) -> Self {
107 Self::with_version(
108 website_url,
109 gt,
110 GeetestVersion::V4 {
111 init_parameters: None,
112 },
113 )
114 }
115
116 pub fn new_v4_with_init_parameters(
118 website_url: impl Into<String>,
119 gt: impl Into<String>,
120 init_parameters: Value,
121 ) -> Self {
122 Self::with_version(
123 website_url,
124 gt,
125 GeetestVersion::V4 {
126 init_parameters: Some(init_parameters),
127 },
128 )
129 }
130
131 pub fn with_proxy(mut self, proxy: ProxyInfo) -> Self {
132 self.proxy = Some(proxy);
133 self
134 }
135
136 pub fn with_geetest_api_server_subdomain(mut self, value: impl Into<String>) -> Self {
137 self.geetest_api_server_subdomain = Some(value.into());
138 self
139 }
140
141 pub fn with_geetest_get_lib(mut self, value: impl Into<String>) -> Self {
142 self.geetest_get_lib = Some(value.into());
143 self
144 }
145
146 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
147 self.user_agent = Some(value.into());
148 self
149 }
150}
151
152impl CaptchaRequest for GeetestRequest {
153 const TASK_TYPE: &'static str = "GeeTestTask";
154
155 fn timeouts(&self) -> GetResultTimeouts {
156 GetResultTimeouts::secs(1.0, 0.0, 1.0, 80.0)
157 }
158
159 fn validate(&self) -> crate::error::Result<()> {
160 require_non_empty("websiteURL", &self.website_url)?;
161 require_non_empty("gt", &self.gt)?;
162 if let GeetestVersion::V3 { challenge } = &self.version {
163 require_non_empty("challenge", challenge)?;
164 }
165 Ok(())
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::proxy::ProxyType;
173 use serde_json::json;
174
175 #[test]
176 fn v3_minimal() {
177 let task = GeetestRequest::new_v3("https://example.com", "GT", "CH")
178 .to_task()
179 .unwrap();
180 assert_eq!(
181 task,
182 json!({"type": "GeeTestTask", "websiteURL": "https://example.com", "gt": "GT",
183 "version": 3, "challenge": "CH"})
184 );
185 }
186
187 #[test]
188 fn v3_full() {
189 let task = GeetestRequest::new_v3("https://example.com", "GT", "CH")
190 .with_proxy(ProxyInfo::new(ProxyType::Http, "1.2.3.4", 80).with_auth("u", "p"))
191 .with_geetest_api_server_subdomain("api-na.geetest.com")
192 .with_geetest_get_lib("{\"static_servers\":[\"x\"]}")
193 .with_user_agent("UA")
194 .to_task()
195 .unwrap();
196 assert_eq!(
197 task,
198 json!({"type": "GeeTestTask", "websiteURL": "https://example.com", "gt": "GT",
199 "version": 3, "challenge": "CH",
200 "geetestApiServerSubdomain": "api-na.geetest.com",
201 "geetestGetLib": "{\"static_servers\":[\"x\"]}", "userAgent": "UA",
202 "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 80,
203 "proxyLogin": "u", "proxyPassword": "p"})
204 );
205 }
206
207 #[test]
208 fn v4_minimal_has_no_challenge() {
209 let task = GeetestRequest::new_v4("https://example.com", "CAPTCHA_ID")
210 .to_task()
211 .unwrap();
212 assert_eq!(
213 task,
214 json!({"type": "GeeTestTask", "websiteURL": "https://example.com",
215 "gt": "CAPTCHA_ID", "version": 4})
216 );
217 }
218
219 #[test]
220 fn v4_full() {
221 let task = GeetestRequest::new_v4_with_init_parameters(
222 "https://example.com",
223 "CAPTCHA_ID",
224 json!({"riskType": "slide"}),
225 )
226 .with_user_agent("UA")
227 .to_task()
228 .unwrap();
229 assert_eq!(
230 task,
231 json!({"type": "GeeTestTask", "websiteURL": "https://example.com",
232 "gt": "CAPTCHA_ID", "version": 4,
233 "initParameters": {"riskType": "slide"}, "userAgent": "UA"})
234 );
235 }
236
237 #[test]
238 fn empty_required_strings_are_rejected() {
239 let err = GeetestRequest::new_v3("", "GT", "CH")
240 .to_task()
241 .unwrap_err();
242 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
243 let err = GeetestRequest::new_v3("https://example.com", "", "CH")
244 .to_task()
245 .unwrap_err();
246 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("gt")));
247 let err = GeetestRequest::new_v3("https://example.com", "GT", "")
248 .to_task()
249 .unwrap_err();
250 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("challenge")));
251 let err = GeetestRequest::new_v4("https://example.com", "")
252 .to_task()
253 .unwrap_err();
254 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("gt")));
255 }
256}