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 AsRef<url::Url> for Url {
127 fn as_ref(&self) -> &url::Url {
128 &self.url
129 }
130}
131
132impl AsMut<url::Url> for Url {
133 fn as_mut(&mut self) -> &mut url::Url {
134 &mut self.url
135 }
136}
137
138impl std::str::FromStr for Url {
139 type Err = url::ParseError;
140
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 let url = url::Url::parse(s)?;
143 Ok(Self {
144 url,
145 fronts: None,
146 current_front: Arc::new(AtomicUsize::new(0)),
147 })
148 }
149}
150
151impl Url {
152 pub fn new<U: reqwest::IntoUrl>(
155 url: U,
156 fronts: Option<Vec<U>>,
157 ) -> Result<Self, reqwest::Error> {
158 let mut url = Self {
159 url: url.into_url()?,
160 fronts: None,
161 current_front: Arc::new(AtomicUsize::new(0)),
162 };
163
164 if let Some(front_domains) = fronts {
166 let f: Vec<reqwest::Url> = front_domains
167 .into_iter()
168 .map(|front| front.into_url())
169 .try_collect()?;
170 url.fronts = Some(f);
171 }
172
173 Ok(url)
174 }
175
176 pub fn parse(s: &str) -> Result<Self, ParseError> {
178 let url = url::Url::parse(s)?;
179 Ok(Self {
180 url,
181 fronts: None,
182 current_front: Arc::new(AtomicUsize::new(0)),
183 })
184 }
185
186 pub fn inner_url(&self) -> &url::Url {
188 &self.url
189 }
190
191 pub fn has_front(&self) -> bool {
193 if let Some(fronts) = &self.fronts {
194 return !fronts.is_empty();
195 }
196 false
197 }
198
199 pub fn front_str(&self) -> Option<&str> {
202 let current = self.current_front.load(Ordering::Relaxed);
203 self.fronts
204 .as_ref()
205 .and_then(|fronts| fronts.get(current))
206 .and_then(|url| url.host_str())
207 }
208
209 pub fn fronts(&self) -> Option<&[url::Url]> {
211 self.fronts.as_deref()
212 }
213
214 pub fn host_str(&self) -> Option<&str> {
216 self.url.host_str()
217 }
218
219 pub fn as_str(&self) -> &str {
223 self.url.as_str()
224 }
225
226 pub fn update(&self) -> bool {
228 if let Some(fronts) = &self.fronts
229 && fronts.len() > 1
230 {
231 let current = self.current_front.load(Ordering::Relaxed);
232 let next = (current + 1) % fronts.len();
233 self.current_front.store(next, Ordering::Relaxed);
234 return next == 0;
235 }
236 true
237 }
238
239 pub fn scheme(&self) -> &str {
241 self.url.scheme()
242 }
243
244 pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
247 self.url.query_pairs()
248 }
249
250 pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<'_, ::url::UrlQuery<'_>> {
253 self.url.query_pairs_mut()
254 }
255
256 pub fn set_query(&mut self, query: Option<&str>) {
258 self.url.set_query(query);
259 }
260
261 pub fn set_path(&mut self, path: &str) {
263 self.url.set_path(path);
264 }
265
266 pub fn set_scheme(&mut self, scheme: &str) {
268 self.url.set_scheme(scheme).unwrap();
269 }
270
271 pub fn set_host(&mut self, host: &str) {
275 self.url.set_host(Some(host)).unwrap();
276 }
277
278 pub fn set_port(&mut self, port: u16) {
284 self.url.set_port(Some(port)).unwrap();
285 }
286
287 pub fn path_segments(&self) -> Option<std::str::Split<'_, char>> {
291 self.url.path_segments()
292 }
293
294 #[allow(clippy::result_unit_err)]
298 pub fn path_segments_mut(&mut self) -> Result<::url::PathSegmentsMut<'_>, ()> {
299 self.url.path_segments_mut()
300 }
301}