summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml3
-rw-r--r--examples/playlists/main.rs15
-rw-r--r--src/auth/mod.rs44
-rw-r--r--src/client.rs113
-rw-r--r--src/errors/auth.rs13
5 files changed, 110 insertions, 78 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 33db76b..d86a21c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,9 +8,8 @@ actix-web = { version = "4.1.0", default-features = false, features = ["macros"]
tokio = { version = "1.21.0", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.85"
-reqwest = { version = "0.11.11", features = ["json"] }
thiserror = "1.0.33"
-hyper = "0.14.20"
+hyper = { version = "0.14.20", features = ["client", "http2", "tcp"] }
[dev_dependencies]
config = "0.13.2"
diff --git a/examples/playlists/main.rs b/examples/playlists/main.rs
index fed5b73..81d3cf2 100644
--- a/examples/playlists/main.rs
+++ b/examples/playlists/main.rs
@@ -2,7 +2,7 @@ use std::error::Error;
use anyhow::Context;
use config::Config;
-use deez::auth::{request_access_token, AuthPromptHandler};
+use deez::auth::AuthPromptHandler;
use deez::client::DeezerClient;
use serde::Deserialize;
@@ -40,8 +40,11 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>>
println!("Retrieved authentication code '{}'", auth_code);
- let access_token =
- request_access_token(settings.app_id, settings.secret_key, auth_code).await?;
+ let client = DeezerClient::new();
+
+ let access_token = client
+ .get_access_token(settings.app_id, settings.secret_key, auth_code)
+ .await?;
println!(
"Retrieved access token '{}' which expires in {} seconds",
@@ -49,13 +52,11 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>>
access_token.expires.as_secs()
);
- let client = DeezerClient::new(access_token);
-
- let me = client.get_me().await?;
+ let me = client.get_me(access_token.clone()).await?;
println!("{:#?}", me);
- let playlists = client.get_user_playlists(me.id).await?;
+ let playlists = client.get_user_playlists(me.id, access_token).await?;
println!("Playlists: {:#?}", playlists);
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<AccessToken, AccessTokenRequestError>
-{
- let response = reqwest::get(format!(
- "{}?app_id={}&secret={}&code={}&output=json",
- ACCESS_TOKEN_URL, app_id, secret_key, auth_code
- ))
- .await?
- .json::<AccessTokenResponse>()
- .await?;
-
- Ok(response.into())
-}
-
-#[derive(Debug, Deserialize)]
-struct AccessTokenResponse
-{
- pub access_token: String,
- pub expires: u64,
-}
-
-impl From<AccessTokenResponse> 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<AccessTokenResponse> 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<HttpConnector>,
- 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<User, DeezerClientError>
+ pub async fn get_me(
+ &self,
+ access_token: AccessToken,
+ ) -> Result<User, DeezerClientError>
{
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<Vec<UserPlaylist>, 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<Playlist, DeezerClientError>
{
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<Uri, DeezerClientError>
+ /// 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<AccessToken, DeezerClientError>
+ {
+ 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<ErrorResponseBody, _> =
+ 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, DeezerClientError>
{
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,
-}