capmonstercloud_client/requests/
friendly.rs1use serde::Serialize;
2
3use super::{require_non_empty, CaptchaRequest};
4use crate::error::Result;
5use crate::proxy::ProxyInfo;
6use crate::timeouts::GetResultTimeouts;
7
8#[derive(Debug, Clone, Serialize)]
10pub struct FriendlyMetadata {
11 #[serde(rename = "apiGetLib")]
17 pub api_get_lib: String,
18}
19
20impl FriendlyMetadata {
21 pub fn new(api_get_lib: impl Into<String>) -> Self {
22 Self {
23 api_get_lib: api_get_lib.into(),
24 }
25 }
26}
27
28#[derive(Debug, Clone, Serialize)]
30pub struct FriendlyCustomTaskRequest {
31 #[serde(rename = "websiteURL")]
33 pub website_url: String,
34 #[serde(rename = "websiteKey")]
36 pub website_key: String,
37 pub metadata: FriendlyMetadata,
38 #[serde(flatten, skip_serializing_if = "Option::is_none")]
39 pub proxy: Option<ProxyInfo>,
40 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
42 pub user_agent: Option<String>,
43}
44
45impl FriendlyCustomTaskRequest {
46 pub fn new(
47 website_url: impl Into<String>,
48 website_key: impl Into<String>,
49 metadata: FriendlyMetadata,
50 ) -> Self {
51 Self {
52 website_url: website_url.into(),
53 website_key: website_key.into(),
54 metadata,
55 proxy: None,
56 user_agent: None,
57 }
58 }
59
60 pub fn with_proxy(mut self, proxy: ProxyInfo) -> Self {
61 self.proxy = Some(proxy);
62 self
63 }
64
65 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
66 self.user_agent = Some(value.into());
67 self
68 }
69}
70
71impl CaptchaRequest for FriendlyCustomTaskRequest {
72 const TASK_TYPE: &'static str = "CustomTask";
73 const CLASS: Option<&'static str> = Some("friendly");
74
75 fn timeouts(&self) -> GetResultTimeouts {
76 GetResultTimeouts::secs(1.0, 0.0, 3.0, 180.0)
77 }
78
79 fn validate(&self) -> Result<()> {
80 require_non_empty("websiteURL", &self.website_url)?;
81 require_non_empty("websiteKey", &self.website_key)?;
82 require_non_empty("metadata.apiGetLib", &self.metadata.api_get_lib)?;
83 Ok(())
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::proxy::ProxyType;
91 use serde_json::json;
92
93 #[test]
94 fn minimal_task() {
95 let task = FriendlyCustomTaskRequest::new(
96 "https://example.com",
97 "FC123",
98 FriendlyMetadata::new("https://x/site.min.js"),
99 )
100 .to_task()
101 .unwrap();
102 assert_eq!(
103 task,
104 json!({
105 "type": "CustomTask", "class": "friendly", "websiteURL": "https://example.com", "websiteKey": "FC123",
106 "metadata": {"apiGetLib": "https://x/site.min.js"}
107 })
108 );
109 }
110
111 #[test]
112 fn full_task() {
113 let task = FriendlyCustomTaskRequest::new(
114 "https://example.com",
115 "FC123",
116 FriendlyMetadata::new("L"),
117 )
118 .with_proxy(ProxyInfo::new(ProxyType::Socks4, "1.2.3.4", 1080).with_auth("u", "p"))
119 .with_user_agent("UA")
120 .to_task()
121 .unwrap();
122 assert_eq!(
123 task,
124 json!({
125 "type": "CustomTask", "class": "friendly", "websiteURL": "https://example.com", "websiteKey": "FC123",
126 "metadata": {"apiGetLib": "L"},
127 "proxyType": "socks4", "proxyAddress": "1.2.3.4", "proxyPort": 1080,
128 "proxyLogin": "u", "proxyPassword": "p", "userAgent": "UA"
129 })
130 );
131 }
132
133 #[test]
134 fn empty_required_strings_are_rejected() {
135 let err = FriendlyCustomTaskRequest::new("", "K", FriendlyMetadata::new("L"))
136 .to_task()
137 .unwrap_err();
138 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
139 let err =
140 FriendlyCustomTaskRequest::new("https://example.com", "", FriendlyMetadata::new("L"))
141 .to_task()
142 .unwrap_err();
143 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteKey")));
144 let err =
145 FriendlyCustomTaskRequest::new("https://example.com", "K", FriendlyMetadata::new(""))
146 .to_task()
147 .unwrap_err();
148 assert!(
149 matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.apiGetLib"))
150 );
151 }
152}