nym_http_api_client/
url.rs1use std::fmt::Display;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10use itertools::Itertools;
11pub use url::ParseError;
12use url::form_urlencoded;
13
14pub trait IntoUrl {
16 fn to_url(self) -> Result<Url, ParseError>;
18
19 fn as_str(&self) -> &str;
21}
22
23impl IntoUrl for &str {
24 fn to_url(self) -> Result<Url, ParseError> {
25 let url = url::Url::parse(self)?;
26 Ok(url.into())
27 }
28
29 fn as_str(&self) -> &str {
30 self
31 }
32}
33
34impl IntoUrl for String {
35 fn to_url(self) -> Result<Url, ParseError> {
36 let url = url::Url::parse(&self)?;
37 Ok(url.into())
38 }
39
40 fn as_str(&self) -> &str {
41 self
42 }
43}
44
45impl IntoUrl for reqwest::Url {
46 fn to_url(self) -> Result<Url, ParseError> {
47 Ok(self.into())
48 }
49
50 fn as_str(&self) -> &str {
51 self.as_str()
52 }
53}
54
55#[derive(Debug, Clone)]
58pub struct Url {
59 url: url::Url,
60 fronts: Option<Vec<url::Url>>,
61 current_front: Arc<AtomicUsize>,
62}
63
64impl IntoUrl for Url {
65 fn to_url(self) -> Result<Url, ParseError> {
66 Ok(self)
67 }
68
69 fn as_str(&self) -> &str {
70 self.url.as_str()
71 }
72}
73
74impl PartialEq for Url {
75 fn eq(&self, other: &Self) -> bool {
76 let current = self.current_front.load(Ordering::Relaxed);
77 let other_current = other.current_front.load(Ordering::Relaxed);
78
79 self.fronts == other.fronts && self.url == other.url && current == other_current
80 }
81}
82
83impl Eq for Url {}
84
85impl std::hash::Hash for Url {
86 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
87 let current = self.current_front.load(Ordering::Relaxed);
88 self.fronts.hash(state);
89 self.url.hash(state);
90 current.hash(state);
91 }
92}
93
94impl Display for Url {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self.fronts {
97 Some(ref fronts) => {
98 let current = self.current_front.load(Ordering::Relaxed);
99 if let Some(front) = fronts.get(current) {
100 write!(f, "{front}=>{}", self.url)
101 } else {
102 write!(f, "{}", self.url)
103 }
104 }
105 None => write!(f, "{}", self.url),
106 }
107 }
108}
109
110impl From<Url> for url::Url {
111 fn from(val: Url) -> Self {
112 val.url
113 }
114}
115
116impl From<reqwest::Url> for Url {
117 fn from(url: url::Url) -> Self {
118 Self {
119 url,
120 fronts: None,
121 current_front: Arc::new(AtomicUsize::new(0)),
122 }
123 }
124}
125
126impl From<&reqwest::Url> for Url {
127 fn from(url: &url::Url) -> Self {
128 Self {
129 url: url.clone(),
130 fronts: None,
131 current_front: Arc::new(AtomicUsize::new(0)),
132 }
133 }
134}
135
136impl AsRef<url::Url> for Url {
137 fn as_ref(&self) -> &url::Url {
138 &self.url
139 }
140}
141
142impl AsMut<url::Url> for Url {
143 fn as_mut(&mut self) -> &mut url::Url {
144 &mut self.url
145 }
146}
147
148impl std::str::FromStr for Url {
149 type Err = url::ParseError;
150
151 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 let url = url::Url::parse(s)?;
153 Ok(Self {
154 url,
155 fronts: None,
156 current_front: Arc::new(AtomicUsize::new(0)),
157 })
158 }
159}
160
161impl Url {
162 pub fn new<U: reqwest::IntoUrl>(
165 url: U,
166 fronts: Option<Vec<U>>,
167 ) -> Result<Self, reqwest::Error> {
168 let mut url = Self {
169 url: url.into_url()?,
170 fronts: None,
171 current_front: Arc::new(AtomicUsize::new(0)),
172 };
173
174 if let Some(front_domains) = fronts {
176 let f: Vec<reqwest::Url> = front_domains
177 .into_iter()
178 .map(|front| front.into_url())
179 .try_collect()?;
180 url.fronts = Some(f);
181 }
182
183 Ok(url)
184 }
185
186 pub fn parse(s: &str) -> Result<Self, ParseError> {
188 let url = url::Url::parse(s)?;
189 Ok(Self {
190 url,
191 fronts: None,
192 current_front: Arc::new(AtomicUsize::new(0)),
193 })
194 }
195
196 pub fn inner_url(&self) -> &url::Url {
198 &self.url
199 }
200
201 pub fn has_front(&self) -> bool {
203 if let Some(fronts) = &self.fronts {
204 return !fronts.is_empty();
205 }
206 false
207 }
208
209 pub fn front_str(&self) -> Option<&str> {
212 let current = self.current_front.load(Ordering::Relaxed);
213 self.fronts
214 .as_ref()
215 .and_then(|fronts| fronts.get(current))
216 .and_then(|url| url.host_str())
217 }
218
219 pub fn fronts(&self) -> Option<&[url::Url]> {
221 self.fronts.as_deref()
222 }
223
224 pub fn host_str(&self) -> Option<&str> {
226 self.url.host_str()
227 }
228
229 pub fn as_str(&self) -> &str {
233 self.url.as_str()
234 }
235
236 pub fn update(&self) -> bool {
238 if let Some(fronts) = &self.fronts
239 && fronts.len() > 1
240 {
241 let current = self.current_front.load(Ordering::Relaxed);
242 let next = (current + 1) % fronts.len();
243 self.current_front.store(next, Ordering::Relaxed);
244 return next == 0;
245 }
246 true
247 }
248
249 pub fn scheme(&self) -> &str {
251 self.url.scheme()
252 }
253
254 pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
257 self.url.query_pairs()
258 }
259
260 pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<'_, ::url::UrlQuery<'_>> {
263 self.url.query_pairs_mut()
264 }
265
266 pub fn set_query(&mut self, query: Option<&str>) {
268 self.url.set_query(query);
269 }
270
271 pub fn set_path(&mut self, path: &str) {
273 self.url.set_path(path);
274 }
275
276 pub fn set_scheme(&mut self, scheme: &str) {
278 self.url.set_scheme(scheme).unwrap();
279 }
280
281 pub fn set_host(&mut self, host: &str) {
285 self.url.set_host(Some(host)).unwrap();
286 }
287
288 pub fn set_port(&mut self, port: u16) {
294 self.url.set_port(Some(port)).unwrap();
295 }
296
297 pub fn path_segments(&self) -> Option<std::str::Split<'_, char>> {
301 self.url.path_segments()
302 }
303
304 #[allow(clippy::result_unit_err)]
308 pub fn path_segments_mut(&mut self) -> Result<::url::PathSegmentsMut<'_>, ()> {
309 self.url.path_segments_mut()
310 }
311}