use serde::{Deserialize, Serialize};
use super::album::AlbumSimplified;
use super::artist::ArtistSimplified;
use super::common::{ExternalIds, ExternalUrls, LinkedFrom, Restrictions};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackSimplified {
pub artists: Option<Vec<ArtistSimplified>>,
pub available_markets: Option<Vec<String>>,
pub disc_number: Option<u32>,
pub duration_ms: u64,
pub explicit: Option<bool>,
pub external_urls: Option<ExternalUrls>,
pub href: Option<String>,
pub id: String,
pub is_playable: Option<bool>,
pub linked_from: Option<LinkedFrom>,
pub restrictions: Option<Restrictions>,
pub name: String,
pub preview_url: Option<String>,
pub track_number: Option<u32>,
#[serde(rename = "type")]
pub item_type: String,
pub uri: String,
pub is_local: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Track {
pub album: Option<AlbumSimplified>,
pub artists: Option<Vec<ArtistSimplified>>,
pub available_markets: Option<Vec<String>>,
pub disc_number: Option<u32>,
pub duration_ms: u64,
pub explicit: Option<bool>,
pub external_ids: Option<ExternalIds>,
pub external_urls: Option<ExternalUrls>,
pub href: Option<String>,
pub id: String,
pub is_playable: Option<bool>,
pub linked_from: Option<LinkedFrom>,
pub restrictions: Option<Restrictions>,
pub name: String,
pub popularity: Option<u32>,
pub preview_url: Option<String>,
pub track_number: Option<u32>,
#[serde(rename = "type")]
pub item_type: String,
pub uri: String,
pub is_local: Option<bool>,
}
impl Track {
pub fn artist_name(&self) -> Option<&str> {
self.artists
.as_ref()
.and_then(|artists| artists.first())
.map(|a| a.name.as_str())
}
pub fn artist_names(&self) -> String {
self.artists
.as_ref()
.map(|artists| {
artists
.iter()
.map(|a| a.name.as_str())
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default()
}
pub fn album_name(&self) -> Option<&str> {
self.album.as_ref().map(|a| a.name.as_str())
}
pub fn duration_str(&self) -> String {
let total_secs = self.duration_ms / 1000;
let mins = total_secs / 60;
let secs = total_secs % 60;
format!("{}:{:02}", mins, secs)
}
pub fn image_url(&self) -> Option<&str> {
self.album.as_ref().and_then(|a| a.image_url())
}
}
impl TrackSimplified {
pub fn artist_name(&self) -> Option<&str> {
self.artists
.as_ref()
.and_then(|artists| artists.first())
.map(|a| a.name.as_str())
}
pub fn duration_str(&self) -> String {
let total_secs = self.duration_ms / 1000;
let mins = total_secs / 60;
let secs = total_secs % 60;
format!("{}:{:02}", mins, secs)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedTrack {
pub added_at: String,
pub track: Track,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn track_simplified_deserializes() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"track_number": 5,
"disc_number": 1
});
let track: TrackSimplified = serde_json::from_value(json).unwrap();
assert_eq!(track.id, "track123");
assert_eq!(track.name, "Test Song");
assert_eq!(track.duration_ms, 210000);
}
#[test]
fn track_simplified_artist_name() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"artists": [{"id": "artist1", "name": "Test Artist", "type": "artist", "uri": "spotify:artist:artist1"}]
});
let track: TrackSimplified = serde_json::from_value(json).unwrap();
assert_eq!(track.artist_name(), Some("Test Artist"));
}
#[test]
fn track_simplified_artist_name_none() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000
});
let track: TrackSimplified = serde_json::from_value(json).unwrap();
assert!(track.artist_name().is_none());
}
#[test]
fn track_simplified_duration_str() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000 });
let track: TrackSimplified = serde_json::from_value(json).unwrap();
assert_eq!(track.duration_str(), "3:30");
}
#[test]
fn track_full_deserializes() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"popularity": 75,
"explicit": true
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.id, "track123");
assert_eq!(track.popularity, Some(75));
assert_eq!(track.explicit, Some(true));
}
#[test]
fn track_artist_name() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"artists": [{"id": "artist1", "name": "Primary Artist", "type": "artist", "uri": "spotify:artist:artist1"}]
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.artist_name(), Some("Primary Artist"));
}
#[test]
fn track_artist_names_multiple() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"artists": [
{"id": "artist1", "name": "Artist One", "type": "artist", "uri": "spotify:artist:artist1"},
{"id": "artist2", "name": "Artist Two", "type": "artist", "uri": "spotify:artist:artist2"}
]
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.artist_names(), "Artist One, Artist Two");
}
#[test]
fn track_artist_names_empty() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.artist_names(), "");
}
#[test]
fn track_album_name() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"album": {"id": "album1", "name": "Test Album", "type": "album", "uri": "spotify:album:album1"}
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.album_name(), Some("Test Album"));
}
#[test]
fn track_album_name_none() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000
});
let track: Track = serde_json::from_value(json).unwrap();
assert!(track.album_name().is_none());
}
#[test]
fn track_duration_str() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 185000 });
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.duration_str(), "3:05");
}
#[test]
fn track_image_url() {
let json = json!({
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000,
"album": {
"id": "album1",
"name": "Test Album",
"type": "album",
"uri": "spotify:album:album1",
"images": [{"url": "https://cover.jpg", "height": 640, "width": 640}]
}
});
let track: Track = serde_json::from_value(json).unwrap();
assert_eq!(track.image_url(), Some("https://cover.jpg"));
}
#[test]
fn saved_track_deserializes() {
let json = json!({
"added_at": "2024-01-15T10:30:00Z",
"track": {
"id": "track123",
"name": "Test Song",
"type": "track",
"uri": "spotify:track:track123",
"duration_ms": 210000
}
});
let saved: SavedTrack = serde_json::from_value(json).unwrap();
assert_eq!(saved.added_at, "2024-01-15T10:30:00Z");
assert_eq!(saved.track.id, "track123");
}
}