#![allow(clippy::missing_errors_doc)]
use std::future::Future;
use std::iter;
use std::time::Instant;
use futures_util::stream::{FuturesOrdered, FuturesUnordered, StreamExt, TryStreamExt};
use isocountry::CountryCode;
use crate::{Client, Error, Response};
pub use albums::*;
pub use artists::*;
pub use browse::*;
pub use episodes::*;
pub use follow::*;
pub use library::*;
pub use personalization::*;
pub use player::*;
pub use playlists::*;
pub use search::*;
pub use shows::*;
pub use tracks::*;
pub use users_profile::*;
macro_rules! endpoint {
($path:literal) => {
concat!("https://api.spotify.com", $path)
};
($path:literal, $($fmt:tt)*) => {
&format!(endpoint!($path), $($fmt)*)
};
}
mod albums;
mod artists;
mod browse;
mod episodes;
mod follow;
mod library;
mod personalization;
mod player;
mod playlists;
mod search;
mod shows;
mod tracks;
mod users_profile;
impl Client {
#[must_use]
pub const fn albums(&self) -> Albums<'_> {
Albums(self)
}
#[must_use]
pub const fn artists(&self) -> Artists<'_> {
Artists(self)
}
#[must_use]
pub const fn browse(&self) -> Browse<'_> {
Browse(self)
}
#[must_use]
pub const fn episodes(&self) -> Episodes<'_> {
Episodes(self)
}
#[must_use]
pub const fn follow(&self) -> Follow<'_> {
Follow(self)
}
#[must_use]
pub const fn library(&self) -> Library<'_> {
Library(self)
}
#[must_use]
pub const fn personalization(&self) -> Personalization<'_> {
Personalization(self)
}
#[must_use]
pub const fn player(&self) -> Player<'_> {
Player(self)
}
#[must_use]
pub const fn playlists(&self) -> Playlists<'_> {
Playlists(self)
}
#[must_use]
pub const fn search(&self) -> Search<'_> {
Search(self)
}
#[must_use]
pub const fn shows(&self) -> Shows<'_> {
Shows(self)
}
#[must_use]
pub const fn tracks(&self) -> Tracks<'_> {
Tracks(self)
}
#[must_use]
pub const fn users_profile(&self) -> UsersProfile<'_> {
UsersProfile(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Market {
Country(CountryCode),
FromToken,
}
impl Market {
fn as_str(self) -> &'static str {
match self {
Market::Country(code) => code.alpha2(),
Market::FromToken => "from_token",
}
}
fn query(self) -> (&'static str, &'static str) {
("market", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeRange {
Short,
Medium,
Long,
}
impl TimeRange {
fn as_str(self) -> &'static str {
match self {
Self::Long => "long_term",
Self::Medium => "medium_term",
Self::Short => "short_term",
}
}
}
type Chunk<'a, I> = iter::Take<&'a mut iter::Peekable<I>>;
async fn chunked_sequence<I: IntoIterator, Fut, T>(
items: I,
chunk_size: usize,
mut f: impl FnMut(Chunk<'_, I::IntoIter>) -> Fut,
) -> Result<Response<Vec<T>>, Error>
where
Fut: Future<Output = Result<Response<Vec<T>>, Error>>,
{
let mut items = items.into_iter().peekable();
let mut futures = FuturesOrdered::new();
while items.peek().is_some() {
futures.push(f(items.by_ref().take(chunk_size)));
}
let mut response = Response {
data: Vec::new(),
expires: Instant::now(),
};
while let Some(mut r) = futures.next().await.transpose()? {
response.data.append(&mut r.data);
response.expires = r.expires;
}
Ok(response)
}
async fn chunked_requests<I: IntoIterator, Fut>(
items: I,
chunk_size: usize,
mut f: impl FnMut(Chunk<'_, I::IntoIter>) -> Fut,
) -> Result<(), Error>
where
Fut: Future<Output = Result<(), Error>>,
{
let mut items = items.into_iter().peekable();
let futures = FuturesUnordered::new();
while items.peek().is_some() {
futures.push(f(items.by_ref().take(chunk_size)));
}
futures.try_collect().await
}
#[cfg(test)]
fn client() -> crate::Client {
dotenv::dotenv().unwrap();
let mut client = crate::Client::with_refresh(
crate::ClientCredentials::from_env().unwrap(),
std::fs::read_to_string(".refresh_token").unwrap(),
);
client.debug = true;
client
}