1use std::num::NonZeroU16;
4
5use crate::types::{Countries, LastChecked, Level, Protocol, TimeToConnect};
6
7use serde::Serialize;
8use serde_repr::Serialize_repr;
9
10#[derive(Clone, Debug, Default, PartialEq)]
16pub struct OptsBuilder {
17 api_key: Option<String>,
18 level: Option<Level>,
19 protocol: Option<Protocol>,
20 countries: Option<Countries>,
21 last_checked: Option<LastChecked>,
22 port: Option<NonZeroU16>,
23 time_to_connect: Option<TimeToConnect>,
24 cookies: Option<bool>,
25 connects_to_google: Option<bool>,
26 https: Option<bool>,
27 post: Option<bool>,
28 referer: Option<bool>,
29 forwards_user_agent: Option<bool>,
30}
31
32impl OptsBuilder {
33 pub fn api_key(mut self, api_key: String) -> Self {
35 self.api_key = Some(api_key);
36 self
37 }
38
39 pub fn level(mut self, level: Level) -> Self {
42 self.level = Some(level);
43 self
44 }
45
46 pub fn protocol(mut self, protocol: Protocol) -> Self {
48 self.protocol = Some(protocol);
49 self
50 }
51
52 pub fn countries(mut self, countries: Countries) -> Self {
54 self.countries = Some(countries);
55 self
56 }
57
58 pub fn last_checked(mut self, last_checked: LastChecked) -> Self {
61 self.last_checked = Some(last_checked);
62 self
63 }
64
65 pub fn port(mut self, port: NonZeroU16) -> Self {
67 self.port = Some(port);
68 self
69 }
70
71 pub fn time_to_connect(mut self, time_to_connect: TimeToConnect) -> Self {
75 self.time_to_connect = Some(time_to_connect);
76 self
77 }
78
79 pub fn cookies(mut self, cookies: bool) -> Self {
81 self.cookies = Some(cookies);
82 self
83 }
84
85 pub fn connects_to_google(mut self, connects_to_google: bool) -> Self {
87 self.connects_to_google = Some(connects_to_google);
88 self
89 }
90
91 pub fn https(mut self, https: bool) -> Self {
93 self.https = Some(https);
94 self
95 }
96
97 pub fn post(mut self, post: bool) -> Self {
99 self.post = Some(post);
100 self
101 }
102
103 pub fn referer(mut self, referer: bool) -> Self {
105 self.referer = Some(referer);
106 self
107 }
108
109 pub fn forwards_user_agent(mut self, forwards_user_agent: bool) -> Self {
111 self.forwards_user_agent = Some(forwards_user_agent);
112 self
113 }
114
115 pub fn build(self) -> Opts {
117 Opts::from(self)
118 }
119}
120
121#[derive(Serialize_repr, Clone, Copy, Debug, PartialEq, Eq)]
123#[repr(u8)]
124pub(crate) enum Limit {
125 Free = 5,
126 Premium = 20,
127}
128
129impl Default for Limit {
130 fn default() -> Self {
131 Self::Free
132 }
133}
134
135#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq)]
137#[serde(rename_all = "lowercase")]
138enum Format {
139 Json,
141}
142
143impl Default for Format {
144 fn default() -> Self {
145 Self::Json
146 }
147}
148
149#[derive(Serialize, Clone, Debug, Default, PartialEq)]
186pub struct Opts {
187 #[serde(rename = "api")]
188 api_key: Option<String>,
189 level: Option<Level>,
190 #[serde(rename = "type")]
191 protocol: Option<Protocol>,
192 #[serde(flatten, skip_serializing_if = "Countries::is_empty")]
194 countries: Countries,
195 #[serde(rename = "last_check")]
196 last_checked: Option<u64>,
197 port: Option<NonZeroU16>,
199 #[serde(rename = "speed")]
200 time_to_connect: Option<u64>,
201 cookies: Option<bool>,
202 #[serde(rename = "google")]
203 connects_to_google: Option<bool>,
204 https: Option<bool>,
205 post: Option<bool>,
206 referer: Option<bool>,
207 #[serde(rename = "user_agent")]
208 forwards_user_agent: Option<bool>,
209 pub(crate) limit: Limit,
210 format: Format,
211}
212
213impl Opts {
214 pub fn builder() -> OptsBuilder {
216 OptsBuilder::default()
217 }
218
219 pub(crate) fn is_premium(&self) -> bool {
221 self.api_key.is_some()
222 }
223}
224
225impl From<OptsBuilder> for Opts {
226 fn from(builder: OptsBuilder) -> Self {
227 Self {
228 limit: match builder.api_key {
229 Some(_) => Limit::Premium,
230 None => Limit::Free,
231 },
232 api_key: builder.api_key,
233 level: builder.level,
234 protocol: builder.protocol,
235 countries: builder.countries.unwrap_or_default(),
236 last_checked: builder
237 .last_checked
238 .map(|last_checked| last_checked.value().as_secs() / 60),
239 port: builder.port,
240 time_to_connect: builder
241 .time_to_connect
242 .map(|time_to_connect| time_to_connect.value().as_secs()),
243 cookies: builder.cookies,
244 connects_to_google: builder.connects_to_google,
245 https: builder.https,
246 post: builder.post,
247 referer: builder.referer,
248 forwards_user_agent: builder.forwards_user_agent,
249 format: Format::default(),
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 use std::{convert::TryFrom, time::Duration};
259
260 use iso_country::Country;
261
262 #[test]
263 fn url_serialization() -> Result<(), serde_urlencoded::ser::Error> {
264 let check_equivalent_params = |opts, expected: &[&str]| {
265 let url = serde_urlencoded::to_string(&opts)?;
267 let mut params: Vec<_> = url.split('&').map(String::from).collect();
268 params.sort();
269
270 let mut expected = expected.to_vec();
272 expected.sort_unstable();
273
274 assert_eq!(params, expected);
275
276 Ok(())
277 };
278
279 check_equivalent_params(Opts::default(), &["format=json", "limit=5"])?;
281 check_equivalent_params(
283 Opts::builder().api_key("<key>".to_string()).build(),
284 &["api=%3Ckey%3E", "format=json", "limit=20"],
285 )?;
286 check_equivalent_params(
289 Opts::builder().countries(Countries::default()).build(),
290 &["format=json", "limit=5"],
291 )?;
292 check_equivalent_params(
294 Opts::builder()
295 .api_key("<key>".to_string())
296 .level(Level::Elite)
297 .protocol(Protocol::Socks4)
298 .countries(Countries::block().countries(&[Country::CH, Country::ES]))
299 .last_checked(LastChecked::try_from(Duration::from_secs(60 * 10)).unwrap())
300 .time_to_connect(TimeToConnect::try_from(Duration::from_secs(10)).unwrap())
301 .port(NonZeroU16::new(8080).unwrap())
302 .cookies(true)
303 .connects_to_google(false)
304 .https(true)
305 .post(false)
306 .referer(true)
307 .forwards_user_agent(false)
308 .build(),
309 &[
310 "limit=20",
312 "format=json",
313 "api=%3Ckey%3E",
315 "level=elite",
317 "type=socks4",
318 "not_country=CH%2CES",
319 "last_check=10",
321 "speed=10",
322 "port=8080",
324 "cookies=true",
326 "google=false",
327 "https=true",
328 "post=false",
329 "referer=true",
330 "user_agent=false",
331 ],
332 )
333 }
334}