This also includes tests against a sample response from the https://monster-siren.hypergryph.com/api/album/1030/data endpoint. Signed-off-by: Yonah <contrib@gensokyo.uk>
110 lines
3.3 KiB
Go
110 lines
3.3 KiB
Go
package monstersirenfetch
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
)
|
|
|
|
// AlbumResponse is the response of /api/album/%d/data and /api/album/%d/detail.
|
|
type AlbumResponse Response[Album]
|
|
|
|
// AlbumSong represents a minimal variant of [Song] embedded in the detail variant of [Album].
|
|
type AlbumSong struct {
|
|
CID StringInt `json:"cid"`
|
|
Name string `json:"name"`
|
|
Artists []string `json:"artistes"`
|
|
}
|
|
|
|
// Album represents the metadata of an album.
|
|
// Field availability is documented for each individual field.
|
|
type Album struct {
|
|
// CID is available for all variants of [Album].
|
|
CID StringInt `json:"cid"`
|
|
// Name is available for all variants of [Album].
|
|
Name string `json:"name"`
|
|
// Intro is available for data and detail variants of [Album].
|
|
Intro string `json:"intro,omitempty"`
|
|
// Belong is available for data and detail variants of [Album].
|
|
Belong string `json:"belong,omitempty"`
|
|
// CoverURL is available for all variants of [Album].
|
|
CoverURL string `json:"coverUrl"`
|
|
// CoverDeURL is available for data and detail variants of [Album].
|
|
CoverDeURL string `json:"coverDeUrl,omitempty"`
|
|
// Songs is available for the detail variant of [Album].
|
|
Songs []AlbumSong `json:"songs,omitempty"`
|
|
// Artists is available for base and data variants of [Album].
|
|
Artists []string `json:"artistes"`
|
|
}
|
|
|
|
func (a *Album) IsFull() bool { return a.Belong != "" && len(a.Songs) > 0 }
|
|
|
|
// Enrich populates the remaining fields of a non-full [Album].
|
|
func (a *Album) Enrich(ctx context.Context, n Net) error {
|
|
if a == nil || a.IsFull() {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
var v AlbumResponse
|
|
if body, _, err := n.Get(ctx, APIPrefix+"/album/"+a.CID.String()+"/detail"); err != nil {
|
|
return err
|
|
} else {
|
|
if err = json.NewDecoder(body).Decode(&v); err != nil {
|
|
return errors.Join(body.Close(), err)
|
|
}
|
|
if err = body.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// these should be unreachable unless the server malfunctions
|
|
if a.CID != v.Data.CID {
|
|
return &InconsistentEnrichError[StringInt]{"cid", a.CID, v.Data.CID}
|
|
}
|
|
if a.Name != v.Data.Name {
|
|
return &InconsistentEnrichError[string]{"name", a.Name, v.Data.Name}
|
|
}
|
|
if a.CoverURL != v.Data.CoverURL {
|
|
return &InconsistentEnrichError[string]{"coverUrl", a.CoverURL, v.Data.CoverURL}
|
|
}
|
|
if v.Data.Artists != nil {
|
|
return &InconsistentEnrichError[[]string]{"artists", nil, v.Data.Artists}
|
|
}
|
|
|
|
v.Data.Artists = a.Artists
|
|
*a = v.Data
|
|
return nil
|
|
}
|
|
|
|
// albumDirect is [Album] without its MarshalJSON method.
|
|
type albumDirect Album
|
|
|
|
// albumNullable is [Album] with corresponding nullable string fields.
|
|
type albumNullable struct {
|
|
CID StringInt `json:"cid"`
|
|
Name string `json:"name"`
|
|
Intro NullableString `json:"intro"`
|
|
Belong NullableString `json:"belong"`
|
|
CoverURL string `json:"coverUrl"`
|
|
CoverDeURL NullableString `json:"coverDeUrl"`
|
|
Songs []AlbumSong `json:"songs,omitempty"`
|
|
Artists []string `json:"artistes"`
|
|
}
|
|
|
|
func (a *Album) MarshalJSON() (data []byte, err error) {
|
|
buf := new(bytes.Buffer)
|
|
e := NewEncoder(buf)
|
|
|
|
if a.Belong == "" { // this covers both data and detail variants
|
|
err = e.Encode((*albumDirect)(a))
|
|
data = buf.Bytes()
|
|
return
|
|
}
|
|
|
|
return json.Marshal(&albumNullable{a.CID, a.Name,
|
|
NullableString(a.Intro), NullableString(a.Belong),
|
|
a.CoverURL, NullableString(a.CoverDeURL), a.Songs, a.Artists})
|
|
}
|