1use http::{
5 header::{HeaderValue, AUTHORIZATION},
6 Error as HttpError, Request,
7};
8use percent_encoding::NON_ALPHANUMERIC;
9use serde::{Deserialize, Deserializer, Serialize};
10use std::net::{IpAddr, SocketAddr};
11
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
14#[non_exhaustive]
15#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
16pub enum LoadType {
17 LoadFailed,
19 NoMatches,
21 PlaylistLoaded,
23 SearchResult,
25 TrackLoaded,
27}
28
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31#[non_exhaustive]
32#[serde(rename_all = "camelCase")]
33pub struct Track {
34 pub info: TrackInfo,
36 pub track: String,
40}
41
42#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
44#[non_exhaustive]
45#[serde(rename_all = "camelCase")]
46pub struct TrackInfo {
47 pub author: Option<String>,
49 pub identifier: String,
51 pub is_seekable: bool,
53 pub is_stream: bool,
55 pub length: u64,
57 pub position: u64,
59 pub title: Option<String>,
61 pub uri: String,
63}
64
65#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
67#[non_exhaustive]
68#[serde(rename_all = "camelCase")]
69pub struct PlaylistInfo {
70 pub name: Option<String>,
72 #[serde(default, deserialize_with = "deserialize_selected_track")]
74 pub selected_track: Option<u64>,
75}
76
77fn deserialize_selected_track<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
79where
80 D: Deserializer<'de>,
81{
82 Ok(Option::<i64>::deserialize(deserializer)
83 .ok()
84 .flatten()
85 .and_then(|selected| u64::try_from(selected).ok()))
86}
87
88#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
90#[non_exhaustive]
91#[serde(rename_all = "camelCase")]
92pub struct LoadedTracks {
93 pub load_type: LoadType,
95 pub playlist_info: PlaylistInfo,
97 pub tracks: Vec<Track>,
99}
100
101#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103#[non_exhaustive]
104#[serde(rename_all = "camelCase")]
105pub struct FailingAddress {
106 pub address: String,
108 pub failing_timestamp: u64,
110 pub failing_time: String,
112}
113
114#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
116#[non_exhaustive]
117pub enum IpBlockType {
118 #[serde(rename = "Inet4Address")]
120 Inet4,
121 #[serde(rename = "Inet6Address")]
123 Inet6,
124}
125
126#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128#[non_exhaustive]
129pub struct IpBlock {
130 pub kind: IpBlockType,
132 pub size: u64,
134}
135
136#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
138#[non_exhaustive]
139#[serde(rename_all = "PascalCase")]
140pub enum RoutePlannerType {
141 NanoIp,
143 RotatingIp,
145 RotatingNanoIp,
147}
148
149#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
151#[non_exhaustive]
152#[serde(untagged)]
153pub enum RoutePlanner {
154 NanoIp(NanoIpRoutePlanner),
156 RotatingIp(RotatingIpRoutePlanner),
158 RotatingNanoIp(RotatingNanoIpRoutePlanner),
160}
161
162#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
164#[non_exhaustive]
165#[serde(rename_all = "camelCase")]
166pub struct NanoIpRoutePlanner {
167 pub class: RoutePlannerType,
171 pub details: NanoIpDetails,
173}
174
175#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
177#[non_exhaustive]
178#[serde(rename_all = "camelCase")]
179pub struct NanoIpDetails {
180 pub current_address_index: u64,
182 pub failing_addresses: Vec<FailingAddress>,
184 pub ip_block: IpBlock,
186}
187
188#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
190#[non_exhaustive]
191#[serde(rename_all = "camelCase")]
192pub struct RotatingIpRoutePlanner {
193 pub class: RoutePlannerType,
197 pub details: RotatingIpDetails,
199}
200
201#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203#[non_exhaustive]
204#[serde(rename_all = "camelCase")]
205pub struct RotatingIpDetails {
206 pub current_address: String,
208 pub failing_addresses: Vec<FailingAddress>,
210 pub ip_block: IpBlock,
212 pub ip_index: u64,
214 pub rotate_index: u64,
216}
217
218#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
220#[non_exhaustive]
221#[serde(rename_all = "camelCase")]
222pub struct RotatingNanoIpRoutePlanner {
223 pub class: RoutePlannerType,
227 pub details: RotatingNanoIpDetails,
229}
230
231#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
233#[non_exhaustive]
234#[serde(rename_all = "camelCase")]
235pub struct RotatingNanoIpDetails {
236 pub block_index: String,
238 pub current_address_index: u64,
240 pub failing_addresses: Vec<FailingAddress>,
242 pub ip_block: IpBlock,
244}
245
246pub fn load_track(
255 address: SocketAddr,
256 identifier: impl AsRef<str>,
257 authorization: impl AsRef<str>,
258) -> Result<Request<&'static [u8]>, HttpError> {
259 let identifier =
260 percent_encoding::percent_encode(identifier.as_ref().as_bytes(), NON_ALPHANUMERIC);
261 let url = format!("http://{address}/loadtracks?identifier={identifier}");
262
263 let mut req = Request::get(url);
264
265 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
266 req = req.header(AUTHORIZATION, auth_value);
267
268 req.body(b"")
269}
270
271pub fn get_route_planner(
280 address: SocketAddr,
281 authorization: impl AsRef<str>,
282) -> Result<Request<&'static [u8]>, HttpError> {
283 let mut req = Request::get(format!("{address}/routeplanner/status"));
284
285 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
286 req = req.header(AUTHORIZATION, auth_value);
287
288 req.body(b"")
289}
290
291#[allow(clippy::missing_panics_doc)]
299pub fn unmark_failed_address(
300 node_address: impl Into<SocketAddr>,
301 authorization: impl AsRef<str>,
302 route_address: impl Into<IpAddr>,
303) -> Result<Request<Vec<u8>>, HttpError> {
304 let mut req = Request::post(format!("{}/routeplanner/status", node_address.into()));
305
306 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
307 req = req.header(AUTHORIZATION, auth_value);
308
309 req.body(
310 serde_json::to_vec(&serde_json::json!({
311 "address": route_address.into(),
312 }))
313 .expect("valid json"),
314 )
315}
316
317#[cfg(test)]
318mod tests {
319 use super::{
320 FailingAddress, IpBlock, IpBlockType, LoadType, LoadedTracks, NanoIpDetails,
321 NanoIpRoutePlanner, PlaylistInfo, RotatingIpDetails, RotatingIpRoutePlanner,
322 RotatingNanoIpDetails, RotatingNanoIpRoutePlanner, RoutePlanner, RoutePlannerType, Track,
323 TrackInfo,
324 };
325 use serde::{Deserialize, Serialize};
326 use serde_test::Token;
327 use static_assertions::{assert_fields, assert_impl_all};
328 use std::fmt::Debug;
329
330 assert_fields!(FailingAddress: address, failing_timestamp, failing_time);
331 assert_impl_all!(
332 FailingAddress: Clone,
333 Debug,
334 Deserialize<'static>,
335 Eq,
336 PartialEq,
337 Send,
338 Serialize,
339 Sync,
340 );
341 assert_impl_all!(
342 IpBlockType: Clone,
343 Debug,
344 Deserialize<'static>,
345 Eq,
346 PartialEq,
347 Send,
348 Serialize,
349 Sync,
350 );
351 assert_fields!(IpBlock: kind, size);
352 assert_impl_all!(
353 IpBlock: Clone,
354 Debug,
355 Deserialize<'static>,
356 Eq,
357 PartialEq,
358 Send,
359 Serialize,
360 Sync,
361 );
362 assert_impl_all!(
363 LoadType: Clone,
364 Debug,
365 Deserialize<'static>,
366 Eq,
367 PartialEq,
368 Send,
369 Serialize,
370 Sync,
371 );
372 assert_fields!(LoadedTracks: load_type, playlist_info, tracks);
373 assert_impl_all!(
374 LoadedTracks: Clone,
375 Debug,
376 Deserialize<'static>,
377 Eq,
378 PartialEq,
379 Send,
380 Serialize,
381 Sync,
382 );
383 assert_fields!(
384 NanoIpDetails: current_address_index,
385 failing_addresses,
386 ip_block
387 );
388 assert_impl_all!(
389 NanoIpDetails: Clone,
390 Debug,
391 Deserialize<'static>,
392 Eq,
393 PartialEq,
394 Send,
395 Serialize,
396 Sync,
397 );
398 assert_fields!(NanoIpRoutePlanner: class, details);
399 assert_impl_all!(
400 NanoIpRoutePlanner: Clone,
401 Debug,
402 Deserialize<'static>,
403 Eq,
404 PartialEq,
405 Send,
406 Serialize,
407 Sync,
408 );
409 assert_fields!(PlaylistInfo: name, selected_track);
410 assert_impl_all!(
411 PlaylistInfo: Clone,
412 Debug,
413 Deserialize<'static>,
414 Eq,
415 PartialEq,
416 Send,
417 Serialize,
418 Sync,
419 );
420 assert_fields!(
421 RotatingIpDetails: current_address,
422 failing_addresses,
423 ip_block,
424 ip_index,
425 rotate_index
426 );
427 assert_impl_all!(
428 RotatingIpDetails: Clone,
429 Debug,
430 Deserialize<'static>,
431 Eq,
432 PartialEq,
433 Send,
434 Serialize,
435 Sync,
436 );
437 assert_fields!(RotatingIpRoutePlanner: class, details);
438 assert_impl_all!(
439 RotatingIpRoutePlanner: Clone,
440 Debug,
441 Deserialize<'static>,
442 Eq,
443 PartialEq,
444 Send,
445 Serialize,
446 Sync,
447 );
448 assert_fields!(
449 RotatingNanoIpDetails: block_index,
450 current_address_index,
451 failing_addresses,
452 ip_block
453 );
454 assert_impl_all!(
455 RotatingNanoIpDetails: Clone,
456 Debug,
457 Deserialize<'static>,
458 Eq,
459 PartialEq,
460 Send,
461 Serialize,
462 Sync,
463 );
464 assert_fields!(RotatingNanoIpRoutePlanner: class, details);
465 assert_impl_all!(
466 RotatingNanoIpRoutePlanner: Clone,
467 Debug,
468 Deserialize<'static>,
469 Eq,
470 PartialEq,
471 Send,
472 Serialize,
473 Sync,
474 );
475 assert_impl_all!(
476 RoutePlannerType: Clone,
477 Debug,
478 Deserialize<'static>,
479 Eq,
480 PartialEq,
481 Send,
482 Serialize,
483 Sync,
484 );
485 assert_impl_all!(
486 RoutePlanner: Clone,
487 Debug,
488 Deserialize<'static>,
489 Eq,
490 PartialEq,
491 Send,
492 Serialize,
493 Sync,
494 );
495 assert_fields!(
496 TrackInfo: author,
497 identifier,
498 is_seekable,
499 is_stream,
500 length,
501 position,
502 title,
503 uri
504 );
505 assert_impl_all!(
506 TrackInfo: Clone,
507 Debug,
508 Deserialize<'static>,
509 Eq,
510 PartialEq,
511 Send,
512 Serialize,
513 Sync
514 );
515 assert_fields!(Track: info, track);
516 assert_impl_all!(
517 Track: Clone,
518 Debug,
519 Deserialize<'static>,
520 Eq,
521 PartialEq,
522 Send,
523 Serialize,
524 Sync
525 );
526
527 #[test]
528 pub fn test_deserialize_playlist_info_negative_selected_track() {
529 let value = PlaylistInfo {
530 name: Some("Test Playlist".to_owned()),
531 selected_track: None,
532 };
533
534 serde_test::assert_de_tokens(
535 &value,
536 &[
537 Token::Struct {
538 name: "PlaylistInfo",
539 len: 13,
540 },
541 Token::Str("name"),
542 Token::Some,
543 Token::Str("Test Playlist"),
544 Token::Str("selectedTrack"),
545 Token::I64(-1),
546 Token::StructEnd,
547 ],
548 );
549 }
550}