composite: flatten album and song data
This establishes relation between album and song. Signed-off-by: Yonah <contrib@gensokyo.uk>
This commit is contained in:
parent
2d6b87702d
commit
2e0a3a9bf8
68
composite.go
Normal file
68
composite.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package monstersirenfetch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlbumConflictError is returned by [Flatten] if two or more albums share the same CID.
|
||||||
|
type AlbumConflictError struct {
|
||||||
|
Index int
|
||||||
|
Previous Album
|
||||||
|
Current Album
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AlbumConflictError) Error() string {
|
||||||
|
return "album CID conflict on index " + strconv.Itoa(e.Index) + ": " +
|
||||||
|
"previous album " + e.Previous.Name + ", " +
|
||||||
|
"current album " + e.Current.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// SongConflictError is returned by [Flatten] if two or more songs belonging to the same [Album] share the same CID.
|
||||||
|
type SongConflictError struct {
|
||||||
|
Index int
|
||||||
|
Previous *Song
|
||||||
|
Current Song
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SongConflictError) Error() string {
|
||||||
|
return "song CID conflict on index " + strconv.Itoa(e.Index) + ": " +
|
||||||
|
"previous song " + e.Previous.Name + ", " +
|
||||||
|
"current song " + e.Current.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompositeAlbum represents an [Album] with a collection of its associated identifier-only [Song].
|
||||||
|
type CompositeAlbum struct {
|
||||||
|
// Songs is a map of [Song.CID] to identifier-only [Song].
|
||||||
|
Songs map[StringInt]*Song
|
||||||
|
|
||||||
|
*Album
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompositeAlbumsMap is a map of [Album.CID] to [CompositeAlbum].
|
||||||
|
type CompositeAlbumsMap map[StringInt]CompositeAlbum
|
||||||
|
|
||||||
|
// Flatten flattens [AlbumsData] and [SongsData] into a [CompositeAlbumsMap].
|
||||||
|
// All values in [CompositeAlbum.Songs] and the [CompositeAlbum.Album] field are guaranteed to be non-nil.
|
||||||
|
func Flatten(albumData AlbumsData, songsData SongsData) (CompositeAlbumsMap, error) {
|
||||||
|
m := make(CompositeAlbumsMap, len(albumData))
|
||||||
|
for i, a := range albumData {
|
||||||
|
if c, ok := m[a.CID]; ok {
|
||||||
|
return nil, &AlbumConflictError{Index: i, Previous: *c.Album, Current: a}
|
||||||
|
}
|
||||||
|
m[a.CID] = CompositeAlbum{Songs: make(map[StringInt]*Song), Album: &a}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range songsData.List {
|
||||||
|
var c *Song
|
||||||
|
if a, ok := m[s.AlbumCID]; !ok {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
} else if c, ok = a.Songs[s.CID]; ok {
|
||||||
|
return nil, &SongConflictError{Index: i, Previous: c, Current: s}
|
||||||
|
} else {
|
||||||
|
a.Songs[s.CID] = &s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
31
composite_test.go
Normal file
31
composite_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package monstersirenfetch_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "git.gensokyo.uk/yonah/monstersirenfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlatten(t *testing.T) {
|
||||||
|
t.Run("sample", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
albumsResp AlbumsResponse
|
||||||
|
songsResp SongsResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(albumsJSON, &albumsResp); err != nil {
|
||||||
|
t.Fatalf("Unmarshal: error = %v", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(songsJSON, &songsResp); err != nil {
|
||||||
|
t.Fatalf("Unmarshal: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, err := Flatten(albumsResp.Data, songsResp.Data); err != nil {
|
||||||
|
t.Fatalf("Flatten: error = %v", err)
|
||||||
|
} else {
|
||||||
|
// TODO(ophestra): validate this result
|
||||||
|
t.Logf("Flatten: %#v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
1
song.go
1
song.go
@ -9,6 +9,7 @@ import (
|
|||||||
type SongResponse Response[Song]
|
type SongResponse Response[Song]
|
||||||
|
|
||||||
// Song holds the metadata of a song.
|
// Song holds the metadata of a song.
|
||||||
|
// Fields marked with omitempty are only populated when the IsFull method returns true.
|
||||||
type Song struct {
|
type Song struct {
|
||||||
CID StringInt `json:"cid"`
|
CID StringInt `json:"cid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user