diff --git a/song.go b/song.go index 46fff99..0af4303 100644 --- a/song.go +++ b/song.go @@ -13,7 +13,6 @@ import ( type SongResponse Response[Song] // Song holds the metadata of a song. -// Fields marked with omitempty are only populated when the IsFull method returns true. type Song struct { CID StringInt `json:"cid"` Name string `json:"name"` @@ -25,9 +24,53 @@ type Song struct { Artists []string `json:"artists"` } -// IsFull returns whether the metadata held by [Song] is considered full (originating from a [SongResponse]). +const ( + // SongVariantCurrent copies the current variant as-is. + SongVariantCurrent = iota + // SongVariantFull leaves all fields intact in the copy. + // This variant is returned by /api/song/%d. + SongVariantFull + // SongVariantBase zeroes [Song.SourceURL], [Song.LyricURL], [Song.MvURL], [Song.MvCoverURL]. + // This variant is included in /api/songs. + SongVariantBase +) + +// IsFull returns whether [Song] is considered to be its [SongVariantFull] variant. func (s *Song) IsFull() bool { return s.SourceURL != "" } +// Copy makes a copy of [Song]. +// For a [Song] where the IsFull method returns true, Copy zeroes fields to convert the copy into other variants. +// For [Song] where IsFull returns false, any variant other than [SongVariantCurrent] is undefined. +func (s *Song) Copy(variant int) *Song { + if s == nil { + return nil + } + + v := *s + if variant == SongVariantCurrent { + return &v + } + if !s.IsFull() { + return nil + } + + switch variant { + case SongVariantFull: + break + + case SongVariantBase: + s.SourceURL = "" + s.LyricURL = "" + s.MvURL = "" + s.MvCoverURL = "" + + default: + return nil + } + + return s +} + // Enrich populates the remaining fields of a non-full [Song]. func (s *Song) Enrich(ctx context.Context, n Net) error { if s == nil || s.IsFull() { diff --git a/song_test.go b/song_test.go index 4b80611..c0285a3 100644 --- a/song_test.go +++ b/song_test.go @@ -22,6 +22,66 @@ func TestSong(t *testing.T) { LyricURL: "https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc", Artists: []string{"塞壬唱片-MSR"}, }}, songJSON) + + t.Run("copy", func(t *testing.T) { + testCases := []struct { + name string + a *Song + variant int + want *Song + }{ + {"nil", nil, SongVariantBase, nil}, + {"current", new(Song), SongVariantCurrent, new(Song)}, + {"full guard", new(Song), SongVariantFull, nil}, + + {"current full", &Song{ + SourceURL: "\x00", + }, SongVariantCurrent, &Song{ + SourceURL: "\x00", + }}, + + {"oob", &Song{ + SourceURL: "\x00", + }, 0xbad, nil}, + + {"full", &Song{ + CID: 48794, + Name: "Warm and Small Light", + AlbumCID: 6660, + SourceURL: "https://res01.hycdn.cn/04ce5de54bb52eb85008644d541d40fa/68CA0442/siren/audio/20240709/a7f650238eaefc9c30a9627d7f78d819.wav", + LyricURL: "https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc", + Artists: []string{"塞壬唱片-MSR"}, + }, SongVariantFull, &Song{ + CID: 48794, + Name: "Warm and Small Light", + AlbumCID: 6660, + SourceURL: "https://res01.hycdn.cn/04ce5de54bb52eb85008644d541d40fa/68CA0442/siren/audio/20240709/a7f650238eaefc9c30a9627d7f78d819.wav", + LyricURL: "https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc", + Artists: []string{"塞壬唱片-MSR"}, + }}, + + {"base", &Song{ + CID: 48794, + Name: "Warm and Small Light", + AlbumCID: 6660, + SourceURL: "https://res01.hycdn.cn/04ce5de54bb52eb85008644d541d40fa/68CA0442/siren/audio/20240709/a7f650238eaefc9c30a9627d7f78d819.wav", + LyricURL: "https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc", + Artists: []string{"塞壬唱片-MSR"}, + }, SongVariantBase, &Song{ + CID: 48794, + Name: "Warm and Small Light", + AlbumCID: 6660, + Artists: []string{"塞壬唱片-MSR"}, + }}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.a.Copy(tc.variant); !reflect.DeepEqual(got, tc.want) { + t.Errorf("Copy: %#v, want %#v", got, tc.want) + } + }) + } + }) } func TestSongEnrich(t *testing.T) {