//! Deezer client. use std::fmt::Debug; use std::time::Duration; use hyper::client::{Client, HttpConnector}; use hyper::Uri; use serde::Deserialize; use crate::auth::{AccessToken, AuthCode}; use crate::errors::client::DeezerClientError; use crate::playlist::Playlist; use crate::user::{User, UserPlaylist, UserPlaylists}; /// Deezer API error. #[derive(Debug, Deserialize)] pub struct DeezerError { /// Error type. #[serde(rename = "type")] pub err_type: String, /// Error message. pub message: String, /// Error code. pub code: u32, } #[derive(Debug, Deserialize)] struct ErrorResponseBody { error: DeezerError, } #[derive(Debug, Deserialize)] struct AccessTokenResponse { pub access_token: String, pub expires: u64, } impl From for AccessToken { fn from(response: AccessTokenResponse) -> Self { Self { access_token: response.access_token, expires: Duration::from_secs(response.expires), } } } /// Deezer client. #[derive(Default)] pub struct DeezerClient { client: Client, api_uri_authority: &'static str, access_token_uri_authority: &'static str, access_token_uri_path: &'static str, } impl DeezerClient { /// Creates a new Deezer client. #[must_use] pub fn new() -> Self { Self { client: Client::new(), api_uri_authority: "api.deezer.com", access_token_uri_authority: "connect.deezer.com", access_token_uri_path: "/oauth/access_token.php", } } /// Returns the authenticated user. /// /// # Errors /// Will return Err if either sending the request or parsing the response fails. pub async fn get_me( &self, access_token: AccessToken, ) -> Result { let response = self .client .get(self.build_endpoint_uri( &"user/me".to_string(), &[("access_token", access_token.access_token)], )?) .await?; let body_buf = &*hyper::body::to_bytes(response).await?; let err_body_result: Result = serde_json::from_slice(body_buf); if let Ok(err_body) = err_body_result { return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); } serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) } /// Returns the playlists of a user. /// /// # Errors /// Will return Err if either sending the request or parsing the response fails. pub async fn get_user_playlists( &self, user_id: u64, access_token: AccessToken, ) -> Result, DeezerClientError> { let response = self .client .get(self.build_endpoint_uri( &format!("user/{}/playlists", user_id), &[("access_token", access_token.access_token)], )?) .await?; let body_buf = &*hyper::body::to_bytes(response).await?; let err_body_result: Result = serde_json::from_slice(body_buf); if let Ok(err_body) = err_body_result { return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); } let user_playlists: UserPlaylists = serde_json::from_slice(body_buf) .map_err(DeezerClientError::ParseResponseFailed)?; Ok(user_playlists.data) } /// Returns a playlist. /// /// # Errors /// Will return Err if either sending the request or parsing the response fails. pub async fn get_playlist( &self, playlist_id: u64, access_token: AccessToken, ) -> Result { let response = self .client .get(self.build_endpoint_uri( &format!("playlist/{}", playlist_id), &[("access_token", access_token.access_token)], )?) .await?; let body_buf = &*hyper::body::to_bytes(response).await?; let err_body_result: Result = serde_json::from_slice(body_buf); if let Ok(err_body) = err_body_result { return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); } serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) } /// Returns a access token. /// /// # Errors /// Will return Err if either sending the request or parsing the response fails. pub async fn get_access_token( &self, app_id: u32, secret_key: String, auth_code: AuthCode, ) -> Result { let uri = Uri::builder() .scheme("http") .authority(self.access_token_uri_authority) .path_and_query(format!( "{}?app_id={}&secret={}&code={}&output=json", self.access_token_uri_path, app_id, secret_key, auth_code )) .build() .map_err(|_| DeezerClientError::BuildAPIEndpointURIFailed)?; let response = self.client.get(uri).await?; let body_buf = &*hyper::body::to_bytes(response).await?; let err_body_result: Result = serde_json::from_slice(body_buf); if let Ok(err_body) = err_body_result { return Err(DeezerClientError::ReceivedErrorResponse(err_body.error)); } let access_token_response: AccessTokenResponse = serde_json::from_slice(body_buf) .map_err(DeezerClientError::ParseResponseFailed)?; Ok(access_token_response.into()) } fn build_endpoint_uri( &self, endpoint: &String, query_params: &[(&'static str, String)], ) -> Result { Uri::builder() .scheme("http") .authority(self.api_uri_authority) .path_and_query(format!( "/{}?{}", endpoint, query_params .iter() .map(|(key, value)| format!("{}={}", key, value)) .fold(String::new(), |acc, query_param| format!( "{}&{}", acc, query_param )) )) .build() .map_err(|_| DeezerClientError::BuildAPIEndpointURIFailed) } }