1use crate::errors::ParamError;
8
9use std::{convert::TryFrom, fmt, time::Duration};
10
11use iso_country::Country;
12use serde::{Deserialize, Serialize};
13use ureq::Response;
14
15#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
16struct BoundedVal<T>
17where
18 T: fmt::Debug + PartialEq + PartialOrd,
19{
20 #[serde(flatten)]
21 pub val: T,
22}
23
24impl<T> BoundedVal<T>
25where
26 T: fmt::Debug + PartialEq + PartialOrd,
27{
28 pub fn new(val: T, bounds: (T, T)) -> Result<Self, ParamError<T>> {
29 debug_assert!(bounds.0 <= bounds.1);
30
31 if val >= bounds.0 && val <= bounds.1 {
32 Ok(Self { val })
33 } else {
34 Err(ParamError::out_of_bounds(val, bounds))
35 }
36 }
37}
38
39macro_rules! bounded_val {
40 ($name:ident, $type:ty, $bounds:ident) => {
41 #[derive(Clone, Debug, PartialEq, Serialize)]
42 pub struct $name {
43 #[serde(flatten)]
44 inner: BoundedVal<$type>,
45 }
46
47 impl $name {
48 pub const BOUNDS: ($type, $type) = $bounds;
49
50 pub fn new(val: $type) -> Result<Self, ParamError<$type>> {
51 let inner = BoundedVal::new(val, Self::BOUNDS)?;
52 Ok(Self { inner })
53 }
54
55 pub fn value(&self) -> $type {
56 self.inner.val
57 }
58 }
59
60 impl TryFrom<$type> for $name {
61 type Error = ParamError<$type>;
62
63 fn try_from(val: $type) -> Result<Self, Self::Error> {
64 Self::new(val)
65 }
66 }
67 };
68}
69
70const LAST_CHECKED_BOUNDS: (Duration, Duration) =
72 (Duration::from_secs(60), Duration::from_secs(60 * 60));
73const TIME_TO_CONNECT_BOUNDS: (Duration, Duration) =
75 (Duration::from_secs(1), Duration::from_secs(60));
76bounded_val! {LastChecked, Duration, LAST_CHECKED_BOUNDS}
77bounded_val! {TimeToConnect, Duration, TIME_TO_CONNECT_BOUNDS}
78
79pub(crate) struct NaiveResponse {
80 pub(crate) status: u16,
81 pub(crate) text: String,
82}
83
84impl NaiveResponse {
85 pub fn new(status: u16, text: String) -> Self {
86 Self { status, text }
87 }
88
89 pub fn ok(&self) -> bool {
90 (200..300).contains(&self.status)
91 }
92}
93
94impl From<Response> for NaiveResponse {
95 fn from(resp: Response) -> Self {
96 let status = resp.status();
97 let text = resp.into_string().unwrap_or_default();
98
99 Self::new(status, text)
100 }
101}
102
103#[derive(Serialize, Clone, Debug, PartialEq)]
104pub enum Countries {
105 #[serde(rename = "country")]
106 AllowList(String),
107 #[serde(rename = "not_country")]
108 BlockList(String),
109}
110
111impl Countries {
112 pub fn allow() -> Self {
113 Self::AllowList(String::new())
114 }
115
116 pub fn block() -> Self {
117 Self::BlockList(String::new())
118 }
119
120 pub fn is_empty(&self) -> bool {
121 match self {
122 Self::AllowList(countries) => countries.is_empty(),
123 Self::BlockList(countries) => countries.is_empty(),
124 }
125 }
126
127 pub fn countries(mut self, countries: &[Country]) -> Self {
128 for country in countries {
129 self = self.country(*country);
130 }
131
132 self
133 }
134
135 pub fn country(self, country: Country) -> Self {
136 if let Country::Unspecified = country {
139 panic!("This library doesn't allow `Unspecified` country in the allow or blocklist");
141 }
142
143 let push_country = |list: String, new_tag: Country| {
144 let new_tag = new_tag.to_string();
145 if list.is_empty() {
146 new_tag
147 } else {
148 [list, new_tag].join(",")
149 }
150 };
151
152 match self {
153 Self::AllowList(list) => Self::AllowList(push_country(list, country)),
154 Self::BlockList(list) => Self::BlockList(push_country(list, country)),
155 }
156 }
157}
158
159impl Default for Countries {
160 fn default() -> Self {
161 Countries::block()
163 }
164}
165
166#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq)]
167#[serde(rename_all = "lowercase")]
168pub enum Level {
169 Anonymous,
170 Elite,
171}
172
173#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq)]
174#[serde(rename_all = "lowercase")]
175pub enum Protocol {
176 Http,
177 Socks4,
178 Socks5,
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 use std::time::Duration;
186
187 mod bounded_vals {
188 use super::*;
189
190 #[test]
191 fn bounds_checking() {
192 let zero_seconds = Duration::from_secs(0);
193 let just_over_minute = Duration::from_secs(61);
194 let just_over_hour = Duration::from_secs(60 * 60 + 1);
195
196 let bounds_err = TimeToConnect::try_from(zero_seconds).unwrap_err();
197 assert_eq!(
198 bounds_err,
199 ParamError::out_of_bounds(zero_seconds, TIME_TO_CONNECT_BOUNDS)
200 );
201
202 let bounds_err = TimeToConnect::try_from(just_over_minute).unwrap_err();
203 assert_eq!(
204 bounds_err,
205 ParamError::out_of_bounds(just_over_minute, TIME_TO_CONNECT_BOUNDS)
206 );
207
208 let bounds_err = LastChecked::try_from(zero_seconds).unwrap_err();
209 assert_eq!(
210 bounds_err,
211 ParamError::out_of_bounds(zero_seconds, LAST_CHECKED_BOUNDS)
212 );
213
214 let bounds_err = LastChecked::try_from(just_over_hour).unwrap_err();
215 assert_eq!(
216 bounds_err,
217 ParamError::out_of_bounds(just_over_hour, LAST_CHECKED_BOUNDS)
218 );
219 }
220
221 #[test]
222 fn it_works() {
223 let half_minute = Duration::from_secs(30);
224 let half_hour = Duration::from_secs(30 * 60);
225
226 let valid_time_to_connect = TimeToConnect::try_from(half_minute).unwrap();
227 assert_eq!(valid_time_to_connect.value(), half_minute);
228
229 let valid_last_checked = LastChecked::try_from(half_hour).unwrap();
230 assert_eq!(valid_last_checked.value(), half_hour);
231 }
232 }
233}