monstersirenfetch/generic.go
Yonah 0999ccd211
cmd/msrfetch: preserve metadata order
The composite maps are no longer needed as the new Album variant makes
more sense than them. They are also unordered while the base variants of
both endpoints are ordered. Composite is therefore only used for
validation in the current implementation.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2025-09-18 21:56:45 +09:00

106 lines
2.4 KiB
Go

package monstersirenfetch
import (
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
const (
APIPrefix = "https://monster-siren.hypergryph.com/api"
)
// InconsistentEnrichError is returned by an Enrich method when a field is inconsistent
// between base data and enrichment data.
type InconsistentEnrichError[T any] struct {
Field string
Value T
NewValue T
}
func (e *InconsistentEnrichError[T]) Error() string {
return "field " + e.Field + " inconsistent: " +
fmt.Sprintf("%v differs from %v", e.Value, e.NewValue)
}
// Net represents an abstraction over the [net] package.
type Net interface {
// Get makes a get request to url and returns the response body.
// The caller must close the body after it finishes reading from it.
Get(ctx context.Context, url string) (body io.ReadCloser, contentLength int64, err error)
}
// Metadata represents metadata, often enriched, of the entire website.
// This is only used externally and is never returned by the API.
type Metadata struct {
Albums []Album `json:"albums"`
Songs []Song `json:"songs"`
}
// Response is a generic API response.
type Response[T any] struct {
Code int `json:"code"`
Message string `json:"msg"`
Data T `json:"data"`
}
// NullableString is a JSON string where its zero value behaves like null.
type NullableString string
func (s *NullableString) MarshalJSON() ([]byte, error) {
if *s == "" {
return []byte("null"), nil
}
return json.Marshal(string(*s))
}
func (s *NullableString) UnmarshalJSON(data []byte) (err error) {
var v *string
err = json.Unmarshal(data, &v)
if err == nil {
if v != nil {
*s = NullableString(*v)
} else {
*s = ""
}
}
return
}
// StringInt is a JSON string representing an integer.
type StringInt int
func (i *StringInt) String() (s string) {
s = strconv.Itoa(int(*i))
if len(s) <= 4 {
s = strings.Repeat("0", 4-len(s)) + s
} else if len(s) < 6 {
s = strings.Repeat("0", 6-len(s)) + s
}
return
}
func (i *StringInt) MarshalJSON() ([]byte, error) { return json.Marshal(i.String()) }
func (i *StringInt) UnmarshalJSON(data []byte) (err error) {
var v string
err = json.Unmarshal(data, &v)
if err == nil {
var n int
n, err = strconv.Atoi(v)
if err == nil {
*i = StringInt(n)
}
}
return
}
// NewEncoder returns a new encoder that writes to w, configured to match upstream API behaviour.
func NewEncoder(w io.Writer) *json.Encoder {
e := json.NewEncoder(w)
e.SetEscapeHTML(false)
return e
}