// Package models provides data models for the Yandex Music API. // All models implement the Model interface with DeJSON/DeList deserialization methods. package models import ( "encoding/json" "fmt" "strconv" "gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils" ) // Model is the base interface for all models. // All model types implement this interface for JSON deserialization and serialization. type Model interface { // DeJSON deserializes a model from JSON data. DeJSON(data []byte, c ClientInterface) (Model, error) // DeList deserializes a list of models from JSON data. DeList(data []byte, c ClientInterface) ([]Model, error) // ToJSON serializes the model to JSON. ToJSON() ([]byte, error) // ToMap converts the model to a map. ToMap() (map[string]any, error) } // BaseModel provides common functionality for models, including a client reference. type BaseModel struct { Client ClientInterface `json:"-"` } // NewBaseModel creates a new BaseModel with the given client interface. func NewBaseModel(c ClientInterface) *BaseModel { return &BaseModel{Client: c} } // DeJSON implements Model.DeJSON for basic models. // This should be overridden by concrete types. func (b *BaseModel) DeJSON(data []byte, c ClientInterface) (Model, error) { // This should be overridden by concrete types return nil, nil } // DeList implements Model.DeList for basic models. // This should be overridden by concrete types. func (b *BaseModel) DeList(data []byte, c ClientInterface) ([]Model, error) { var rawList []json.RawMessage if err := json.Unmarshal(data, &rawList); err != nil { return nil, err } result := make([]Model, 0, len(rawList)) for range rawList { // This should be overridden by concrete types result = append(result, nil) } return result, nil } // ToJSON serializes the model to JSON using the default JSON backend. func (b *BaseModel) ToJSON() ([]byte, error) { return utils.Marshal(b) } // ToMap converts the model to a map by serializing to JSON and unmarshaling into a map. func (b *BaseModel) ToMap() (map[string]any, error) { data, err := b.ToJSON() if err != nil { return nil, err } var result map[string]any if err := json.Unmarshal(data, &result); err != nil { return nil, err } return result, nil } // SetClient sets the client interface on the model. func (b *BaseModel) SetClient(c ClientInterface) { b.Client = c } // GetClient returns the client interface associated with the model. func (b *BaseModel) GetClient() ClientInterface { return b.Client } // JSONID is an ID that can be represented as either a JSON string or a JSON number. // The Yandex Music API sometimes returns IDs as numbers (e.g., 12345) and sometimes // as strings (e.g., "12345"). JSONID accepts both and stores the value as a string. type JSONID string // UnmarshalJSON implements json.Unmarshaler, accepting both JSON strings and numbers. func (id *JSONID) UnmarshalJSON(data []byte) error { // Try string first var s string if err := json.Unmarshal(data, &s); err == nil { *id = JSONID(s) return nil } // Try number (float64 is the default JSON number decoding) var f float64 if err := json.Unmarshal(data, &f); err != nil { return fmt.Errorf("cannot unmarshal %v into JSONID: %w", string(data), err) } *id = JSONID(strconv.FormatFloat(f, 'f', -1, 64)) return nil } // String returns the ID as a Go string. func (id JSONID) String() string { return string(id) } // JSONR128 is a flexible R128 loudness normalization value that accepts both JSON numbers and objects. // The Yandex Music API sometimes returns r128 as a number (e.g., -23.5) and sometimes // as an object (e.g., {"value": -23.5}). JSONR128 accepts both and stores as float64. type JSONR128 float64 // UnmarshalJSON implements json.Unmarshaler, accepting both JSON numbers and objects. func (r *JSONR128) UnmarshalJSON(data []byte) error { // Try float64 first var f float64 if err := json.Unmarshal(data, &f); err == nil { *r = JSONR128(f) return nil } // Try object with "value" key var obj struct { Value float64 `json:"value"` } if err := json.Unmarshal(data, &obj); err == nil { *r = JSONR128(obj.Value) return nil } // Default to 0 if neither format works *r = 0 return nil } // Float64 returns the R128 value as a float64. func (r JSONR128) Float64() float64 { return float64(r) }