Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module splitting #31

Merged
merged 13 commits into from
Feb 19, 2025
700 changes: 0 additions & 700 deletions src/raw/response.rs

This file was deleted.

3 changes: 3 additions & 0 deletions src/raw/response/art/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/art.html
// Currently no api responses have been made for this page.
// The header comments will be made as needed
234 changes: 234 additions & 0 deletions src/raw/response/core/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
use std::collections::HashMap;

use serde::Serialize;

use super::response_type;
use crate::raw::response::Deserialize;

// --------- GET /1/search/users/
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-search-users-

// TODO

// --------- POST /1/submit-listens
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-submit-listens

response_type! {
/// Response type for [`Client::submit_listens`](super::Client::submit_listens).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct SubmitListensResponse {
pub status: String,
}
}

// --------- GET /1/user/(user_name)/listens
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listens

response_type! {
/// Response type for [`Client::user_listens`](super::Client::user_listens).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensResponse {
pub payload: UserListensPayload,
}
}

/// Type of the [`UserListensResponse::payload`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensPayload {
pub count: u64,
pub latest_listen_ts: i64,
pub oldest_listen_ts: i64,
pub user_id: String,
pub listens: Vec<UserListensListen>,
}

/// Type of the [`UserListensPayload::listens`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensListen {
pub user_name: String,
pub inserted_at: i64,
pub listened_at: i64,
pub recording_msid: String,
pub track_metadata: UserListensTrackMetadata,
}

/// Type of the [`UserListensListen::track_metadata`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensTrackMetadata {
pub artist_name: String,
pub track_name: String,
pub release_name: Option<String>,
pub additional_info: HashMap<String, serde_json::Value>,
pub mbid_mapping: Option<UserListensMBIDMapping>,
}

/// Type of the [`UserListensTrackMetadata::mbid_mapping`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensMBIDMapping {
pub artist_mbids: Option<Vec<String>>,
pub artists: Option<Vec<UserListensMappingArtist>>,
pub recording_mbid: String,
pub recording_name: Option<String>,
pub caa_id: Option<u64>,
pub caa_release_mbid: Option<String>,
pub release_mbid: Option<String>,
}

/// Type of the [`UserListensMBIDMapping::artists`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListensMappingArtist {
pub artist_mbid: String,
pub artist_credit_name: String,
pub join_phrase: String,
}

// --------- GET /1/user/(user_name)/listen-count
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listen-count

response_type! {
/// Response type for [`Client::user_listen_count`](super::Client::user_listen_count).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListenCountResponse {
pub payload: UserListenCountPayload,
}
}

/// Type of the [`UserListenCountResponse::payload`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserListenCountPayload {
pub count: u64,
}

// --------- GET /1/user/(user_name)/playing-now
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-playing-now

response_type! {
/// Response type for [`Client::user_playing_now`](super::Client::user_playing_now).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserPlayingNowResponse {
pub payload: UserPlayingNowPayload,
}
}

/// Type of the [`UserPlayingNowResponse::payload`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserPlayingNowPayload {
pub count: u8,
pub user_id: String,
pub listens: Vec<UserPlayingNowListen>,
pub playing_now: bool,
}

/// Type of the [`UserPlayingNowPayload::listens`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserPlayingNowListen {
pub track_metadata: UserPlayingNowTrackMetadata,
pub playing_now: bool,
}

/// Type of the [`UserPlayingNowListen::track_metadata`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UserPlayingNowTrackMetadata {
pub artist_name: String,
pub track_name: String,
pub release_name: Option<String>,
pub additional_info: HashMap<String, serde_json::Value>,
}

// --------- GET /1/user/(user_name)/similar-users
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-similar-users

response_type! {
/// Response type for [`Client::user_similar_users`](super::Client::user_similar_users).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct UserSimilarUsersResponse {
pub payload: Vec<UserSimilarUsersPayload>,
}
}

/// Type of the [`UserSimilarUsersResponse::payload`] field.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct UserSimilarUsersPayload {
pub user_name: String,
pub similarity: f64,
}

// --------- GET /1/user/(user_name)/similar-to/(other_user_name)
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-similar-to-(other_user_name)

response_type! {
#[derive(Debug, Deserialize, Serialize)]
pub struct UserSimilarToResponse {
pub user_name: String,
pub similarity: f64,
}
}

// --------- GET /1/validate-token
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-validate-token

response_type! {
/// Response type for [`Client::validate_token`](super::Client::validate_token).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct ValidateTokenResponse {
pub code: u16,
pub message: String,

pub valid: bool,
pub user_name: Option<String>,
}
}

// --------- POST /1/delete-listen
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-delete-listen

response_type! {
/// Response type for [`Client::delete_listen`](super::Client::delete_listen).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct DeleteListenResponse {
pub status: String,
}
}

// --------- GET /1/user/(playlist_user_name)/playlists/recommendations
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(playlist_user_name)-playlists-recommendations

// TODO

// --------- GET /1/user/(user_name)/services
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-services

// TODO

// --------- GET /1/lb-radio/tags
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-lb-radio-tags

