aspotify 0.2.0

Asynchronous client for the Spotify API
Documentation
//! Endpoint functions to the Spotify API.
//!
//! # Common Parameters
//!
//! | Parameter | Use |
//! | --- | --- |
//! | `token` | An access token needed to make the request, generated by either a [CCFlow](../authorization/c_c_flow/struct.CCFlow.html) or an [AuthCodeFlow](../authorization/auth_code_flow/struct.AuthCodeFlow.html). |
//! | `id(s)` | The [Spotify ID(s)](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) of the required resource. |
//! | `country` | Limits the request to one particular country, so that resources not available in the country will not appear in the results. |
//! | `market` | Limits the request to one particular country, and applies [Track Relinking](https://developer.spotify.com/documentation/general/guides/track-relinking-guide/). |
//! | `locale` | The language of the response. It consists of an ISO-639 language code and an ISO-3166 country code (for, example, En and GBR is British English). |
//! | `limit` | When the function returns a [Page](../model/struct.Page.html), [CursorPage](../model/struct.CursorPage.html) or [TwoWayCursorPage](../model/struct.TwoWayCursorPage.html), this determines the maximum length of the page. |
//! | `offset` | When the function returns a [Page](../model/struct.Page.html), this determines what index in the larger list the page starts at. |
//! | `cursor`, `before` and `after` | When the function returns a [CursorPage](../model/struct.CursorPage.html) or [TwoWayCursorPage](../model/struct.TwoWayCursorPage.html), this determines to give the next (`cursor` or `after`) or previous (`before`) page. |

pub use albums::*;
pub use artists::*;
pub use browse::*;
pub use follow::*;
use isocountry::CountryCode;
pub use library::*;
pub use personalization::*;
pub use player::*;
pub use playlists::*;
pub use search::*;
pub use tracks::*;
pub use users_profile::*;

macro_rules! request {
    (
        $token:expr,
        $method:ident $path:expr
        $(, path_params = [$($path_param:expr),*])?
        $(, header_params = {$($header_param_name:literal: $header_param_value:expr),*})?
        $(, query_params = {$($query_param_name:literal: $query_param_value:expr),*})?
        $(, optional_query_params = {$($optional_query_param_name:literal: $optional_query_param_value:expr),*})?
        $(, additional_query_params = $additional_query_params:expr)?
        $(, body = $body:expr)?
        $(, ret = $type:ty)?
        $(, or_else =  $else:expr)?
    ) => {{
        #[allow(unused_mut)]
        let mut request = crate::CLIENT.request(
            reqwest::Method::$method,
            &format!(concat!("https://api.spotify.com", $path)$($(, $path_param)*)?)
        )
            $($(.header($header_param_name, $header_param_value))*)?
            $(.query(&[$(($query_param_name, $query_param_value)),*]))?
            $(.body($body))?
            .bearer_auth(&$token.token);
        $(
            $(
                if let Some(optional_query_param) = $optional_query_param_value {
                    request = request.query(&[($optional_query_param_name, optional_query_param)]);
                }
            )*
        )?
        $(
            for (name, value) in $additional_query_params.into_iter() {
                request = request.query(&[(name, value)]);
            }
        )?
        let response = loop {
            let response = request.try_clone().unwrap().send().await?;
            if response.status() != 429 {
                break response;
            }
            let wait = response.headers().get("Retry-After").and_then(|val| val.to_str().ok()).and_then(|secs| secs.parse::<u64>().ok());
            // 2 seconds is default retry after time; should never be used if the Spotify API and
            // my code are both correct.
            let wait = wait.unwrap_or(2);
            tokio::time::delay_for(std::time::Duration::from_secs(wait)).await;
        };
        let status = response.status();
        let text = response.text().await?;
        if cfg!(test) {
            println!("Request is {:?}", request);
            $(println!("Request body is {}", $body);)?
            println!("Response status is {}", status);
            println!("Response body is '{}'", text);
        }
        $(
            if text.is_empty() {
                return Ok($else);
            }
        )?
        if !status.is_success() {
            return Err(EndpointError::SpotifyError(serde_json::from_str(&text)?));
        }
        $(
            serde_json::from_str::<$type>(&text)?
        )?
    }};
}

pub mod albums;
pub mod artists;
pub mod browse;
pub mod follow;
pub mod library;
pub mod personalization;
pub mod player;
pub mod playlists;
pub mod search;
pub mod tracks;
pub mod users_profile;

/// A market in which to limit the request to.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Market {
    /// A country code.
    Country(CountryCode),
    /// Deduce the current country from the access token. Requires `user-read-private`.
    FromToken,
}

impl Market {
    fn as_str(self) -> &'static str {
        match self {
            Market::Country(code) => code.alpha2(),
            Market::FromToken => "from_token",
        }
    }
}

/// A time range from which to calculate the response.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeRange {
    /// Use several years of data.
    LongTerm,
    /// Use approximately the last 6 months of data.
    MediumTerm,
    /// Use approximately the last 4 weeks of data.
    ShortTerm,
}

impl TimeRange {
    fn as_str(self) -> &'static str {
        match self {
            Self::LongTerm => "long_term",
            Self::MediumTerm => "medium_term",
            Self::ShortTerm => "short_term",
        }
    }
}

#[cfg(test)]
async fn token() -> crate::AccessToken {
    dotenv::dotenv().unwrap();
    crate::AuthCodeFlow::from_refresh(
        &crate::ClientCredentials::from_env().unwrap(),
        std::fs::read_to_string(".refresh_token").unwrap(),
    )
    .send()
    .await
    .unwrap()
}