Fix Album model to match Python reference
- Labels: changed from []Label to []LabelOrString (API returns both objects and strings) - Trailer: changed from *TrailerInfo to *AlbumTrailerStatus (Python has AlbumTrailerStatus with only 'available' field) - Added LabelOrString type with custom UnmarshalJSON - Added AlbumTrailerStatus type with Available field
This commit is contained in:
1 parent
6312c24b0f
commit
277a3e6924
2 files changed
+110
-44
No files matched your search
@@ -17,7 +17,7 @@ type Album struct {
|
|||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
ReleaseDate string `json:"release_date"`
|
ReleaseDate string `json:"release_date"`
|
||||||
Artists []Artist `json:"artists"`
|
Artists []Artist `json:"artists"`
|
||||||
Labels []Label `json:"labels"`
|
Labels []LabelOrString `json:"labels"`
|
||||||
Volumes [][]Track `json:"volumes"`
|
Volumes [][]Track `json:"volumes"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Meta *AlbumMeta `json:"meta"`
|
Meta *AlbumMeta `json:"meta"`
|
||||||
@@ -32,7 +32,93 @@ type Album struct {
|
|||||||
Options []string `json:"options"`
|
Options []string `json:"options"`
|
||||||
TrackPositions []TrackPosition `json:"track_positions"`
|
TrackPositions []TrackPosition `json:"track_positions"`
|
||||||
SimilarAlbums []Album `json:"similar_albums"`
|
SimilarAlbums []Album `json:"similar_albums"`
|
||||||
Trailer *TrailerInfo `json:"trailer"`
|
Trailer *AlbumTrailerStatus `json:"trailer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelOrString represents a label that can be either a Label object or a string.
|
||||||
|
// The Yandex Music API sometimes returns labels as objects and sometimes as strings.
|
||||||
|
type LabelOrString struct {
|
||||||
|
Label *Label `json:"-"`
|
||||||
|
Str string `json:"-"`
|
||||||
|
IsStr bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler, accepting both Label objects and strings.
|
||||||
|
func (l *LabelOrString) UnmarshalJSON(data []byte) error {
|
||||||
|
// Try string first
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err == nil {
|
||||||
|
l.Str = s
|
||||||
|
l.IsStr = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Try Label object
|
||||||
|
var label Label
|
||||||
|
if err := json.Unmarshal(data, &label); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Label = &label
|
||||||
|
l.IsStr = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler.
|
||||||
|
func (l LabelOrString) MarshalJSON() ([]byte, error) {
|
||||||
|
if l.IsStr {
|
||||||
|
return json.Marshal(l.Str)
|
||||||
|
}
|
||||||
|
return json.Marshal(l.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the label as a string.
|
||||||
|
func (l LabelOrString) String() string {
|
||||||
|
if l.IsStr {
|
||||||
|
return l.Str
|
||||||
|
}
|
||||||
|
if l.Label != nil {
|
||||||
|
return l.Label.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlbumTrailerStatus represents trailer availability status for an album.
|
||||||
|
type AlbumTrailerStatus struct {
|
||||||
|
BaseModel
|
||||||
|
Available bool `json:"available"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeJSON deserializes AlbumTrailerStatus from JSON data.
|
||||||
|
func (ats *AlbumTrailerStatus) DeJSON(data []byte, c ClientInterface) (Model, error) {
|
||||||
|
var raw map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(c)}
|
||||||
|
|
||||||
|
if v, ok := raw["available"]; ok {
|
||||||
|
json.Unmarshal(v, &status.Available)
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeList deserializes a list of AlbumTrailerStatus from JSON data.
|
||||||
|
func (ats *AlbumTrailerStatus) DeList(data []byte, c ClientInterface) ([]Model, error) {
|
||||||
|
var rawList []json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &rawList); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]Model, 0, len(rawList))
|
||||||
|
for _, item := range rawList {
|
||||||
|
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(c)}
|
||||||
|
if err := json.Unmarshal(item, status); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, status)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeJSON deserializes an Album from JSON data.
|
// DeJSON deserializes an Album from JSON data.
|
||||||
@@ -101,10 +187,11 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
|
|||||||
album.Artists = artists
|
album.Artists = artists
|
||||||
}
|
}
|
||||||
if v, ok := raw["labels"]; ok {
|
if v, ok := raw["labels"]; ok {
|
||||||
var labels []Label
|
var labels []LabelOrString
|
||||||
json.Unmarshal(v, &labels)
|
if err := json.Unmarshal(v, &labels); err == nil {
|
||||||
album.Labels = labels
|
album.Labels = labels
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if v, ok := raw["volumes"]; ok {
|
if v, ok := raw["volumes"]; ok {
|
||||||
var volumes [][]Track
|
var volumes [][]Track
|
||||||
json.Unmarshal(v, &volumes)
|
json.Unmarshal(v, &volumes)
|
||||||
@@ -131,7 +218,7 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
|
|||||||
album.SimilarAlbums = similar
|
album.SimilarAlbums = similar
|
||||||
}
|
}
|
||||||
if v, ok := raw["trailer"]; ok {
|
if v, ok := raw["trailer"]; ok {
|
||||||
var trailer TrailerInfo
|
var trailer AlbumTrailerStatus
|
||||||
json.Unmarshal(v, &trailer)
|
json.Unmarshal(v, &trailer)
|
||||||
album.Trailer = &trailer
|
album.Trailer = &trailer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestAlbum_DeJSON(t *testing.T) {
|
|||||||
"best": {"tracks": [{"id": "bt1", "title": "Best Track", "duration_ms": 200000}]},
|
"best": {"tracks": [{"id": "bt1", "title": "Best Track", "duration_ms": 200000}]},
|
||||||
"track_positions": [{"revision": 1, "timestamp": "2023-01-01T00:00:00Z", "position": 1, "from_cache": false}],
|
"track_positions": [{"revision": 1, "timestamp": "2023-01-01T00:00:00Z", "position": 1, "from_cache": false}],
|
||||||
"similar_albums": [{"id": "sim1", "title": "Similar Album", "cover_uri": "https://example.com/sim.jpg", "year": 2022}],
|
"similar_albums": [{"id": "sim1", "title": "Similar Album", "cover_uri": "https://example.com/sim.jpg", "year": 2022}],
|
||||||
"trailer": {"preview_uri": "https://example.com/preview.mp3", "track_id": "t1", "album_id": "123", "duration": 30}
|
"trailer": {"available": true}
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
album := &Album{BaseModel: *NewBaseModel(client)}
|
album := &Album{BaseModel: *NewBaseModel(client)}
|
||||||
@@ -101,8 +101,8 @@ func TestAlbum_DeJSON(t *testing.T) {
|
|||||||
if len(resultAlbum.Labels) != 1 {
|
if len(resultAlbum.Labels) != 1 {
|
||||||
t.Errorf("Expected 1 label, got %d", len(resultAlbum.Labels))
|
t.Errorf("Expected 1 label, got %d", len(resultAlbum.Labels))
|
||||||
}
|
}
|
||||||
if resultAlbum.Labels[0].Name != "Label 1" {
|
if resultAlbum.Labels[0].String() != "Label 1" {
|
||||||
t.Errorf("Expected label name 'Label 1', got '%s'", resultAlbum.Labels[0].Name)
|
t.Errorf("Expected label name 'Label 1', got '%s'", resultAlbum.Labels[0].String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test volumes
|
// Test volumes
|
||||||
@@ -164,17 +164,8 @@ func TestAlbum_DeJSON(t *testing.T) {
|
|||||||
if resultAlbum.Trailer == nil {
|
if resultAlbum.Trailer == nil {
|
||||||
t.Fatal("Expected Trailer to be non-nil")
|
t.Fatal("Expected Trailer to be non-nil")
|
||||||
}
|
}
|
||||||
if resultAlbum.Trailer.PreviewURI != "https://example.com/preview.mp3" {
|
if !resultAlbum.Trailer.Available {
|
||||||
t.Errorf("Expected PreviewURI 'https://example.com/preview.mp3', got '%s'", resultAlbum.Trailer.PreviewURI)
|
t.Errorf("Expected Trailer.Available true, got false")
|
||||||
}
|
|
||||||
if resultAlbum.Trailer.TrackID != "t1" {
|
|
||||||
t.Errorf("Expected TrackID 't1', got '%s'", resultAlbum.Trailer.TrackID)
|
|
||||||
}
|
|
||||||
if resultAlbum.Trailer.AlbumID != "123" {
|
|
||||||
t.Errorf("Expected AlbumID '123', got '%s'", resultAlbum.Trailer.AlbumID)
|
|
||||||
}
|
|
||||||
if resultAlbum.Trailer.Duration != 30 {
|
|
||||||
t.Errorf("Expected Duration 30, got %d", resultAlbum.Trailer.Duration)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,50 +299,38 @@ func TestAlbumMeta_DeList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrailerInfo_DeJSON(t *testing.T) {
|
func TestAlbumTrailerStatus_DeJSON(t *testing.T) {
|
||||||
client := newMockClient()
|
client := newMockClient()
|
||||||
data := []byte(`{
|
data := []byte(`{
|
||||||
"preview_uri": "https://example.com/preview.mp3",
|
"available": true
|
||||||
"track_id": "track123",
|
|
||||||
"album_id": "album456",
|
|
||||||
"duration": 45
|
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
trailer := &TrailerInfo{BaseModel: *NewBaseModel(client)}
|
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(client)}
|
||||||
result, err := trailer.DeJSON(data, client)
|
result, err := status.DeJSON(data, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("DeJSON failed: %v", err)
|
t.Fatalf("DeJSON failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultTrailer := result.(*TrailerInfo)
|
resultStatus := result.(*AlbumTrailerStatus)
|
||||||
if resultTrailer.PreviewURI != "https://example.com/preview.mp3" {
|
if !resultStatus.Available {
|
||||||
t.Errorf("Expected PreviewURI 'https://example.com/preview.mp3', got '%s'", resultTrailer.PreviewURI)
|
t.Errorf("Expected Available true, got false")
|
||||||
}
|
|
||||||
if resultTrailer.TrackID != "track123" {
|
|
||||||
t.Errorf("Expected TrackID 'track123', got '%s'", resultTrailer.TrackID)
|
|
||||||
}
|
|
||||||
if resultTrailer.AlbumID != "album456" {
|
|
||||||
t.Errorf("Expected AlbumID 'album456', got '%s'", resultTrailer.AlbumID)
|
|
||||||
}
|
|
||||||
if resultTrailer.Duration != 45 {
|
|
||||||
t.Errorf("Expected Duration 45, got %d", resultTrailer.Duration)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrailerInfo_DeList(t *testing.T) {
|
func TestAlbumTrailerStatus_DeList(t *testing.T) {
|
||||||
client := newMockClient()
|
client := newMockClient()
|
||||||
data := []byte(`[
|
data := []byte(`[
|
||||||
{"preview_uri": "https://example.com/preview1.mp3", "track_id": "t1", "album_id": "a1", "duration": 30},
|
{"available": true},
|
||||||
{"preview_uri": "https://example.com/preview2.mp3", "track_id": "t2", "album_id": "a2", "duration": 45}
|
{"available": false}
|
||||||
]`)
|
]`)
|
||||||
|
|
||||||
trailer := &TrailerInfo{BaseModel: *NewBaseModel(client)}
|
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(client)}
|
||||||
result, err := trailer.DeList(data, client)
|
result, err := status.DeList(data, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("DeList failed: %v", err)
|
t.Fatalf("DeList failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) != 2 {
|
if len(result) != 2 {
|
||||||
t.Errorf("Expected 2 trailers, got %d", len(result))
|
t.Errorf("Expected 2 statuses, got %d", len(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user