diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/auth/mod.rs | 1 | ||||
-rw-r--r-- | src/client.rs | 133 | ||||
-rw-r--r-- | src/creator.rs | 13 | ||||
-rw-r--r-- | src/errors/client.rs | 28 | ||||
-rw-r--r-- | src/errors/mod.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/playlist.rs | 166 | ||||
-rw-r--r-- | src/user.rs | 153 |
8 files changed, 480 insertions, 19 deletions
diff --git a/src/auth/mod.rs b/src/auth/mod.rs index c5beeb3..fae0383 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -96,6 +96,7 @@ impl AuthPromptHandler } /// A Deezer access token. +#[derive(Debug)] pub struct AccessToken { /// The access token. diff --git a/src/client.rs b/src/client.rs index 5c6858b..0699545 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,50 +1,145 @@ //! Deezer client. -use std::error::Error; +use std::fmt::Debug; +use hyper::client::{Client, HttpConnector}; +use hyper::Uri; use serde::Deserialize; -/// A user. +use crate::auth::AccessToken; +use crate::errors::client::DeezerClientError; +use crate::playlist::Playlist; +use crate::user::{User, UserPlaylist, UserPlaylists}; + +/// Deezer API error. #[derive(Debug, Deserialize)] -pub struct User +pub struct DeezerError { - /// The user ID. - pub id: u32, + /// Error type. + #[serde(rename = "type")] + pub err_type: String, + + /// Error message. + pub message: String, - /// The user name. - pub name: String, + /// Error code. + pub code: u32, +} + +#[derive(Debug, Deserialize)] +struct ErrorResponseBody +{ + error: DeezerError, } /// Deezer client. -#[derive(Default)] pub struct DeezerClient { - client: reqwest::Client, + client: Client<HttpConnector>, api_url: &'static str, + access_token: AccessToken, } impl DeezerClient { /// Creates a new Deezer client. #[must_use] - pub fn new() -> Self + pub fn new(access_token: AccessToken) -> Self { Self { - client: reqwest::Client::new(), - api_url: "https://api.deezer.com", + client: Client::new(), + api_url: "api.deezer.com", + access_token, } } /// Returns the authenticated user. - pub async fn get_me(&self) -> Result<User, Box<dyn Error + Send + Sync>> + /// + /// # Errors + /// Will return Err if either sending the request or parsing the response fails. + pub async fn get_me(&self) -> Result<User, DeezerClientError> + { + let response = self + .client + .get(self.build_endpoint_uri(&"user/me".to_string())?) + .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)); + } + + 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, + ) -> Result<Vec<UserPlaylist>, DeezerClientError> + { + let response = self + .client + .get(self.build_endpoint_uri(&format!("user/{}/playlists", user_id))?) + .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 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, + ) -> Result<Playlist, DeezerClientError> { - let user = self + let response = self .client - .get(format!("{}/user/me", self.api_url)) - .send() - .await? - .json::<User>() + .get(self.build_endpoint_uri(&format!("playlist/{}", playlist_id))?) .await?; - Ok(user) + 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)); + } + + serde_json::from_slice(body_buf).map_err(DeezerClientError::ParseResponseFailed) + } + + fn build_endpoint_uri(&self, endpoint: &String) -> Result<Uri, DeezerClientError> + { + Uri::builder() + .scheme("http") + .authority(self.api_url) + .path_and_query(format!( + "/{}?access_token={}", + endpoint, self.access_token.access_token + )) + .build() + .map_err(|_| DeezerClientError::BuildAPIEndpointURIFailed) } } diff --git a/src/creator.rs b/src/creator.rs new file mode 100644 index 0000000..81e3797 --- /dev/null +++ b/src/creator.rs @@ -0,0 +1,13 @@ +//! Creator. +use serde::Deserialize; + +/// A user that created another object. +#[derive(Debug, Deserialize)] +pub struct Creator +{ + /// User ID. + pub id: u64, + + /// User name. + pub name: String, +} diff --git a/src/errors/client.rs b/src/errors/client.rs new file mode 100644 index 0000000..c3397e3 --- /dev/null +++ b/src/errors/client.rs @@ -0,0 +1,28 @@ +//! Error types for [`DeezerClient`]. + +use crate::client::DeezerError; + +/// Error type for [`DeezerClient`]. +#[derive(Debug, thiserror::Error)] +pub enum DeezerClientError +{ + /// Failed to send a HTTP request. + #[error("Failed to send HTTP request")] + SendRequestFailed(#[from] hyper::Error), + + /// Failed to parse a response from the Deezer API. + #[error("Failed to parse response from the Deezer API")] + ParseResponseFailed(#[from] serde_json::Error), + + /// Received a error response from the Deezer API. + #[error("Received a error response from the Deezer API")] + ReceivedErrorResponse(DeezerError), + + /// Failed to build API endpoint URI. + #[error("Failed to build API endpoint URI")] + BuildAPIEndpointURIFailed, + + /// Received a invalid response body from the Deezer API. + #[error("Received a invalid response body from the Deezer API")] + InvalidResponseBody, +} diff --git a/src/errors/mod.rs b/src/errors/mod.rs index ea85f4a..ad4da47 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -1,3 +1,4 @@ //! Error types. pub mod auth; +pub mod client; @@ -5,4 +5,8 @@ #![allow(clippy::module_name_repetitions)] pub mod auth; +pub mod client; +pub mod creator; pub mod errors; +pub mod playlist; +pub mod user; diff --git a/src/playlist.rs b/src/playlist.rs new file mode 100644 index 0000000..b918282 --- /dev/null +++ b/src/playlist.rs @@ -0,0 +1,166 @@ +//! Playlist. +use serde::Deserialize; + +use crate::creator::Creator; + +/// A playlist. +#[derive(Debug, Deserialize)] +pub struct Playlist +{ + /// The playlist's Deezer id. + pub id: u64, + + /// The playlist's title. + pub title: String, + + /// The playlist description. + pub description: String, + + /// The playlist's duration (seconds). + pub duration: u64, + + /// If the playlist is public or not. + pub public: bool, + + /// If the playlist is the love tracks playlist. + pub is_loved_track: bool, + + /// If the playlist is collaborative or not. + pub collaborative: bool, + + /// Nb tracks in the playlist. + pub nb_tracks: u64, + + /// Nb tracks not seen. + pub unseen_track_count: Option<u64>, + + /// The number of playlist's fans. + pub fans: u64, + + /// The url of the playlist on Deezer. + pub link: String, + + /// The share link of the playlist on Deezer. + pub share: String, + + /// The url of the playlist's cover. Add 'size' parameter to the url to change size. + /// Can be 'small', 'medium', 'big', 'xl'. + pub picture: String, + + /// The url of the playlist's cover in size small. + pub picture_small: String, + + /// The url of the playlist's cover in size medium. + pub picture_medium: String, + + /// The url of the playlist's cover in size big. + pub picture_big: String, + + /// The url of the playlist's cover in size xl. + pub picture_xl: String, + + /// The checksum for the track list. + pub checksum: String, + + /// The creator of the playlist. + pub creator: Creator, + + /// List of tracks. + pub tracks: PlaylistTracks, +} + +/// A track in a playlist. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrack +{ + /// The track's Deezer id + pub id: u64, + + /// true if the track is readable in the player for the current user + pub readable: bool, + + /// The track's fulltitle + pub title: String, + + /// The track's short title + pub title_short: String, + + /// The track version + pub title_version: String, + + /// The track unseen status + pub unseen: Option<bool>, + + /// The url of the track on Deezer + pub link: String, + + /// The track's duration in seconds + pub duration: u64, + + /// The track's Deezer rank + pub rank: u64, + + /// Whether the track contains explicit lyrics + pub explicit_lyrics: bool, + + /// The url of track's preview file. This file contains the first 30 seconds of the + /// track + pub preview: String, + + /// The time when the track has been added to the playlist + pub time_add: u64, + + /// The track's artist. + pub artist: PlaylistTrackArtist, + + /// The track's album. + pub album: PlaylistTrackAlbum, +} + +/// The artist of a playlist track. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrackArtist +{ + /// Artist id. + pub id: u64, + + /// Artist name. + pub name: String, + + /// Artist url. + pub link: String, +} + +/// The album of a playlist track. +#[derive(Debug, Deserialize)] +pub struct PlaylistTrackAlbum +{ + /// Album id. + pub id: u64, + + /// Album title. + pub title: String, + + /// Album cover. + pub cover: String, + + /// Album cover in size small. + pub cover_small: String, + + /// Album cover in size medium. + pub cover_medium: String, + + /// Album cover in size big. + pub cover_big: String, + + /// Album cover in side xl. + pub cover_xl: String, +} + +/// Tracks in a playlist. +#[derive(Debug, Deserialize)] +pub struct PlaylistTracks +{ + /// Tracks. + pub data: Vec<PlaylistTrack>, +} diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..ee22f5a --- /dev/null +++ b/src/user.rs @@ -0,0 +1,153 @@ +//! Deezer user. + +/* +pub enum ExplicitContentLevel +{ + explicit_display, + explicit_no_recommendation, + explicit_hide, +} +*/ +use serde::Deserialize; + +use crate::creator::Creator; + +/// A Deezer user. +#[derive(Debug, Deserialize)] +pub struct User +{ + /// The user's Deezer ID + pub id: u64, + + /// The user's Deezer nickname + pub name: String, + + /// The user's last name + pub lastname: String, + + /// The user's first name + pub firstname: String, + + /// The user's email + pub email: Option<String>, + + /// The user's status + pub status: u32, + + /// The user's birthday + pub birthday: String, + + /// The user's inscription date + pub inscription_date: String, + + /// The user's gender : F or M + pub gender: String, + + /// The String of the profil for the user on Deezer + pub link: String, + + /// The String of the user's profil picture. Add 'size' parameter to the String to + /// change size. Can be 'small', 'medium', 'big', 'xl' + pub picture: String, + + /// The String of the user's profil picture in size small. + pub picture_small: String, + + /// The String of the user's profil picture in size medium. + pub picture_medium: String, + + /// The String of the user's profil picture in size big. + pub picture_big: String, + + /// The String of the user's profil picture in size xl. + pub picture_xl: String, + + /// The user's country + pub country: String, + + /// The user's language + pub lang: String, + + /// If the user is a kid or not + pub is_kid: bool, + + /// The user's explicit content level according to his country + pub explicit_content_level: String, + + /// The user's available explicit content levels according to his country. Possible + /// values are: explicit_display, explicit_no_recommendation and explicit_hide + pub explicit_content_levels_available: Vec<String>, + + /// API Link to the flow of this user + pub tracklist: String, + + /// Object type. + #[serde(rename = "type")] + pub object_type: String, +} + +/// A user's playlist. +#[derive(Debug, Deserialize)] +pub struct UserPlaylist +{ + /// The playlist's Deezer id + pub id: u64, + + /// The playlist's title + pub title: String, + + /// The playlist's duration (seconds) + pub duration: u64, + + /// If the playlist is public or not + pub public: bool, + + /// If the playlist is the love tracks playlist + pub is_loved_track: bool, + + /// If the playlist is collaborative or not + pub collaborative: bool, + + /// Nb tracks in the playlist + pub nb_tracks: u64, + + /// The number of playlist's fans + pub fans: u64, + + /// The url of the playlist on Deezer + pub link: String, + + /// The url of the playlist's cover. Add 'size' parameter to the url to change size. + /// Can be 'small', 'medium', 'big', 'xl' + pub picture: String, + + /// The url of the playlist's cover in size small. + pub picture_small: String, + + /// The url of the playlist's cover in size medium. + pub picture_medium: String, + + /// The url of the playlist's cover in size big. + pub picture_big: String, + + /// The url of the playlist's cover in size xl. + pub picture_xl: String, + + /// The checksum for the track list + pub checksum: String, + + /// The time when the playlist has been added + pub time_add: u64, + + /// The time when the playlist has been updated + pub time_mod: u64, + + /// The creator of the playlist. + pub creator: Creator, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct UserPlaylists +{ + pub data: Vec<UserPlaylist>, +} |