Skip to content

Commit

Permalink
Convert more numbers in API (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog authored Mar 1, 2024
1 parent f3ff008 commit 09f2482
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 42 deletions.
117 changes: 84 additions & 33 deletions crates/oxidize-common/src/models/spotify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,125 @@
//
// Copyright (c) 2015 Vincent Prouillet

pub mod album;
pub mod artist;
pub mod audio;
pub mod category;
pub mod context;
pub mod cud_result;
pub mod device;
pub mod image;
pub mod offset;
pub mod page;
pub mod playing;
pub mod playlist;
pub mod recommend;
pub mod search;
pub mod senum;
pub mod track;
pub mod user;

use std::fmt;

use serde::de::{Deserialize, Deserializer, Error};

use serde_json::Number;

trait NumberTrait: Copy + fmt::Display + TryFrom<u64> + TryFrom<i64> {
const MAX: Self;

fn from_f64(x: f64) -> Self;

fn as_f64(self) -> f64;
}

macro_rules! number {
($($t:ty),*) => {
$(
impl NumberTrait for $t {
const MAX: Self = <$t>::MAX;

fn from_f64(x: f64) -> Self {
x as Self
}

fn as_f64(self) -> f64 {
self as f64
}
}
)*
};
}

number!(u8, u16, u32, u64, i8, i16, i32, i64);

#[inline]
pub fn f64_to_u32(x: f64) -> Option<u32> {
let y = x as u32;
fn f64_to_number<T>(x: f64) -> Option<T>
where
T: NumberTrait,
{
let y = T::from_f64(x);

if y as f64 == x {
if T::as_f64(y) == x {
Some(y)
} else {
None
}
}

pub fn deserialize_option_u32<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
fn deserialize_option_number<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: NumberTrait,
{
let Some(number) = <Option<Number>>::deserialize(deserializer)? else {
return Ok(None);
};

Ok(Some(convert_number(number)?))
}

fn deserialize_number<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: NumberTrait,
{
let number = Number::deserialize(deserializer)?;
convert_number(number)
}

fn convert_number<T, E>(number: Number) -> Result<T, E>
where
T: NumberTrait,
E: Error,
{
if let Some(n) = number.as_u64() {
let Ok(n) = u32::try_from(n) else {
return Err(D::Error::custom(format_args!(
let Ok(n) = T::try_from(n) else {
return Err(E::custom(format_args!(
"Number {n} is out of numerical bounds 0-{}",
u32::MAX
T::MAX
)));
};

return Ok(Some(n));
return Ok(n);
}

if let Some(n) = number.as_i64() {
let Ok(n) = u32::try_from(n) else {
return Err(D::Error::custom(format_args!(
let Ok(n) = T::try_from(n) else {
return Err(E::custom(format_args!(
"Number {n} is out of numerical bounds 0-{}",
u32::MAX
T::MAX
)));
};

return Ok(Some(n));
return Ok(n);
}

if let Some(n) = number.as_f64().and_then(f64_to_u32) {
return Ok(Some(n));
if let Some(n) = number.as_f64().and_then(f64_to_number) {
return Ok(n);
}

Err(D::Error::custom(format_args!(
Err(E::custom(format_args!(
"Number {number} is not a valid u32"
)))
}

pub mod album;
pub mod artist;
pub mod audio;
pub mod category;
pub mod context;
pub mod cud_result;
pub mod device;
pub mod image;
pub mod offset;
pub mod page;
pub mod playing;
pub mod playlist;
pub mod recommend;
pub mod search;
pub mod senum;
pub mod track;
pub mod user;
13 changes: 9 additions & 4 deletions crates/oxidize-common/src/models/spotify/album.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,29 @@ use super::track::SimplifiedTrack;
/// Simplified Album Object
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SimplifiedAlbum {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album_group: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album_type: Option<String>,
pub artists: Vec<SimplifiedArtist>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub available_markets: Vec<String>,
pub external_urls: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub images: Vec<Image>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub release_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub release_date_precision: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub restrictions: Option<Restrictions>,
#[serde(rename = "type")]
pub _type: Type,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
}

Expand All @@ -58,6 +62,7 @@ pub struct FullAlbum {
pub id: String,
pub images: Vec<Image>,
pub name: String,
#[serde(deserialize_with = "super::deserialize_number")]
pub popularity: u32,
pub release_date: String,
pub release_date_precision: String,
Expand Down
4 changes: 4 additions & 0 deletions crates/oxidize-common/src/models/spotify/artist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ use super::senum::Type;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SimplifiedArtist {
pub external_urls: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: String,
#[serde(rename = "type")]
pub _type: Type,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
}

Expand All @@ -32,6 +35,7 @@ pub struct FullArtist {
pub id: String,
pub images: Vec<Image>,
pub name: String,
#[serde(deserialize_with = "super::deserialize_number")]
pub popularity: u32,
#[serde(rename = "type")]
pub _type: Type,
Expand Down
15 changes: 15 additions & 0 deletions crates/oxidize-common/src/models/spotify/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ pub struct AudioFeatures {
pub acousticness: f32,
pub analysis_url: String,
pub danceability: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub duration_ms: u32,
pub energy: f32,
pub id: String,
pub instrumentalness: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub key: i32,
pub liveness: f32,
pub loudness: f32,
pub mode: f32,
pub speechiness: f32,
pub tempo: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub time_signature: i32,
pub track_href: String,
#[serde(rename = "type")]
Expand Down Expand Up @@ -63,10 +66,12 @@ pub struct AudioAnalysisSection {
pub loudness: f32,
pub tempo: f32,
pub tempo_confidence: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub key: i32,
pub key_confidence: f32,
pub mode: f32,
pub mode_confidence: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub time_signature: i32,
pub time_signature_confidence: f32,
}
Expand All @@ -77,7 +82,9 @@ pub struct AudioAnalysisMeta {
pub analyzer_version: String,
pub platform: String,
pub detailed_status: String,
#[serde(deserialize_with = "super::deserialize_number")]
pub status_code: i32,
#[serde(deserialize_with = "super::deserialize_number")]
pub timestamp: u64,
pub analysis_time: f32,
pub input_process: String,
Expand All @@ -91,6 +98,7 @@ pub struct AudioAnalysisSegment {
pub loudness_start: f32,
pub loudness_max_time: f32,
pub loudness_max: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loudness_end: Option<f32>,
pub pitches: Vec<f32>,
pub timbre: Vec<f32>,
Expand All @@ -99,20 +107,27 @@ pub struct AudioAnalysisSegment {
///[audio analysis](https://developer.spotify.com/web-api/get-audio-analysis/)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AudioAnalysisTrack {
#[serde(deserialize_with = "super::deserialize_number")]
pub num_samples: u32,
pub duration: f32,
pub sample_md5: String,
#[serde(deserialize_with = "super::deserialize_number")]
pub offset_seconds: u32,
#[serde(deserialize_with = "super::deserialize_number")]
pub window_seconds: u32,
#[serde(deserialize_with = "super::deserialize_number")]
pub analysis_sample_rate: i32,
#[serde(deserialize_with = "super::deserialize_number")]
pub analysis_channels: u32,
pub end_of_fade_in: f32,
pub start_of_fade_out: f32,
pub loudness: f32,
pub tempo: f32,
pub tempo_confidence: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub time_signature: i32,
pub time_signature_confidence: f32,
#[serde(deserialize_with = "super::deserialize_number")]
pub key: u32,
pub key_confidence: f32,
pub mode: f32,
Expand Down
6 changes: 6 additions & 0 deletions crates/oxidize-common/src/models/spotify/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,24 @@ pub struct FullPlayingContext {
pub device: Device,
pub repeat_state: RepeatState,
pub shuffle_state: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<Context>,
#[serde(deserialize_with = "super::deserialize_number")]
pub timestamp: u64,
#[serde(default, deserialize_with = "super::deserialize_option_number")]
pub progress_ms: Option<u32>,
pub is_playing: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item: Option<FullTrack>,
}

///[get the users currently playing track](https://developer.spotify.com/web-api/get-the-users-currently-playing-track/)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SimplifiedPlayingContext {
pub context: Option<Context>,
#[serde(deserialize_with = "super::deserialize_number")]
pub timestamp: u64,
#[serde(default, deserialize_with = "super::deserialize_option_number")]
pub progress_ms: Option<u32>,
pub is_playing: bool,
pub item: Option<FullTrack>,
Expand Down
1 change: 1 addition & 0 deletions crates/oxidize-common/src/models/spotify/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Device {
pub name: String,
#[serde(rename = "type")]
pub _type: DeviceType,
#[serde(deserialize_with = "super::deserialize_number")]
pub volume_percent: u32,
}

Expand Down
6 changes: 2 additions & 4 deletions crates/oxidize-common/src/models/spotify/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

use serde::{Deserialize, Serialize};

use super::deserialize_option_u32;

///[image object](https://developer.spotify.com/web-api/object-model/#image-object)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Image {
#[serde(deserialize_with = "deserialize_option_u32")]
#[serde(default, deserialize_with = "super::deserialize_option_number")]
pub height: Option<u32>,
pub url: String,
#[serde(deserialize_with = "deserialize_option_u32")]
#[serde(default, deserialize_with = "super::deserialize_option_number")]
pub width: Option<u32>,
}

Expand Down
2 changes: 2 additions & 0 deletions crates/oxidize-common/src/models/spotify/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
///[offset object](https://developer.spotify.com/documentation/web-api/reference/player/start-a-users-playback/)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Offset {
#[serde(default, deserialize_with = "super::deserialize_option_number")]
pub position: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
}
Loading

0 comments on commit 09f2482

Please sign in to comment.