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

Revert the patch for Spotify API Bug after Spotify fixed the problem. #461

Merged
merged 4 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ rspotify-http = { path = "rspotify-http", version = "0.12.0", default-features =
async-stream = { version = "0.3.2", optional = true }
async-trait = { version = "0.1.51", optional = true }
base64 = "0.22.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono = { version = "0.4.35", features = ["serde"] }
dotenvy = { version = "0.15.0", optional = true }
futures = { version = "0.3.17", optional = true }

Expand Down
2 changes: 1 addition & 1 deletion examples/ureq/seek_track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn main() {
// This function requires the `cli` feature enabled.
spotify.prompt_for_token(&url).unwrap();

match spotify.seek_track(chrono::Duration::seconds(25), None) {
match spotify.seek_track(chrono::Duration::try_seconds(25).unwrap(), None) {
Ok(_) => println!("Change to previous playback successful"),
Err(_) => eprintln!("Change to previous playback failed"),
}
Expand Down
4 changes: 2 additions & 2 deletions examples/with_auto_reauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ async fn expire_token<S: BaseClient>(spotify: &S) {
let token = token.as_mut().expect("Token can't be empty as this point");
// In a regular case, the token would expire with time. Here we just do
// it manually.
let now = Utc::now().checked_sub_signed(Duration::seconds(10));
let now = Utc::now().checked_sub_signed(Duration::try_seconds(10).unwrap());
token.expires_at = now;
token.expires_in = Duration::seconds(0);
token.expires_in = Duration::try_seconds(0).unwrap();
// We also use a garbage access token to make sure it's actually
// refreshed.
token.access_token = "garbage".to_owned();
Expand Down
4 changes: 1 addition & 3 deletions rspotify-model/src/artist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};

use std::collections::HashMap;

use crate::{data_type_patcher::as_u32, ArtistId, CursorBasedPage, Followers, Image};
use crate::{ArtistId, CursorBasedPage, Followers, Image};

/// Simplified Artist Object
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
Expand All @@ -25,8 +25,6 @@ pub struct FullArtist {
pub id: ArtistId<'static>,
pub images: Vec<Image>,
pub name: String,
// TODO: remove this statement after Spotify fix the [issue](https://github.com/ramsayleung/rspotify/issues/452)
#[serde(deserialize_with = "as_u32")]
pub popularity: u32,
}

Expand Down
6 changes: 3 additions & 3 deletions rspotify-model/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
path::Path,
};

use chrono::{DateTime, Duration, Utc};
use chrono::{DateTime, Duration, TimeDelta, Utc};
use serde::{Deserialize, Serialize};

/// Spotify access token information
Expand Down Expand Up @@ -46,7 +46,7 @@ impl Default for Token {
fn default() -> Self {
Self {
access_token: String::new(),
expires_in: Duration::seconds(0),
expires_in: Duration::try_seconds(0).unwrap(),
expires_at: Some(Utc::now()),
refresh_token: None,
scopes: HashSet::new(),
Expand Down Expand Up @@ -81,7 +81,7 @@ impl Token {
#[must_use]
pub fn is_expired(&self) -> bool {
self.expires_at.map_or(true, |expiration| {
Utc::now() + Duration::seconds(10) >= expiration
Utc::now() + TimeDelta::try_seconds(10).unwrap() >= expiration
})
}

Expand Down
9 changes: 5 additions & 4 deletions rspotify-model/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! All objects related to context

use chrono::serde::ts_milliseconds;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Deserializer, Serialize};

use std::collections::HashMap;

use crate::{
custom_serde::{millisecond_timestamp, option_duration_ms},
CurrentlyPlayingType, Device, DisallowKey, PlayableItem, RepeatState, Type,
custom_serde::option_duration_ms, CurrentlyPlayingType, Device, DisallowKey, PlayableItem,
RepeatState, Type,
};

/// Context object
Expand All @@ -25,7 +26,7 @@ pub struct Context {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CurrentlyPlayingContext {
pub context: Option<Context>,
#[serde(with = "millisecond_timestamp")]
#[serde(with = "ts_milliseconds")]
pub timestamp: DateTime<Utc>,
#[serde(default)]
#[serde(with = "option_duration_ms", rename = "progress_ms")]
Expand All @@ -42,7 +43,7 @@ pub struct CurrentPlaybackContext {
pub repeat_state: RepeatState,
pub shuffle_state: bool,
pub context: Option<Context>,
#[serde(with = "millisecond_timestamp")]
#[serde(with = "ts_milliseconds")]
pub timestamp: DateTime<Utc>,
#[serde(default)]
#[serde(with = "option_duration_ms", rename = "progress_ms")]
Expand Down
79 changes: 23 additions & 56 deletions rspotify-model/src/custom_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,32 @@ pub mod duration_ms {
where
E: de::Error,
{
Ok(Duration::milliseconds(v))
Duration::try_milliseconds(v).ok_or_else(|| {
E::invalid_value(
serde::de::Unexpected::Signed(v),
&"an invalid duration in milliseconds",
)
})
}

// JSON deserializer calls visit_u64 for non-negative intgers
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
i64::try_from(v)
.map(Duration::milliseconds)
.map_err(E::custom)
match i64::try_from(v) {
Ok(val) => Duration::try_milliseconds(val).ok_or_else(|| {
E::invalid_value(
serde::de::Unexpected::Signed(val),
&"a valid duration in
milliseconds",
)
}),
Err(_) => Err(E::custom(format!(
"Conversion error: u64 to i64 conversion failed for value {}",
v
))),
}
}
}

Expand All @@ -49,57 +64,6 @@ pub mod duration_ms {
}
}

pub mod millisecond_timestamp {
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{de, Serializer};
use std::fmt;

/// Vistor to help deserialize unix millisecond timestamp to
/// `chrono::DateTime`.
struct DateTimeVisitor;

impl<'de> de::Visitor<'de> for DateTimeVisitor {
type Value = DateTime<Utc>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"an unix millisecond timestamp represents DataTime<UTC>"
)
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
let second = (v - v % 1000) / 1000;
let nanosecond = ((v % 1000) * 1_000_000) as u32;
// The maximum value of i64 is large enough to hold milliseconds,
// so it would be safe to convert it i64.
match NaiveDateTime::from_timestamp_opt(second as i64, nanosecond) {
Some(ndt) => Ok(DateTime::<Utc>::from_naive_utc_and_offset(ndt, Utc)),
None => Err(E::custom(format!("v is invalid second: {v}"))),
}
}
}

/// Deserialize Unix millisecond timestamp to `DateTime<Utc>`
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_u64(DateTimeVisitor)
}

/// Serialize `DateTime<Utc>` to Unix millisecond timestamp
pub fn serialize<S>(x: &DateTime<Utc>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_i64(x.timestamp_millis())
}
}

pub mod option_duration_ms {
use crate::custom_serde::duration_ms;
use chrono::Duration;
Expand Down Expand Up @@ -202,7 +166,10 @@ pub mod duration_second {
D: de::Deserializer<'de>,
{
let duration: i64 = Deserialize::deserialize(d)?;
Ok(Duration::seconds(duration))
Duration::try_seconds(duration).ok_or(serde::de::Error::invalid_value(
serde::de::Unexpected::Signed(duration),
&"an invalid duration in seconds",
))
}

/// Serialize `chrono::Duration` to seconds (represented as u64)
Expand Down
28 changes: 0 additions & 28 deletions rspotify-model/src/data_type_patcher.rs

This file was deleted.

6 changes: 0 additions & 6 deletions rspotify-model/src/image.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
//! Image object

pub use crate::data_type_patcher::as_some_u32;

use serde::{Deserialize, Serialize};

/// Image object
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Image {
// TODO: remove this statement after Spotify fix the [issue](https://github.com/ramsayleung/rspotify/issues/452)
#[serde(deserialize_with = "as_some_u32")]
pub height: Option<u32>,
pub url: String,
// TODO: remove this statement after Spotify fix the [issue](https://github.com/ramsayleung/rspotify/issues/452)
#[serde(deserialize_with = "as_some_u32")]
pub width: Option<u32>,
}
9 changes: 3 additions & 6 deletions rspotify-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pub mod auth;
pub mod category;
pub mod context;
pub(crate) mod custom_serde;
pub mod data_type_patcher;
pub mod device;
pub mod enums;
pub mod error;
Expand All @@ -25,9 +24,9 @@ pub mod track;
pub mod user;

pub use {
album::*, artist::*, audio::*, auth::*, category::*, context::*, data_type_patcher::as_u32,
device::*, enums::*, error::*, idtypes::*, image::*, offset::*, page::*, playing::*,
playlist::*, recommend::*, search::*, show::*, track::*, user::*,
album::*, artist::*, audio::*, auth::*, category::*, context::*, device::*, enums::*, error::*,
idtypes::*, image::*, offset::*, page::*, playing::*, playlist::*, recommend::*, search::*,
show::*, track::*, user::*,
};

use serde::{Deserialize, Serialize};
Expand All @@ -37,8 +36,6 @@ use serde::{Deserialize, Serialize};
pub struct Followers {
// This field will always set to null, as the Web API does not support it at the moment.
// pub href: Option<String>,
// TODO: remove this statement after Spotify fix the [issue](https://github.com/ramsayleung/rspotify/issues/452)
#[serde(deserialize_with = "as_u32")]
pub total: u32,
}

Expand Down
2 changes: 1 addition & 1 deletion src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ mod test {
async fn test_auth_headers() {
let tok = Token {
access_token: "test-access_token".to_string(),
expires_in: Duration::seconds(1),
expires_in: Duration::try_seconds(1).unwrap(),
expires_at: Some(Utc::now()),
scopes: scopes!("playlist-read-private"),
refresh_token: Some("...".to_string()),
Expand Down
28 changes: 11 additions & 17 deletions tests/test_models.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use chrono::{DateTime, Duration};
use rspotify::model::*;
use serde::de::DeserializeOwned;
use wasm_bindgen_test::*;
Expand Down Expand Up @@ -52,7 +52,7 @@ fn test_simplified_track() {

"#;
let track: SimplifiedTrack = deserialize(json_str);
let duration = Duration::milliseconds(276773);
let duration = Duration::try_milliseconds(276773).unwrap();
assert_eq!(track.duration, duration);
}

Expand Down Expand Up @@ -200,7 +200,7 @@ fn test_simplified_episode() {
simplified_episode.release_date_precision,
DatePrecision::Day
);
let duration = Duration::milliseconds(2685023);
let duration = Duration::try_milliseconds(2685023).unwrap();
assert_eq!(simplified_episode.duration, duration);
}

Expand Down Expand Up @@ -269,7 +269,7 @@ fn test_full_episode() {
"#;
let full_episode: FullEpisode = deserialize(json_str);
assert_eq!(full_episode.release_date_precision, DatePrecision::Day);
let duration = Duration::milliseconds(1502795);
let duration = Duration::try_milliseconds(1502795).unwrap();
assert_eq!(full_episode.duration, duration);
}

Expand Down Expand Up @@ -531,7 +531,7 @@ fn test_audio_features() {
}
"#;
let audio_features: AudioFeatures = deserialize(json);
let duration = Duration::milliseconds(255349);
let duration = Duration::try_milliseconds(255349).unwrap();
assert_eq!(audio_features.duration, duration);
}

Expand Down Expand Up @@ -611,7 +611,7 @@ fn test_full_track() {
}
"#;
let full_track: FullTrack = deserialize(json);
let duration = Duration::milliseconds(207959);
let duration = Duration::try_milliseconds(207959).unwrap();
assert_eq!(full_track.duration, duration);
}

Expand All @@ -625,7 +625,7 @@ fn test_resume_point() {
}
"#;
let resume_point: ResumePoint = deserialize(json);
let duration = Duration::milliseconds(423432);
let duration = Duration::try_milliseconds(423432).unwrap();
assert_eq!(resume_point.resume_position, duration);
}

Expand All @@ -639,7 +639,7 @@ fn test_resume_point_negative() {
}
"#;
let resume_point: ResumePoint = deserialize(json);
let duration = Duration::milliseconds(-1000);
let duration = Duration::try_milliseconds(-1000).unwrap();
assert_eq!(resume_point.resume_position, duration);
}

Expand Down Expand Up @@ -748,13 +748,10 @@ fn test_currently_playing_context() {
let timestamp = 1607769168429;
let second: i64 = (timestamp - timestamp % 1000) / 1000;
let nanosecond = (timestamp % 1000) * 1000000;
let dt = DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(second, nanosecond as u32).unwrap(),
Utc,
);
let dt = DateTime::from_timestamp(second, nanosecond as u32).unwrap();
assert_eq!(currently_playing_context.timestamp, dt);

let duration = Duration::milliseconds(22270);
let duration = Duration::try_milliseconds(22270).unwrap();
assert_eq!(currently_playing_context.progress, Some(duration));
}

Expand Down Expand Up @@ -864,10 +861,7 @@ fn test_current_playback_context() {
let timestamp = 1607774342714;
let second: i64 = (timestamp - timestamp % 1000) / 1000;
let nanosecond = (timestamp % 1000) * 1000000;
let dt = DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(second, nanosecond as u32).unwrap(),
Utc,
);
let dt = DateTime::from_timestamp(second, nanosecond as u32).unwrap();
assert_eq!(current_playback_context.timestamp, dt);
assert!(current_playback_context.progress.is_none());
}
Expand Down
Loading
Loading