This is still quite easy to stub, and the extra information is very useful to the caller. Signed-off-by: Yonah <contrib@gensokyo.uk>
174 lines
4.9 KiB
Go
174 lines
4.9 KiB
Go
package monstersirenfetch
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"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"`
|
|
}
|
|
|
|
const (
|
|
// AlbumVariantCurrent copies the current variant as-is.
|
|
AlbumVariantCurrent = iota
|
|
// AlbumVariantFull leaves all fields intact in the copy.
|
|
// This variant is not returned by any endpoint and is obtained via [Album.Enrich].
|
|
AlbumVariantFull
|
|
// AlbumVariantDetail zeroes [Album.Artists].
|
|
// This variant is returned by /api/album/%s/detail.
|
|
AlbumVariantDetail
|
|
// AlbumVariantData zeroes [Album.Songs].
|
|
// This variant is returned by /api/album/%d/data.
|
|
AlbumVariantData
|
|
// AlbumVariantBase zeroes [Album.Intro], [Album.Belong], [Album.CoverDeURL], [Album.Songs].
|
|
// This variant is included in /api/albums.
|
|
AlbumVariantBase
|
|
)
|
|
|
|
// IsFull returns whether [Album] is considered to be its [AlbumVariantFull] variant.
|
|
func (a *Album) IsFull() bool { return a.Belong != "" && len(a.Songs) > 0 }
|
|
|
|
// Copy makes a copy of [Album].
|
|
// For an [Album] where the IsFull method returns true, Copy zeroes fields to convert the copy into other variants.
|
|
// For [Album] where IsFull returns false, any variant other than [AlbumVariantCurrent] is undefined.
|
|
func (a *Album) Copy(v *Album, variant int) bool {
|
|
if v == nil || a == nil || v == a {
|
|
return false
|
|
}
|
|
|
|
if variant == AlbumVariantCurrent {
|
|
*v = *a
|
|
return true
|
|
}
|
|
if !a.IsFull() {
|
|
return false
|
|
}
|
|
|
|
switch variant {
|
|
case AlbumVariantFull:
|
|
*v = *a
|
|
|
|
case AlbumVariantDetail:
|
|
*v = *a
|
|
v.Artists = nil
|
|
|
|
case AlbumVariantData:
|
|
*v = *a
|
|
v.Songs = nil
|
|
|
|
case AlbumVariantBase:
|
|
*v = *a
|
|
v.Intro = ""
|
|
v.Belong = ""
|
|
v.CoverDeURL = ""
|
|
v.Songs = nil
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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
|
|
u := APIPrefix + "/album/" + a.CID.String() + "/detail"
|
|
if resp, err := n.Get(ctx, u); err != nil {
|
|
return err
|
|
} else if resp.StatusCode != http.StatusOK {
|
|
return &ResponseStatusError{u, resp.StatusCode, resp.Body.Close()}
|
|
} else {
|
|
if err = json.NewDecoder(resp.Body).Decode(&v); err != nil {
|
|
return errors.Join(resp.Body.Close(), err)
|
|
}
|
|
if err = resp.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})
|
|
}
|