From 8a02d3386d4ce0b58de943fcf42bd072af1e0b42 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 8 Sep 2022 21:08:48 +0200 Subject: refactor: move get access token to DeezerClient --- src/auth/mod.rs | 44 +-------------------- src/client.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++------- src/errors/auth.rs | 13 ------ 3 files changed, 101 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/auth/mod.rs b/src/auth/mod.rs index fae0383..ce550e1 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -11,12 +11,11 @@ use tokio::task::JoinHandle; use tokio::{select, spawn}; use crate::auth::service::retrieve_auth_code; -use crate::errors::auth::{AccessTokenRequestError, AuthPromptHandlerError}; +use crate::errors::auth::AuthPromptHandlerError; mod service; const AUTH_URL: &str = "https://connect.deezer.com/oauth/auth.php"; -const ACCESS_TOKEN_URL: &str = "https://connect.deezer.com/oauth/access_token.php"; /// A Deezer authentication code. #[derive(Debug, Deserialize, Clone)] @@ -96,7 +95,7 @@ impl AuthPromptHandler } /// A Deezer access token. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AccessToken { /// The access token. @@ -105,42 +104,3 @@ pub struct AccessToken /// The duration until the access token expires. pub expires: Duration, } - -/// Sends a request for a access token. -/// -/// # Errors -/// Will return Err if either sending the request fails or parsing the response fails. -pub async fn request_access_token( - app_id: u32, - secret_key: String, - auth_code: AuthCode, -) -> Result -{ - let response = reqwest::get(format!( - "{}?app_id={}&secret={}&code={}&output=json", - ACCESS_TOKEN_URL, app_id, secret_key, auth_code - )) - .await? - .json::() - .await?; - - Ok(response.into()) -} - -#[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), - } - } -} diff --git a/src/client.rs b/src/client.rs index 0699545..179377f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,11 +1,12 @@ //! 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; +use crate::auth::{AccessToken, AuthCode}; use crate::errors::client::DeezerClientError; use crate::playlist::Playlist; use crate::user::{User, UserPlaylist, UserPlaylists}; @@ -31,24 +32,45 @@ 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_url: &'static str, - access_token: AccessToken, + 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(access_token: AccessToken) -> Self + pub fn new() -> Self { Self { client: Client::new(), - api_url: "api.deezer.com", - access_token, + api_uri_authority: "api.deezer.com", + access_token_uri_authority: "connect.deezer.com", + access_token_uri_path: "/oauth/access_token.php", } } @@ -56,11 +78,17 @@ impl DeezerClient /// /// # Errors /// Will return Err if either sending the request or parsing the response fails. - pub async fn get_me(&self) -> Result + pub async fn get_me( + &self, + access_token: AccessToken, + ) -> Result { let response = self .client - .get(self.build_endpoint_uri(&"user/me".to_string())?) + .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?; @@ -82,11 +110,15 @@ impl DeezerClient 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))?) + .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?; @@ -111,11 +143,15 @@ impl DeezerClient 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))?) + .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?; @@ -130,14 +166,63 @@ impl DeezerClient serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) } - fn build_endpoint_uri(&self, endpoint: &String) -> Result + /// 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_url) + .authority(self.api_uri_authority) .path_and_query(format!( - "/{}?access_token={}", - endpoint, self.access_token.access_token + "/{}?{}", + 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) diff --git a/src/errors/auth.rs b/src/errors/auth.rs index cd4741d..656673e 100644 --- a/src/errors/auth.rs +++ b/src/errors/auth.rs @@ -8,16 +8,3 @@ pub enum AuthPromptHandlerError #[error("HTTP server failed to bind to address")] BindAddressFailed, } - -/// Access token request error. -#[derive(Debug, thiserror::Error)] -pub enum AccessTokenRequestError -{ - /// Sending access token request failed. - #[error("Sending access token request failed")] - SendFailed(#[from] reqwest::Error), - - /// Parsing access token respone failed. - #[error("Parsing access token respone failed")] - ResponseParseFailed, -} -- cgit v1.2.3-18-g5258