// TODO

// --------- GET /1/lb-radio/artist/(seed_artist_mbid)
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-lb-radio-artist-(seed_artist_mbid)

// TODO

// --------- GET /1/latest-import
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-latest-import

response_type! {
/// Response type for [`Client::get_latest_import`](super::Client::get_latest_import).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct GetLatestImportResponse {
pub latest_import: i64,
pub musicbrainz_id: String,
}
}

// --------- POST /1/latest-import
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-latest-import

response_type! {
/// Response type for [`Client::update_latest_import`](super::Client::update_latest_import).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct UpdateLatestImportResponse {
pub status: String,
}
}
3 changes: 3 additions & 0 deletions src/raw/response/metadata/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/metadata.html
// Currently no api responses have been made for this page.
// The header comments will be made as needed
19 changes: 19 additions & 0 deletions src/raw/response/misc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use serde::Deserialize;
use serde::Serialize;

use crate::raw::response::response_type;

// --------- GET /1/status/get-dump-info
// https://listenbrainz.readthedocs.io/en/latest/users/api/misc.html#get--1-status-get-dump-info

response_type! {
/// Response type for [`Client::status_get_dump_info`](super::Client::status_get_dump_info).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct StatusGetDumpInfoResponse {
pub code: u16,
pub message: String,

pub id: i64,
pub timestamp: String,
}
}
135 changes: 135 additions & 0 deletions src/raw/response/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! Low-level response data models.
//!
//! Every response type has the `rate_limit` field, which contains rate limiting
//! information. See the documentation of the [`RateLimit`] type for more
//! details.

#![allow(missing_docs)]

use attohttpc::Response;
use serde::de::DeserializeOwned;
use serde::Deserialize;

use crate::Error;

// Sub modules
pub mod art;
pub mod core;
pub mod metadata;
pub mod misc;
pub mod playlists;
pub mod popularity;
pub mod recommendations;
pub mod recordings;
pub mod settings;
pub mod social;
pub mod statistics;

// Reexport of the sub modules
pub use crate::raw::response::art::*;
pub use crate::raw::response::core::*;
pub use crate::raw::response::metadata::*;
pub use crate::raw::response::misc::*;
pub use crate::raw::response::playlists::*;
pub use crate::raw::response::popularity::*;
pub use crate::raw::response::recommendations::*;
pub use crate::raw::response::recordings::*;
pub use crate::raw::response::settings::*;
pub use crate::raw::response::social::*;
pub use crate::raw::response::statistics::*;

/// Contains rate limiting information.
///
/// ListenBrainz API rate limiting is described in the [API docs].
/// Prefer using the [`RateLimit::reset_in`] field over [`RateLimit::reset`],
/// as the former is resilient against clients with incorrect clocks.
///
/// [API docs]: https://listenbrainz.readthedocs.io/en/production/dev/api/#rate-limiting
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RateLimit {
pub limit: u64,
pub remaining: u64,
pub reset_in: u64,
pub reset: i64,
}

impl RateLimit {
/// Extract rate limiting information from the `X-RateLimit-` headers.
/// Only returns `Some` if all fields are present and valid.
fn from_headers(response: &Response) -> Option<Self> {
let headers = response.headers();

let limit = headers
.get("X-RateLimit-Limit")?
.to_str()
.ok()?
.parse()
.ok()?;
let remaining = headers
.get("X-RateLimit-Remaining")?
.to_str()
.ok()?
.parse()
.ok()?;
let reset_in = headers
.get("X-RateLimit-Reset-In")?
.to_str()
.ok()?
.parse()
.ok()?;
let reset = headers
.get("X-RateLimit-Reset")?
.to_str()
.ok()?
.parse()
.ok()?;

Some(Self {
limit,
remaining,
reset_in,
reset,
})
}
}

/// Internal trait for response types.
/// Allows converting the response type from an `attohttpc::Response`,
/// by deserializing the body into the response type and then
/// adding the `rate_limit` field from headers.
pub(crate) trait ResponseType: DeserializeOwned {
fn from_response(response: Response) -> Result<Self, Error>;
}

/// Internal macro for response types.
/// Wraps the definition of a response type, adds the `rate_limit` field,
/// and implements the `ResponseType` trait.
macro_rules! response_type {
(
$(#[$meta:meta])*
pub struct $name:ident {
$(pub $field:ident: $field_ty:ty),*
$(,)?
}
) => {
$(#[$meta])*
pub struct $name {
#[serde(skip)]
pub rate_limit: Option<crate::raw::response::RateLimit>,
$(pub $field: $field_ty),*
}

impl crate::raw::response::ResponseType for $name {
fn from_response(response: crate::raw::response::Response) -> Result<Self, crate::raw::response::Error> {
let response = crate::raw::response::Error::try_from_error_response(response)?;
let rate_limit = crate::raw::response::RateLimit::from_headers(&response);
let mut result: Self = response.json()?;
result.rate_limit = rate_limit;
Ok(result)
}
}
}
}

// Let the childrens access the macro
pub(super) use response_type;
Loading