use crate::*;
use chrono::{DateTime, Utc};
use serde::Deserialize;
fn format_language(locale: (LanguageCode, CountryCode)) -> String {
format!("{}_{}", locale.0.code(), locale.1.alpha2())
}
pub async fn get_category(
token: &AccessToken,
name: &str,
locale: Option<(LanguageCode, CountryCode)>,
country: Option<CountryCode>,
) -> Result<Category, EndpointError<Error>> {
Ok(request!(
token,
GET "/v1/browse/categories/{}",
path_params = [name],
optional_query_params = {"locale": locale.map(format_language), "country": country.map(|c| c.alpha2())},
ret = Category
))
}
pub async fn get_categories(
token: &AccessToken,
limit: usize,
offset: usize,
locale: Option<(LanguageCode, CountryCode)>,
country: Option<CountryCode>,
) -> Result<Page<Category>, EndpointError<Error>> {
#[derive(Deserialize)]
struct CategoryPage {
categories: Page<Category>,
};
Ok(request!(
token,
GET "/v1/browse/categories",
query_params = {"limit": limit.to_string(), "offset": offset.to_string()},
optional_query_params = {"locale": locale.map(format_language), "country": country.map(|c| c.alpha2())},
ret = CategoryPage
).categories)
}
pub async fn get_category_playlists(
token: &AccessToken,
name: &str,
limit: usize,
offset: usize,
country: Option<CountryCode>,
) -> Result<Page<PlaylistSimplified>, EndpointError<Error>> {
#[derive(Deserialize)]
struct Playlists {
playlists: Page<PlaylistSimplified>,
};
Ok(request!(
token,
GET "/v1/browse/categories/{}/playlists",
path_params = [name],
query_params = {"limit": limit.to_string(), "offset": offset.to_string()},
optional_query_params = {"country": country.map(|c| c.alpha2())},
ret = Playlists
)
.playlists)
}
pub async fn get_featured_playlists(
token: &AccessToken,
limit: usize,
offset: usize,
locale: Option<(LanguageCode, CountryCode)>,
time: Option<DateTime<Utc>>,
country: Option<CountryCode>,
) -> Result<FeaturedPlaylists, EndpointError<Error>> {
Ok(request!(
token,
GET "/v1/browse/featured-playlists",
query_params = {"limit": limit.to_string(), "offset": offset.to_string()},
optional_query_params = {"locale": locale.map(format_language), "timestamp": time.map(|t| t.to_rfc3339()), "country": country.map(|c| c.alpha2())},
ret = FeaturedPlaylists
))
}
pub async fn get_new_releases(
token: &AccessToken,
limit: usize,
offset: usize,
country: Option<CountryCode>,
) -> Result<Page<AlbumSimplified>, EndpointError<Error>> {
#[derive(Deserialize)]
struct NewReleases {
albums: Page<AlbumSimplified>,
};
Ok(request!(
token,
GET "/v1/browse/new-releases",
query_params = {"limit": limit.to_string(), "offset": offset.to_string()},
optional_query_params = {"country": country.map(|c| c.alpha2())},
ret = NewReleases
)
.albums)
}
pub async fn get_recommendations<'iter, I>(
token: &AccessToken,
seed_artists: &[&str],
seed_genres: &[&str],
seed_tracks: &[&str],
attributes: I,
limit: usize,
market: Option<Market>,
) -> Result<Recommendations, EndpointError<Error>>
where
I: IntoIterator<Item = &'iter (&'iter str, &'iter str)>,
{
Ok(request!(
token,
GET "/v1/recommendations",
query_params = {"seed_artists": seed_artists.join(","), "seed_genres": seed_genres.join(","), "seed_tracks": seed_tracks.join(","), "limit": limit.to_string()},
optional_query_params = {"market": market.map(|m| m.as_str())},
additional_query_params = attributes,
ret = Recommendations
))
}
#[cfg(test)]
mod tests {
use crate::endpoints::token;
use crate::*;
use chrono::DateTime;
#[tokio::test]
async fn test_get_category() {
let category = get_category(
&token().await,
"pop",
Some((LanguageCode::En, CountryCode::GBR)),
Some(CountryCode::GBR),
)
.await
.unwrap();
assert_eq!(category.id, "pop");
assert_eq!(category.name, "Pop");
}
#[tokio::test]
async fn test_get_categories() {
let categories = get_categories(&token().await, 2, 0, None, None)
.await
.unwrap();
assert_eq!(categories.limit, 2);
assert_eq!(categories.offset, 0);
assert!(categories.items.len() <= 2);
}
#[tokio::test]
async fn test_get_category_playlists() {
let playlists =
get_category_playlists(&token().await, "chill", 1, 3, Some(CountryCode::GBR))
.await
.unwrap();
assert_eq!(playlists.limit, 1);
assert_eq!(playlists.offset, 3);
assert!(playlists.items.len() <= 1);
}
#[tokio::test]
async fn test_get_featured_playlists() {
let playlists = get_featured_playlists(
&token().await,
2,
0,
None,
Some(
DateTime::parse_from_rfc3339("2015-05-02T19:25:47Z")
.unwrap()
.into(),
),
None,
)
.await
.unwrap()
.playlists;
assert_eq!(playlists.limit, 2);
assert_eq!(playlists.offset, 0);
assert!(playlists.items.len() <= 2);
}
#[tokio::test]
async fn test_get_new_releases() {
let releases = get_new_releases(&token().await, 1, 0, None).await.unwrap();
assert_eq!(releases.limit, 1);
assert_eq!(releases.offset, 0);
assert!(releases.items.len() <= 1);
}
#[tokio::test]
async fn test_get_recommendations() {
let recommendations = get_recommendations(
&token().await,
&[],
&["rock"],
&["2RTkebdbPFyg4AMIzJZql1", "6fTt0CH2t0mdeB2N9XFG5r"],
&[
("max_acousticness", "0.8"),
("min_loudness", "-40"),
("target_popularity", "100"),
],
3,
Some(Market::Country(CountryCode::GBR)),
)
.await
.unwrap();
assert!(recommendations.seeds.len() <= 3);
assert_eq!(
recommendations
.seeds
.iter()
.filter(|seed| seed.entity_type == SeedType::Artist)
.count(),
0
);
assert_eq!(
recommendations
.seeds
.iter()
.filter(|seed| seed.entity_type == SeedType::Genre)
.count(),
1
);
assert_eq!(
recommendations
.seeds
.iter()
.filter(|seed| seed.entity_type == SeedType::Track)
.count(),
2
);
assert!(recommendations.tracks.len() <= 3);
}
}