1use crate::model::incoming::{Exception, Track};
5use http::{
6 Error as HttpError, Request,
7 header::{AUTHORIZATION, HeaderValue},
8};
9use percent_encoding::NON_ALPHANUMERIC;
10use serde::{Deserialize, Deserializer, Serialize};
11use std::net::{IpAddr, SocketAddr};
12
13#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
15#[non_exhaustive]
16#[serde(rename_all = "camelCase")]
17pub struct PlaylistInfo {
18 pub name: String,
20 #[serde(default, deserialize_with = "deserialize_selected_track")]
22 pub selected_track: Option<u64>,
23}
24
25fn deserialize_selected_track<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
27where
28 D: Deserializer<'de>,
29{
30 Ok(Option::<i64>::deserialize(deserializer)
31 .ok()
32 .flatten()
33 .and_then(|selected| u64::try_from(selected).ok()))
34}
35
36#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
38#[non_exhaustive]
39#[serde(rename_all = "camelCase")]
40pub enum LoadResultName {
41 Empty,
43 Error,
45 Playlist,
47 Search,
49 Track,
51}
52
53#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
55#[non_exhaustive]
56#[serde(tag = "loadType", content = "data", rename_all = "camelCase")]
57pub enum LoadResultData {
58 Empty,
60 Error(Exception),
62 Playlist(PlaylistResult),
64 Search(Vec<Track>),
66 Track(Track),
68}
69
70#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
72#[non_exhaustive]
73#[serde(rename_all = "camelCase")]
74pub struct PlaylistResult {
75 pub info: PlaylistInfo,
77 pub tracks: Vec<Track>,
79}
80
81#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
83#[non_exhaustive]
84#[serde(rename_all = "camelCase")]
85pub struct LoadedTracks {
86 #[serde(flatten)]
88 pub data: LoadResultData,
89}
90
91#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93#[non_exhaustive]
94#[serde(rename_all = "camelCase")]
95pub struct FailingAddress {
96 pub address: String,
98 pub failing_timestamp: u64,
100 pub failing_time: String,
102}
103
104#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
106#[non_exhaustive]
107pub enum IpBlockType {
108 #[serde(rename = "Inet4Address")]
110 Inet4,
111 #[serde(rename = "Inet6Address")]
113 Inet6,
114}
115
116#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
118#[non_exhaustive]
119pub struct IpBlock {
120 pub kind: IpBlockType,
122 pub size: u64,
124}
125
126#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128#[non_exhaustive]
129#[serde(rename_all = "PascalCase")]
130pub enum RoutePlannerType {
131 NanoIp,
133 RotatingIp,
135 RotatingNanoIp,
137}
138
139#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
141#[non_exhaustive]
142#[serde(untagged)]
143pub enum RoutePlanner {
144 NanoIp(NanoIpRoutePlanner),
146 RotatingIp(RotatingIpRoutePlanner),
148 RotatingNanoIp(RotatingNanoIpRoutePlanner),
150}
151
152#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
154#[non_exhaustive]
155#[serde(rename_all = "camelCase")]
156pub struct NanoIpRoutePlanner {
157 pub class: RoutePlannerType,
161 pub details: NanoIpDetails,
163}
164
165#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
167#[non_exhaustive]
168#[serde(rename_all = "camelCase")]
169pub struct NanoIpDetails {
170 pub current_address_index: u64,
172 pub failing_addresses: Vec<FailingAddress>,
174 pub ip_block: IpBlock,
176}
177
178#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
180#[non_exhaustive]
181#[serde(rename_all = "camelCase")]
182pub struct RotatingIpRoutePlanner {
183 pub class: RoutePlannerType,
187 pub details: RotatingIpDetails,
189}
190
191#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
193#[non_exhaustive]
194#[serde(rename_all = "camelCase")]
195pub struct RotatingIpDetails {
196 pub current_address: String,
198 pub failing_addresses: Vec<FailingAddress>,
200 pub ip_block: IpBlock,
202 pub ip_index: u64,
204 pub rotate_index: u64,
206}
207
208#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
210#[non_exhaustive]
211#[serde(rename_all = "camelCase")]
212pub struct RotatingNanoIpRoutePlanner {
213 pub class: RoutePlannerType,
217 pub details: RotatingNanoIpDetails,
219}
220
221#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
223#[non_exhaustive]
224#[serde(rename_all = "camelCase")]
225pub struct RotatingNanoIpDetails {
226 pub block_index: String,
228 pub current_address_index: u64,
230 pub failing_addresses: Vec<FailingAddress>,
232 pub ip_block: IpBlock,
234}
235
236pub fn load_track(
245 address: SocketAddr,
246 identifier: impl AsRef<str>,
247 authorization: impl AsRef<str>,
248) -> Result<Request<&'static [u8]>, HttpError> {
249 let identifier =
250 percent_encoding::percent_encode(identifier.as_ref().as_bytes(), NON_ALPHANUMERIC);
251 let http_protocol = if cfg!(feature = "tls") {
252 "https"
253 } else {
254 "http"
255 };
256
257 let url = format!("{http_protocol}://{address}/v4/loadtracks?identifier={identifier}");
258
259 let mut req = Request::get(url);
260
261 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
262 req = req.header(AUTHORIZATION, auth_value);
263
264 req.body(b"")
265}
266
267pub fn decode_track(
277 address: SocketAddr,
278 encoded: impl AsRef<str>,
279 authorization: impl AsRef<str>,
280) -> Result<Request<&'static [u8]>, HttpError> {
281 let identifier =
282 percent_encoding::percent_encode(encoded.as_ref().as_bytes(), NON_ALPHANUMERIC);
283 let http_protocol = if cfg!(feature = "tls") {
284 "https"
285 } else {
286 "http"
287 };
288
289 let url = format!("{http_protocol}://{address}/v4/decodetrack?encodedTrack={identifier}");
290
291 let mut req = Request::get(url);
292
293 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
294 req = req.header(AUTHORIZATION, auth_value);
295
296 req.body(b"")
297}
298
299pub fn get_route_planner(
308 address: SocketAddr,
309 authorization: impl AsRef<str>,
310) -> Result<Request<&'static [u8]>, HttpError> {
311 let mut req = Request::get(format!("{address}/v4/routeplanner/status"));
312
313 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
314 req = req.header(AUTHORIZATION, auth_value);
315
316 req.body(b"")
317}
318
319#[allow(clippy::missing_panics_doc)]
327pub fn unmark_failed_address(
328 node_address: impl Into<SocketAddr>,
329 authorization: impl AsRef<str>,
330 route_address: impl Into<IpAddr>,
331) -> Result<Request<Vec<u8>>, HttpError> {
332 let mut req = Request::post(format!("{}/v4/routeplanner/status", node_address.into()));
333
334 let auth_value = HeaderValue::from_str(authorization.as_ref())?;
335 req = req.header(AUTHORIZATION, auth_value);
336
337 req.body(
338 serde_json::to_vec(&serde_json::json!({
339 "address": route_address.into(),
340 }))
341 .expect("valid json"),
342 )
343}
344
345#[cfg(test)]
346mod tests {
347 use super::{
348 FailingAddress, IpBlock, IpBlockType, LoadedTracks, NanoIpDetails, NanoIpRoutePlanner,
349 PlaylistInfo, RotatingIpDetails, RotatingIpRoutePlanner, RotatingNanoIpDetails,
350 RotatingNanoIpRoutePlanner, RoutePlanner, RoutePlannerType, Track,
351 };
352 use crate::model::incoming::TrackInfo;
353 use serde::{Deserialize, Serialize};
354 use serde_test::Token;
355 use static_assertions::{assert_fields, assert_impl_all};
356 use std::fmt::Debug;
357
358 assert_fields!(FailingAddress: address, failing_timestamp, failing_time);
359 assert_impl_all!(
360 FailingAddress: Clone,
361 Debug,
362 Deserialize<'static>,
363 Eq,
364 PartialEq,
365 Send,
366 Serialize,
367 Sync,
368 );
369 assert_impl_all!(
370 IpBlockType: Clone,
371 Debug,
372 Deserialize<'static>,
373 Eq,
374 PartialEq,
375 Send,
376 Serialize,
377 Sync,
378 );
379 assert_fields!(IpBlock: kind, size);
380 assert_impl_all!(
381 IpBlock: Clone,
382 Debug,
383 Deserialize<'static>,
384 Eq,
385 PartialEq,
386 Send,
387 Serialize,
388 Sync,
389 );
390 assert_impl_all!(
391 LoadedTracks: Clone,
392 Debug,
393 Deserialize<'static>,
394 Eq,
395 PartialEq,
396 Send,
397 Serialize,
398 Sync,
399 );
400 assert_fields!(
401 NanoIpDetails: current_address_index,
402 failing_addresses,
403 ip_block
404 );
405 assert_impl_all!(
406 NanoIpDetails: Clone,
407 Debug,
408 Deserialize<'static>,
409 Eq,
410 PartialEq,
411 Send,
412 Serialize,
413 Sync,
414 );
415 assert_fields!(NanoIpRoutePlanner: class, details);
416 assert_impl_all!(
417 NanoIpRoutePlanner: Clone,
418 Debug,
419 Deserialize<'static>,
420 Eq,
421 PartialEq,
422 Send,
423 Serialize,
424 Sync,
425 );
426 assert_fields!(PlaylistInfo: name, selected_track);
427 assert_impl_all!(
428 PlaylistInfo: Clone,
429 Debug,
430 Deserialize<'static>,
431 Eq,
432 PartialEq,
433 Send,
434 Serialize,
435 Sync,
436 );
437 assert_fields!(
438 RotatingIpDetails: current_address,
439 failing_addresses,
440 ip_block,
441 ip_index,
442 rotate_index
443 );
444 assert_impl_all!(
445 RotatingIpDetails: Clone,
446 Debug,
447 Deserialize<'static>,
448 Eq,
449 PartialEq,
450 Send,
451 Serialize,
452 Sync,
453 );
454 assert_fields!(RotatingIpRoutePlanner: class, details);
455 assert_impl_all!(
456 RotatingIpRoutePlanner: Clone,
457 Debug,
458 Deserialize<'static>,
459 Eq,
460 PartialEq,
461 Send,
462 Serialize,
463 Sync,
464 );
465 assert_fields!(
466 RotatingNanoIpDetails: block_index,
467 current_address_index,
468 failing_addresses,
469 ip_block
470 );
471 assert_impl_all!(
472 RotatingNanoIpDetails: Clone,
473 Debug,
474 Deserialize<'static>,
475 Eq,
476 PartialEq,
477 Send,
478 Serialize,
479 Sync,
480 );
481 assert_fields!(RotatingNanoIpRoutePlanner: class, details);
482 assert_impl_all!(
483 RotatingNanoIpRoutePlanner: Clone,
484 Debug,
485 Deserialize<'static>,
486 Eq,
487 PartialEq,
488 Send,
489 Serialize,
490 Sync,
491 );
492 assert_impl_all!(
493 RoutePlannerType: Clone,
494 Debug,
495 Deserialize<'static>,
496 Eq,
497 PartialEq,
498 Send,
499 Serialize,
500 Sync,
501 );
502 assert_impl_all!(
503 RoutePlanner: Clone,
504 Debug,
505 Deserialize<'static>,
506 Eq,
507 PartialEq,
508 Send,
509 Serialize,
510 Sync,
511 );
512 assert_fields!(
513 TrackInfo: author,
514 identifier,
515 is_seekable,
516 is_stream,
517 length,
518 position,
519 title,
520 uri
521 );
522 assert_impl_all!(
523 TrackInfo: Clone,
524 Debug,
525 Deserialize<'static>,
526 Eq,
527 PartialEq,
528 Send,
529 Serialize,
530 Sync
531 );
532 assert_fields!(Track: encoded, info);
533 assert_impl_all!(
534 Track: Clone,
535 Debug,
536 Deserialize<'static>,
537 Eq,
538 PartialEq,
539 Send,
540 Serialize,
541 Sync
542 );
543
544 #[test]
545 pub fn test_deserialize_playlist_info_negative_selected_track() {
546 let value = PlaylistInfo {
547 name: "Test Playlist".to_owned(),
548 selected_track: None,
549 };
550
551 serde_test::assert_de_tokens(
552 &value,
553 &[
554 Token::Struct {
555 name: "PlaylistInfo",
556 len: 13,
557 },
558 Token::Str("name"),
559 Token::Str("Test Playlist"),
560 Token::Str("selectedTrack"),
561 Token::I64(-1),
562 Token::StructEnd,
563 ],
564 );
565 }
566}