Skip to content

Commit

Permalink
Merge pull request #251 from wernerdweight/master
Browse files Browse the repository at this point in the history
fix: unmarshal numeric fields regardless of them being int or float
  • Loading branch information
strideynet authored Mar 18, 2024
2 parents 24bcfe4 + 4c75845 commit ce25abf
Show file tree
Hide file tree
Showing 18 changed files with 807 additions and 57 deletions.
2 changes: 1 addition & 1 deletion album.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ type FullAlbum struct {
// The popularity of the album, represented as an integer between 0 and 100,
// with 100 being the most popular. Popularity of an album is calculated
// from the popularity of the album's individual tracks.
Popularity int `json:"popularity"`
Popularity Numeric `json:"popularity"`
Tracks SimpleTrackPage `json:"tracks"`
ExternalIDs map[string]string `json:"external_ids"`
}
Expand Down
2 changes: 1 addition & 1 deletion artist.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type FullArtist struct {
SimpleArtist
// The popularity of the artist, expressed as an integer between 0 and 100.
// The artist's popularity is calculated from the popularity of the artist's tracks.
Popularity int `json:"popularity"`
Popularity Numeric `json:"popularity"`
// A list of genres the artist is associated with. For example, "Prog Rock"
// or "Post-Grunge". If not yet classified, the slice is empty.
Genres []string `json:"genres"`
Expand Down
32 changes: 26 additions & 6 deletions artist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ const albumsResponse = `
"href" : "https://api.spotify.com/v1/albums/1WXM7DYQRT7QX8AKBJRfK9",
"id" : "1WXM7DYQRT7QX8AKBJRfK9",
"images" : [ {
"height" : 640,
"height" : 640.0,
"url" : "https://i.scdn.co/image/590dbe5504d2898c120b942bee2b699404783896",
"width" : 640
"width" : 640.0
}, {
"height" : 300,
"height" : 300.0,
"url" : "https://i.scdn.co/image/9a4db24b1930e8683b4dfd19c7bd2a40672c6718",
"width" : 300
"width" : 300.0
}, {
"height" : 64,
"height" : 64.0,
"url" : "https://i.scdn.co/image/d5cfc167e03ed328ae7dfa9b56d3628d81b6831b",
"width" : 64
"width" : 64.0
} ],
"name" : "The Days / Nights",
"type" : "album",
Expand Down Expand Up @@ -136,6 +136,26 @@ func TestRelatedArtists(t *testing.T) {
}
}

func TestRelatedArtistsWithFloats(t *testing.T) {
client, server := testClientFile(http.StatusOK, "test_data/related_artists_with_floats.txt")
defer server.Close()

artists, err := client.GetRelatedArtists(context.Background(), ID("43ZHCT0cAZBISjO8DG9PnE"))
if err != nil {
t.Fatal(err)
}
if count := len(artists); count != 20 {
t.Fatalf("Got %d artists, wanted 20\n", count)
}
a2 := artists[2]
if a2.Name != "Carl Perkins" {
t.Error("Expected Carl Perkins, got ", a2.Name)
}
if a2.Popularity != 54 {
t.Errorf("Expected popularity 54, got %d\n", a2.Popularity)
}
}

func TestArtistAlbumsFiltered(t *testing.T) {
client, server := testClientString(http.StatusOK, albumsResponse)
defer server.Close()
Expand Down
10 changes: 5 additions & 5 deletions audio_analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type Section struct {
KeyConfidence float64 `json:"key_confidence"`
Mode Mode `json:"mode"`
ModeConfidence float64 `json:"mode_confidence"`
TimeSignature int `json:"time_signature"`
TimeSignature Numeric `json:"time_signature"`
TimeSignatureConfidence float64 `json:"time_signature_confidence"`
}

Expand All @@ -70,16 +70,16 @@ type AnalysisTrack struct {
NumSamples int64 `json:"num_samples"`
Duration float64 `json:"duration"`
SampleMD5 string `json:"sample_md5"`
OffsetSeconds int `json:"offset_seconds"`
WindowSeconds int `json:"window_seconds"`
OffsetSeconds Numeric `json:"offset_seconds"`
WindowSeconds Numeric `json:"window_seconds"`
AnalysisSampleRate int64 `json:"analysis_sample_rate"`
AnalysisChannels int `json:"analysis_channels"`
AnalysisChannels Numeric `json:"analysis_channels"`
EndOfFadeIn float64 `json:"end_of_fade_in"`
StartOfFadeOut float64 `json:"start_of_fade_out"`
Loudness float64 `json:"loudness"`
Tempo float64 `json:"tempo"`
TempoConfidence float64 `json:"tempo_confidence"`
TimeSignature int `json:"time_signature"`
TimeSignature Numeric `json:"time_signature"`
TimeSignatureConfidence float64 `json:"time_signature_confidence"`
Key Key `json:"key"`
KeyConfidence float64 `json:"key_confidence"`
Expand Down
8 changes: 4 additions & 4 deletions audio_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type AudioFeatures struct {
// and 1.0 is most danceable.
Danceability float32 `json:"danceability"`
// The length of the track in milliseconds.
Duration int `json:"duration_ms"`
Duration Numeric `json:"duration_ms"`
// Energy is a measure from 0.0 to 1.0 and represents a perceptual measure
// of intensity and activity. Typically, energetic tracks feel fast, loud,
// and noisy.
Expand All @@ -39,7 +39,7 @@ type AudioFeatures struct {
Instrumentalness float32 `json:"instrumentalness"`
// The key the track is in. Integers map to pitches using standard Pitch Class notation
// (https://en.wikipedia.org/wiki/Pitch_class).
Key int `json:"key"`
Key Numeric `json:"key"`
// Detects the presence of an audience in the recording. Higher liveness
// values represent an increased probability that the track was performed live.
// A value above 0.8 provides strong likelihood that the track is live.
Expand All @@ -49,7 +49,7 @@ type AudioFeatures struct {
// loudness of tracks. Typical values range between -60 and 0 dB.
Loudness float32 `json:"loudness"`
// Mode indicates the modality (major or minor) of a track.
Mode int `json:"mode"`
Mode Numeric `json:"mode"`
// Detects the presence of spoken words in a track. The more exclusively
// speech-like the recording, the closer to 1.0 the speechiness will be.
// Values above 0.66 describe tracks that are probably made entirely of
Expand All @@ -61,7 +61,7 @@ type AudioFeatures struct {
Tempo float32 `json:"tempo"`
// An estimated overall time signature of a track. The time signature (meter)
// is a notational convention to specify how many beats are in each bar (or measure).
TimeSignature int `json:"time_signature"`
TimeSignature Numeric `json:"time_signature"`
// A link to the Web API endpoint providing full details of the track.
TrackURL string `json:"track_href"`
// The Spotify URI for the track.
Expand Down
4 changes: 2 additions & 2 deletions cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ type cursorPage struct {
Endpoint string `json:"href"`
// The maximum number of items returned, as set in the query
// (or default value if unset).
Limit int `json:"limit"`
Limit Numeric `json:"limit"`
// The URL to the next set of items.
Next string `json:"next"`
// The total number of items available to return.
Total int `json:"total"`
Total Numeric `json:"total"`
// The cursor used to find the next set of items.
Cursor Cursor `json:"cursors"`
}
Expand Down
6 changes: 3 additions & 3 deletions page.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ type basePage struct {
Endpoint string `json:"href"`
// The maximum number of items in the response, as set
// in the query (or default value if unset).
Limit int `json:"limit"`
Limit Numeric `json:"limit"`
// The offset of the items returned, as set in the query
// (or default value if unset).
Offset int `json:"offset"`
Offset Numeric `json:"offset"`
// The total number of items available to return.
Total int `json:"total"`
Total Numeric `json:"total"`
// The URL to the next page of items (if available).
Next string `json:"next"`
// The URL to the previous page of items (if available).
Expand Down
4 changes: 2 additions & 2 deletions page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestClient_NextPage(t *testing.T) {
assert.Equal(t, tt.ExpectedPath != "", wasCalled)
if tt.Err == nil {
assert.NoError(t, err)
assert.Equal(t, 100, tt.Input.Total) // value should be from original 600
assert.Equal(t, 100, int(tt.Input.Total)) // value should be from original 600
} else {
assert.EqualError(t, err, tt.Err.Error())
}
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestClient_PreviousPage(t *testing.T) {
assert.Equal(t, tt.ExpectedPath != "", wasCalled)
if tt.Err == nil {
assert.NoError(t, err)
assert.Equal(t, 100, tt.Input.Total) // value should be from original 600
assert.Equal(t, 100, int(tt.Input.Total)) // value should be from original 600
} else {
assert.EqualError(t, err, tt.Err.Error())
}
Expand Down
8 changes: 4 additions & 4 deletions player.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type PlayerDevice struct {
// Type of device, such as "Computer", "Smartphone" or "Speaker".
Type string `json:"type"`
// Volume The current volume in percent.
Volume int `json:"volume_percent"`
Volume Numeric `json:"volume_percent"`
}

// PlayerState contains information about the current playback.
Expand Down Expand Up @@ -57,7 +57,7 @@ type CurrentlyPlaying struct {
// PlaybackContext current context
PlaybackContext PlaybackContext `json:"context"`
// Progress into the currently playing track.
Progress int `json:"progress_ms"`
Progress Numeric `json:"progress_ms"`
// Playing If something is currently playing.
Playing bool `json:"is_playing"`
// The currently playing track. Can be null.
Expand Down Expand Up @@ -110,7 +110,7 @@ type PlayOptions struct {
// Must be a positive number. Passing in a position that is greater
// than the length of the track will cause the player to start playing the next song.
// Defaults to 0, starting a track from the beginning.
PositionMs int `json:"position_ms,omitempty"`
PositionMs Numeric `json:"position_ms,omitempty"`
}

// RecentlyPlayedOptions describes options for the recently-played request. All
Expand All @@ -120,7 +120,7 @@ type PlayOptions struct {
type RecentlyPlayedOptions struct {
// Limit is the maximum number of items to return. Must be no greater than
// fifty.
Limit int
Limit Numeric

// AfterEpochMs is a Unix epoch in milliseconds that describes a time after
// which to return songs.
Expand Down
8 changes: 4 additions & 4 deletions playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type PlaylistTracks struct {
// the playlist's tracks can be retrieved.
Endpoint string `json:"href"`
// The total number of tracks in the playlist.
Total uint `json:"total"`
Total Numeric `json:"total"`
}

// SimplePlaylist contains basic info about a Spotify playlist.
Expand Down Expand Up @@ -621,14 +621,14 @@ func (c *Client) UserFollowsPlaylist(ctx context.Context, playlistID ID, userIDs
type PlaylistReorderOptions struct {
// The position of the first track to be reordered.
// This field is required.
RangeStart int `json:"range_start"`
RangeStart Numeric `json:"range_start"`
// The amount of tracks to be reordered. This field is optional. If
// you don't set it, the value 1 will be used.
RangeLength int `json:"range_length,omitempty"`
RangeLength Numeric `json:"range_length,omitempty"`
// The position where the tracks should be inserted. To reorder the
// tracks to the end of the playlist, simply set this to the position
// after the last track. This field is required.
InsertBefore int `json:"insert_before"`
InsertBefore Numeric `json:"insert_before"`
// The playlist's snapshot ID against which you wish to make the changes.
// This field is optional.
SnapshotID string `json:"snapshot_id,omitempty"`
Expand Down
12 changes: 6 additions & 6 deletions recommendation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ type Recommendations struct {
// RecommendationSeed represents a recommendation seed after
// being processed by the Spotify API
type RecommendationSeed struct {
AfterFilteringSize int `json:"afterFilteringSize"`
AfterRelinkingSize int `json:"afterRelinkingSize"`
Endpoint string `json:"href"`
ID ID `json:"id"`
InitialPoolSize int `json:"initialPoolSize"`
Type string `json:"type"`
AfterFilteringSize Numeric `json:"afterFilteringSize"`
AfterRelinkingSize Numeric `json:"afterRelinkingSize"`
Endpoint string `json:"href"`
ID ID `json:"id"`
InitialPoolSize Numeric `json:"initialPoolSize"`
Type string `json:"type"`
}

// MaxNumberOfSeeds allowed by Spotify for a recommendation request
Expand Down
4 changes: 2 additions & 2 deletions show.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type EpisodePage struct {
Description string `json:"description"`

// The episode length in milliseconds.
Duration_ms int `json:"duration_ms"`
Duration_ms Numeric `json:"duration_ms"`

// Whether or not the episode has explicit content
// (true = yes it does; false = no it does not OR unknown).
Expand Down Expand Up @@ -147,7 +147,7 @@ type ResumePointObject struct {
FullyPlayed bool `json:"fully_played"`

// The user’s most recent position in the episode in milliseconds.
ResumePositionMs int `json:"resume_position_ms"`
ResumePositionMs Numeric `json:"resume_position_ms"`
}

// ReleaseDateTime converts the show's ReleaseDate to a time.TimeValue.
Expand Down
21 changes: 17 additions & 4 deletions spotify.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,24 @@ func (id *ID) String() string {
return string(*id)
}

// Numeric is a convenience type for handling numbers sent as either integers or floats.
type Numeric int

// UnmarshalJSON unmarshals a JSON number (float or int) into the Numeric type.
func (n *Numeric) UnmarshalJSON(data []byte) error {
var f float64
if err := json.Unmarshal(data, &f); err != nil {
return err
}
*n = Numeric(int(f))
return nil
}

// Followers contains information about the number of people following a
// particular artist or playlist.
type Followers struct {
// The total number of followers.
Count uint `json:"total"`
Count Numeric `json:"total"`
// A link to the Web API endpoint providing full details of the followers,
// or the empty string if this data is not available.
Endpoint string `json:"href"`
Expand All @@ -115,9 +128,9 @@ type Followers struct {
// Image identifies an image associated with an item.
type Image struct {
// The image height, in pixels.
Height int `json:"height"`
Height Numeric `json:"height"`
// The image width, in pixels.
Width int `json:"width"`
Width Numeric `json:"width"`
// The source URL of the image.
URL string `json:"url"`
}
Expand Down Expand Up @@ -222,7 +235,7 @@ func (c *Client) execute(req *http.Request, result interface{}, needsStatus ...i
// If the context is cancelled, return the original error
case <-time.After(retryDuration(resp)):
continue
}
}
}
if resp.StatusCode == http.StatusNoContent {
return nil
Expand Down
64 changes: 64 additions & 0 deletions test_data/find_track_with_floats.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"album" : {
"album_type" : "single",
"available_markets" : [ "AD", "AR", "AT", "AU", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IT", "LI", "LT", "LV", "MC", "MT", "MX", "MY", "NI", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
"external_urls" : {
"spotify" : "https://open.spotify.com/album/3X33e7UII5loqrEgauOKEC"
},
"href" : "https://api.spotify.com/v1/albums/3X33e7UII5loqrEgauOKEC",
"id" : "3X33e7UII5loqrEgauOKEC",
"images" : [ {
"height" : 640.0,
"url" : "https://i.scdn.co/image/c171113a197828a6ee8017d1ede2e78c9a7df654",
"width" : 640.0
}, {
"height" : 300.0,
"url" : "https://i.scdn.co/image/de50cbd4f0e62be8d4ffd11c7d6f3c59d964bdb6",
"width" : 300.0
}, {
"height" : 64.0,
"url" : "https://i.scdn.co/image/5a18558e39d542ec9d71345daafe89d5862aa67a",
"width" : 64.0
} ],
"name" : "Timber",
"type" : "album",
"uri" : "spotify:album:3X33e7UII5loqrEgauOKEC"
},
"artists" : [ {
"external_urls" : {
"spotify" : "https://open.spotify.com/artist/0TnOYISbd1XYRBk9myaseg"
},
"href" : "https://api.spotify.com/v1/artists/0TnOYISbd1XYRBk9myaseg",
"id" : "0TnOYISbd1XYRBk9myaseg",
"name" : "Pitbull",
"type" : "artist",
"uri" : "spotify:artist:0TnOYISbd1XYRBk9myaseg"
}, {
"external_urls" : {
"spotify" : "https://open.spotify.com/artist/6LqNN22kT3074XbTVUrhzX"
},
"href" : "https://api.spotify.com/v1/artists/6LqNN22kT3074XbTVUrhzX",
"id" : "6LqNN22kT3074XbTVUrhzX",
"name" : "Ke$ha",
"type" : "artist",
"uri" : "spotify:artist:6LqNN22kT3074XbTVUrhzX"
} ],
"available_markets" : [ "AD", "AR", "AT", "AU", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IT", "LI", "LT", "LV", "MC", "MT", "MX", "MY", "NI", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
"disc_number" : 1.0,
"duration_ms" : 204053.0,
"explicit" : false,
"external_ids" : {
"isrc" : "USRC11301695"
},
"external_urls" : {
"spotify" : "https://open.spotify.com/track/1zHlj4dQ8ZAtrayhuDDmkY"
},
"href" : "https://api.spotify.com/v1/tracks/1zHlj4dQ8ZAtrayhuDDmkY",
"id" : "1zHlj4dQ8ZAtrayhuDDmkY",
"name" : "Timber",
"popularity" : 85.0,
"preview_url" : "https://p.scdn.co/mp3-preview/18d0a45538122fbe33f22604d0e5608789c10ae4",
"track_number" : 1.0,
"type" : "track",
"uri" : "spotify:track:1zHlj4dQ8ZAtrayhuDDmkY"
}
Loading

0 comments on commit ce25abf

Please sign in to comment.