1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
4use serde_json::Value;
5
6use crate::{Error, Result};
7
8#[derive(Debug, Clone, PartialEq, Default)]
10pub enum Solution {
11 #[default]
13 Missing,
14 Value(Value),
16}
17
18impl Solution {
19 #[must_use]
21 pub const fn is_missing(&self) -> bool {
22 matches!(self, Self::Missing)
23 }
24
25 #[must_use]
27 pub const fn as_value(&self) -> Option<&Value> {
28 match self {
29 Self::Missing => None,
30 Self::Value(value) => Some(value),
31 }
32 }
33
34 #[must_use]
36 pub fn into_value(self) -> Option<Value> {
37 match self {
38 Self::Missing => None,
39 Self::Value(value) => Some(value),
40 }
41 }
42
43 pub fn deserialize<T>(&self) -> Result<T>
48 where
49 T: DeserializeOwned,
50 {
51 let value = self.as_value().ok_or_else(|| {
52 Error::UnexpectedResponse("ready task result does not contain a solution".to_owned())
53 })?;
54 T::deserialize(value).map_err(|source| Error::SolutionDecode {
58 source,
59 raw: Box::new(value.clone()),
60 })
61 }
62}
63
64impl Serialize for Solution {
65 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
66 where
67 S: Serializer,
68 {
69 match self {
70 Self::Missing => serializer.serialize_none(),
71 Self::Value(value) => value.serialize(serializer),
72 }
73 }
74}
75
76impl<'de> Deserialize<'de> for Solution {
77 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 Value::deserialize(deserializer).map(Self::Value)
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92#[serde(remote = "Self")]
93pub struct RecaptchaSolution {
94 #[serde(rename = "gRecaptchaResponse")]
96 pub token: String,
97 #[serde(default)]
99 pub sec_ch_ua: String,
100 #[serde(default)]
102 pub user_agent: String,
103 #[serde(flatten, skip_serializing)]
105 pub extra: BTreeMap<String, Value>,
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110#[serde(remote = "Self")]
111pub struct FunCaptchaSolution {
112 pub token: String,
114 #[serde(flatten, skip_serializing)]
116 pub extra: BTreeMap<String, Value>,
117}
118
119#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
125#[non_exhaustive]
126#[serde(remote = "Self")]
127pub struct FunCaptchaClassificationSolution {
128 #[serde(flatten, skip_serializing)]
130 pub extra: BTreeMap<String, Value>,
131}
132
133#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
139#[non_exhaustive]
140#[serde(remote = "Self")]
141pub struct HcaptchaClassificationSolution {
142 #[serde(flatten, skip_serializing)]
144 pub extra: BTreeMap<String, Value>,
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149#[serde(remote = "Self")]
150pub struct CloudflareTurnstileSolution {
151 pub token: String,
153 #[serde(default)]
155 pub header: BTreeMap<String, String>,
156 #[serde(flatten, skip_serializing)]
158 pub extra: BTreeMap<String, Value>,
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167#[serde(remote = "Self", rename_all = "camelCase")]
168pub struct Cloudflare5sSolution {
169 #[serde(default)]
171 pub header: BTreeMap<String, String>,
172 #[serde(default)]
174 pub cookies: BTreeMap<String, String>,
175 #[serde(default)]
177 pub tls_version: String,
178 #[serde(default)]
180 pub body: String,
181 #[serde(default)]
184 pub s_token: String,
185 #[serde(flatten, skip_serializing)]
187 pub extra: BTreeMap<String, Value>,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192#[serde(remote = "Self")]
193pub struct HcaptchaSolution {
194 #[serde(rename = "generated_pass_UUID")]
196 pub generated_pass_uuid: String,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub ua: Option<String>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub lang: Option<String>,
203 #[serde(flatten, skip_serializing)]
205 pub extra: BTreeMap<String, Value>,
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215#[serde(remote = "Self")]
216pub struct PerimeterXSolution {
217 #[serde(rename = "_px3")]
219 pub px3: String,
220 #[serde(rename = "_pxvid", default)]
222 pub pxvid: String,
223 #[serde(rename = "_pxde", default)]
225 pub pxde: String,
226 #[serde(flatten, skip_serializing)]
228 pub extra: BTreeMap<String, Value>,
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233#[serde(remote = "Self")]
234pub struct AkamaiWebSolution {
235 pub payload: String,
237 #[serde(default)]
242 pub encodedata: String,
243 #[serde(flatten, skip_serializing)]
245 pub extra: BTreeMap<String, Value>,
246}
247
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250#[serde(remote = "Self")]
251pub struct AkamaiSbsdSolution {
252 pub payload: String,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub bm_lso_time: Option<String>,
259 #[serde(flatten, skip_serializing)]
261 pub extra: BTreeMap<String, Value>,
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266#[serde(remote = "Self")]
267pub struct TlsForwardSolution {
268 #[serde(default)]
270 pub status: u16,
271 #[serde(default)]
273 pub code: u16,
274 #[serde(default)]
276 pub headers: BTreeMap<String, Value>,
277 #[serde(default)]
279 pub cookies: HashMap<String, Value>,
280 #[serde(default)]
282 pub body: String,
283 #[serde(flatten, skip_serializing)]
285 pub extra: BTreeMap<String, Value>,
286}
287
288#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
293#[serde(remote = "Self", default, rename_all = "camelCase")]
294pub struct ReClassificationSolution {
295 pub r#type: String,
297 pub has_object: bool,
299 pub objects: Vec<usize>,
301 #[serde(flatten, skip_serializing)]
303 pub extra: BTreeMap<String, Value>,
304}
305
306impl ReClassificationSolution {
307 #[must_use]
309 pub fn is_multi(&self) -> bool {
310 self.r#type == "multi"
311 }
312
313 #[must_use]
315 pub fn is_single(&self) -> bool {
316 self.r#type == "single"
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
328#[serde(remote = "Self")]
329pub struct DataDomeSolution {
330 #[serde(default)]
332 pub kind: Option<String>,
333 #[serde(default)]
335 pub url: Option<String>,
336 #[serde(default)]
338 pub body: Option<String>,
339 #[serde(flatten, skip_serializing)]
341 pub extra: BTreeMap<String, Value>,
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346#[serde(remote = "Self")]
347pub struct IncapsulaSolution {
348 #[serde(default)]
350 pub status: Option<u16>,
351 pub data: String,
356 #[serde(flatten, skip_serializing)]
358 pub extra: BTreeMap<String, Value>,
359}
360
361macro_rules! impl_solution_serde {
373 ($($ty:ident),+ $(,)?) => {$(
374 impl Serialize for $ty {
375 fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
376 where
377 S: Serializer,
378 {
379 crate::wire::merge_extra(|s| <$ty>::serialize(self, s), &self.extra, serializer)
380 }
381 }
382
383 impl<'de> Deserialize<'de> for $ty {
384 fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
385 where
386 D: Deserializer<'de>,
387 {
388 <$ty>::deserialize(deserializer)
389 }
390 }
391 )+};
392}
393
394impl_solution_serde!(
395 AkamaiSbsdSolution,
396 AkamaiWebSolution,
397 Cloudflare5sSolution,
398 CloudflareTurnstileSolution,
399 DataDomeSolution,
400 FunCaptchaClassificationSolution,
401 FunCaptchaSolution,
402 HcaptchaClassificationSolution,
403 HcaptchaSolution,
404 IncapsulaSolution,
405 PerimeterXSolution,
406 ReClassificationSolution,
407 RecaptchaSolution,
408 TlsForwardSolution,
409);