pub mod config;
use config::ConfigManager;
use derive_builder::Builder;
use reqwest::Proxy;
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::HashMap};
use typed_builder::TypedBuilder;
pub type SerializedIdentifier = String;
#[derive(Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SearchMode {
FastFirst,
OrderFirst,
}
impl Default for SearchMode {
fn default() -> Self {
SearchMode::FastFirst
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
#[non_exhaustive]
pub struct Artist {
#[builder(default = "".to_string())]
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
#[non_exhaustive]
pub struct Album {
#[builder(default = "".to_string())]
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TypedBuilder)]
#[non_exhaustive]
pub struct Song {
#[builder(default = "".to_string())]
pub id: String,
pub name: String,
#[builder(default)]
pub duration: Option<i64>,
#[builder(default)]
pub artists: Vec<Artist>,
#[builder(default)]
pub album: Option<Album>,
#[builder(default)]
pub context: Option<HashMap<String, String>>,
}
#[derive(Clone, Serialize, Deserialize, TypedBuilder)]
#[non_exhaustive]
pub struct SongSearchInformation {
pub source: Cow<'static, str>,
pub identifier: SerializedIdentifier,
#[builder(default)]
pub song: Option<Song>,
#[builder(default)]
pub pre_retrieve_result: Option<RetrievedSongInfo>,
}
#[derive(Clone, Serialize, Deserialize, TypedBuilder)]
#[non_exhaustive]
pub struct RetrievedSongInfo {
pub source: Cow<'static, str>,
pub url: String,
}
#[derive(Clone, Default, Serialize, Deserialize, Builder)]
#[builder(setter(into), default)]
#[non_exhaustive]
pub struct Context {
pub proxy_uri: Option<Cow<'static, str>>,
pub enable_flac: bool,
pub search_mode: SearchMode,
pub config: Option<ConfigManager>,
}
impl Context {
pub fn try_get_proxy(&self) -> reqwest::Result<Option<Proxy>> {
self.proxy_uri
.as_ref()
.map(|uri| Proxy::all(uri.to_string()))
.transpose()
}
}
impl std::fmt::Display for Song {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.keyword())
}
}
impl Song {
pub fn keyword(&self) -> String {
let mut keyword = self.name.to_string();
if self.artists.is_empty() {
return keyword;
}
let max_idx = self.artists.len() - 1;
keyword.push_str(" - ");
for (idx, artist) in self.artists.iter().enumerate() {
keyword.push_str(&artist.name);
if idx != max_idx {
keyword.push_str(", ");
}
}
keyword
}
}
#[cfg(test)]
mod tests {
use crate::{Artist, Song};
#[test]
fn test_keyword_with_no_artist() {
let s = Song {
id: "114514".to_string(),
name: "Lost River".to_string(),
artists: vec![],
..Default::default()
};
assert_eq!(s.keyword(), "Lost River");
}
#[test]
fn test_keyword_with_single_artist() {
let s = Song {
id: "123".to_string(),
name: "TT".to_string(),
artists: vec![Artist {
id: "114".to_string(),
name: "Twice".to_string(),
}],
..Default::default()
};
assert_eq!(s.keyword(), "TT - Twice");
}
#[test]
fn test_keyword_with_multiple_artist() {
let s = Song {
id: "123".to_string(),
name: "Hope for Tomorrow - Melchi Remix".to_string(),
artists: vec![
Artist {
id: "1".to_string(),
name: "Alex H".to_string(),
},
Artist {
id: "2".to_string(),
name: "Z8phyR".to_string(),
},
Artist {
id: "3".to_string(),
name: "Melchi".to_string(),
},
],
..Default::default()
};
assert_eq!(
s.keyword(),
"Hope for Tomorrow - Melchi Remix - Alex H, Z8phyR, Melchi"
);
}
}