Add comprehensive godoc documentation to all exported symbols
- Document all exported types, functions, methods, and constants across 52 files - Add package-level doc comments to client, models, utils, and ynison packages - Document HTTP method and endpoint path for each API client method - Document model struct fields and DeJSON/DeList/ToJSON/ToMap methods - Fix duplicate package doc comment in internal/models/base.go Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
7a68ad86cb
commit
0456ca0665
54 files changed
+1097
-652
No files matched your search
+23
-12
@@ -1,3 +1,4 @@
|
||||
// Package utils provides utility functions for the Yandex Music API client.
|
||||
package utils
|
||||
|
||||
import (
|
||||
@@ -6,42 +7,49 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSONBackend defines the interface for JSON encoding/decoding
|
||||
// JSONBackend defines the interface for JSON encoding/decoding.
|
||||
// Implementations can use encoding/json, json-iterator, or other backends.
|
||||
type JSONBackend interface {
|
||||
// Marshal encodes a Go value to JSON.
|
||||
Marshal(v any) ([]byte, error)
|
||||
// Unmarshal decodes JSON data into a Go value.
|
||||
Unmarshal(data []byte, v any) error
|
||||
}
|
||||
|
||||
// DefaultJSONBackend uses the standard library encoding/json
|
||||
// DefaultJSONBackend uses the standard library encoding/json.
|
||||
type DefaultJSONBackend struct{}
|
||||
|
||||
// Marshal encodes a Go value to JSON using encoding/json.
|
||||
func (DefaultJSONBackend) Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal decodes JSON data into a Go value using encoding/json.
|
||||
func (DefaultJSONBackend) Unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// GetDefaultJSONBackend returns the default JSON backend
|
||||
// GetDefaultJSONBackend returns the default JSON backend (encoding/json).
|
||||
func GetDefaultJSONBackend() JSONBackend {
|
||||
return DefaultJSONBackend{}
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded JSON value
|
||||
// RawMessage is a raw encoded JSON value.
|
||||
// It is an alias for encoding/json.RawMessage.
|
||||
type RawMessage = json.RawMessage
|
||||
|
||||
// Marshal is a convenience function
|
||||
// Marshal is a convenience function that marshals a value using the default JSON backend.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return GetDefaultJSONBackend().Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal is a convenience function
|
||||
// Unmarshal is a convenience function that unmarshals data using the default JSON backend.
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
return GetDefaultJSONBackend().Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// ToSnakeCase converts camelCase to snake_case
|
||||
// ToSnakeCase converts camelCase to snake_case.
|
||||
// For example, "camelCase" becomes "camel_case".
|
||||
func ToSnakeCase(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
@@ -60,7 +68,8 @@ func ToSnakeCase(s string) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ToCamelCase converts snake_case to camelCase
|
||||
// ToCamelCase converts snake_case to camelCase.
|
||||
// For example, "snake_case" becomes "snakeCase".
|
||||
func ToCamelCase(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
@@ -79,7 +88,7 @@ func ToCamelCase(s string) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// MapKeysToSnakeCase converts all keys in a map to snake_case
|
||||
// MapKeysToSnakeCase converts all keys in a map to snake_case.
|
||||
func MapKeysToSnakeCase(m map[string]any) map[string]any {
|
||||
result := make(map[string]any, len(m))
|
||||
for k, v := range m {
|
||||
@@ -88,7 +97,7 @@ func MapKeysToSnakeCase(m map[string]any) map[string]any {
|
||||
return result
|
||||
}
|
||||
|
||||
// MapKeysToCamelCase converts all keys in a map to camelCase
|
||||
// MapKeysToCamelCase converts all keys in a map to camelCase.
|
||||
func MapKeysToCamelCase(m map[string]any) map[string]any {
|
||||
result := make(map[string]any, len(m))
|
||||
for k, v := range m {
|
||||
@@ -97,7 +106,8 @@ func MapKeysToCamelCase(m map[string]any) map[string]any {
|
||||
return result
|
||||
}
|
||||
|
||||
// StructToMap converts a struct to a map using json tags
|
||||
// StructToMap converts a struct to a map using json tags.
|
||||
// The forRequest parameter is currently unused but reserved for future use.
|
||||
func StructToMap(v any, forRequest bool) (map[string]any, error) {
|
||||
data, err := Marshal(v)
|
||||
if err != nil {
|
||||
@@ -110,7 +120,8 @@ func StructToMap(v any, forRequest bool) (map[string]any, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetJSONTag returns the json tag for a struct field
|
||||
// GetJSONTag returns the json tag name for a struct field at the given index.
|
||||
// It returns an empty string if the field has no json tag or the tag is "-".
|
||||
func GetJSONTag(t reflect.Type, fieldIndex int) string {
|
||||
if fieldIndex >= t.NumField() {
|
||||
return ""
|
||||
|
||||
+31
-19
@@ -1,3 +1,4 @@
|
||||
// Package utils provides utility functions for the Yandex Music API client.
|
||||
package utils
|
||||
|
||||
import (
|
||||
@@ -5,19 +6,20 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Response represents an API response
|
||||
// Response represents an API response from the Yandex Music API.
|
||||
type Response struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ErrorDescription string `json:"errorDescription,omitempty"`
|
||||
}
|
||||
|
||||
// GetResult returns the result as raw JSON
|
||||
// GetResult returns the result as raw JSON.
|
||||
func (r *Response) GetResult() json.RawMessage {
|
||||
return r.Result
|
||||
}
|
||||
|
||||
// GetError returns the error message
|
||||
// GetError returns the error message from the response.
|
||||
// It returns Error if set, otherwise ErrorDescription, or "Unknown error" if neither is set.
|
||||
func (r *Response) GetError() string {
|
||||
if r.Error != "" {
|
||||
return r.Error
|
||||
@@ -28,12 +30,13 @@ func (r *Response) GetError() string {
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
// HasError returns true if the response contains an error
|
||||
// HasError returns true if the response contains an error.
|
||||
func (r *Response) HasError() bool {
|
||||
return r.Error != "" || r.ErrorDescription != ""
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements custom unmarshaling
|
||||
// UnmarshalJSON implements custom unmarshaling for Response.
|
||||
// If result is missing, the whole response is wrapped in result.
|
||||
func (r *Response) UnmarshalJSON(data []byte) error {
|
||||
// First try to unmarshal as a standard response
|
||||
type Alias Response
|
||||
@@ -53,7 +56,7 @@ func (r *Response) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeJSON creates a Response from JSON data
|
||||
// DeJSON creates a Response from JSON data.
|
||||
func DeJSON(data []byte) (*Response, error) {
|
||||
var resp Response
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
@@ -62,22 +65,30 @@ func DeJSON(data []byte) (*Response, error) {
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Error types
|
||||
// Error types for the Yandex Music API client.
|
||||
var (
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrBadRequest = errors.New("bad request")
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrNetwork = errors.New("network error")
|
||||
ErrTimeout = errors.New("timeout")
|
||||
// ErrUnauthorized is returned when authentication fails (HTTP 401/403).
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
// ErrBadRequest is returned when the request is malformed (HTTP 400).
|
||||
ErrBadRequest = errors.New("bad request")
|
||||
// ErrNotFound is returned when the requested resource is not found (HTTP 404).
|
||||
ErrNotFound = errors.New("not found")
|
||||
// ErrNetwork is returned for network-related errors.
|
||||
ErrNetwork = errors.New("network error")
|
||||
// ErrTimeout is returned when a request times out.
|
||||
ErrTimeout = errors.New("timeout")
|
||||
// ErrSchemaMismatch is returned when the API response schema doesn't match expectations.
|
||||
ErrSchemaMismatch = errors.New("schema mismatch")
|
||||
)
|
||||
|
||||
// YandexMusicError is the base error type
|
||||
// YandexMusicError is the base error type for Yandex Music API errors.
|
||||
// It wraps an underlying error with a descriptive message.
|
||||
type YandexMusicError struct {
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error returns the error message, including the wrapped error if present.
|
||||
func (e *YandexMusicError) Error() string {
|
||||
if e.Err != nil {
|
||||
return e.Message + ": " + e.Err.Error()
|
||||
@@ -85,16 +96,17 @@ func (e *YandexMusicError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error for compatibility with errors.Is and errors.As.
|
||||
func (e *YandexMusicError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewYandexMusicError creates a new YandexMusicError
|
||||
// NewYandexMusicError creates a new YandexMusicError with the given message and underlying error.
|
||||
func NewYandexMusicError(message string, err error) *YandexMusicError {
|
||||
return &YandexMusicError{Message: message, Err: err}
|
||||
}
|
||||
|
||||
// IsUnauthorizedError checks if error is unauthorized
|
||||
// IsUnauthorizedError checks if an error is an unauthorized error.
|
||||
func IsUnauthorizedError(err error) bool {
|
||||
var yme *YandexMusicError
|
||||
if errors.As(err, &yme) {
|
||||
@@ -103,7 +115,7 @@ func IsUnauthorizedError(err error) bool {
|
||||
return errors.Is(err, ErrUnauthorized)
|
||||
}
|
||||
|
||||
// IsBadRequestError checks if error is bad request
|
||||
// IsBadRequestError checks if an error is a bad request error.
|
||||
func IsBadRequestError(err error) bool {
|
||||
var yme *YandexMusicError
|
||||
if errors.As(err, &yme) {
|
||||
@@ -112,7 +124,7 @@ func IsBadRequestError(err error) bool {
|
||||
return errors.Is(err, ErrBadRequest)
|
||||
}
|
||||
|
||||
// IsNotFoundError checks if error is not found
|
||||
// IsNotFoundError checks if an error is a not found error.
|
||||
func IsNotFoundError(err error) bool {
|
||||
var yme *YandexMusicError
|
||||
if errors.As(err, &yme) {
|
||||
@@ -121,7 +133,7 @@ func IsNotFoundError(err error) bool {
|
||||
return errors.Is(err, ErrNotFound)
|
||||
}
|
||||
|
||||
// IsNetworkError checks if error is network error
|
||||
// IsNetworkError checks if an error is a network error.
|
||||
func IsNetworkError(err error) bool {
|
||||
var yme *YandexMusicError
|
||||
if errors.As(err, &yme) {
|
||||
@@ -130,7 +142,7 @@ func IsNetworkError(err error) bool {
|
||||
return errors.Is(err, ErrNetwork)
|
||||
}
|
||||
|
||||
// IsTimeoutError checks if error is timeout
|
||||
// IsTimeoutError checks if an error is a timeout error.
|
||||
func IsTimeoutError(err error) bool {
|
||||
var yme *YandexMusicError
|
||||
if errors.As(err, &yme) {
|
||||
|
||||
+32
-13
@@ -9,16 +9,24 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SchemaMismatch represents a mismatch between expected and actual API response
|
||||
// SchemaMismatch represents a mismatch between expected and actual API response.
|
||||
// It captures details about missing fields, unknown fields, and unknown values.
|
||||
type SchemaMismatch struct {
|
||||
ModelName string
|
||||
MissingFields []string
|
||||
UnknownFields []string
|
||||
UnknownValues map[string]any
|
||||
Endpoint string
|
||||
// ModelName is the name of the model that had the mismatch.
|
||||
ModelName string
|
||||
// MissingFields lists fields that were expected but not found in the response.
|
||||
MissingFields []string
|
||||
// UnknownFields lists fields that were not expected but found in the response.
|
||||
UnknownFields []string
|
||||
// UnknownValues maps unknown field names to their values.
|
||||
UnknownValues map[string]any
|
||||
// Endpoint is the API endpoint where the mismatch occurred.
|
||||
Endpoint string
|
||||
// LibraryVersion is the version of the library when the mismatch occurred.
|
||||
LibraryVersion string
|
||||
}
|
||||
|
||||
// Describe returns a human-readable description of the schema mismatch.
|
||||
func (sm *SchemaMismatch) Describe() string {
|
||||
var parts []string
|
||||
parts = append(parts, fmt.Sprintf("Model: %s", sm.ModelName))
|
||||
@@ -36,17 +44,22 @@ func (sm *SchemaMismatch) Describe() string {
|
||||
return strings.Join(parts, "\n")
|
||||
}
|
||||
|
||||
// SchemaMismatchHandler is a callback for handling schema mismatches
|
||||
// SchemaMismatchHandler is a callback for handling schema mismatches.
|
||||
// It is invoked when a schema mismatch is detected, if configured.
|
||||
type SchemaMismatchHandler func(*SchemaMismatch)
|
||||
|
||||
// SchemaMismatchConfig holds configuration for schema mismatch handling
|
||||
// SchemaMismatchConfig holds configuration for schema mismatch handling.
|
||||
type SchemaMismatchConfig struct {
|
||||
// ReportUnknownFields indicates whether unknown fields should be reported.
|
||||
ReportUnknownFields bool
|
||||
Strict bool
|
||||
OnSchemaMismatch SchemaMismatchHandler
|
||||
// Strict indicates whether schema mismatches should be treated as errors.
|
||||
Strict bool
|
||||
// OnSchemaMismatch is an optional callback for custom mismatch handling.
|
||||
OnSchemaMismatch SchemaMismatchHandler
|
||||
}
|
||||
|
||||
// DefaultSchemaMismatchConfig returns the default configuration
|
||||
// DefaultSchemaMismatchConfig returns the default configuration for schema mismatch handling.
|
||||
// The default config does not report unknown fields, is not strict, and has no callback.
|
||||
func DefaultSchemaMismatchConfig() SchemaMismatchConfig {
|
||||
return SchemaMismatchConfig{
|
||||
ReportUnknownFields: false,
|
||||
@@ -55,7 +68,9 @@ func DefaultSchemaMismatchConfig() SchemaMismatchConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// ReportSchemaMismatch reports a schema mismatch according to config
|
||||
// ReportSchemaMismatch reports a schema mismatch according to the provided config.
|
||||
// If a callback is configured, it is invoked with the mismatch details.
|
||||
// If strict mode is enabled, an error is returned. Otherwise, a warning is logged.
|
||||
func ReportSchemaMismatch(config SchemaMismatchConfig, modelName, endpoint string, missingFields, unknownFields []string, unknownValues map[string]any) error {
|
||||
mismatch := &SchemaMismatch{
|
||||
ModelName: modelName,
|
||||
@@ -80,18 +95,22 @@ func ReportSchemaMismatch(config SchemaMismatchConfig, modelName, endpoint strin
|
||||
return nil
|
||||
}
|
||||
|
||||
// SchemaMismatchTracker tracks reported mismatches to avoid duplicates
|
||||
// SchemaMismatchTracker tracks reported mismatches to avoid duplicate reports.
|
||||
// It is safe for concurrent use.
|
||||
type SchemaMismatchTracker struct {
|
||||
mu sync.Mutex
|
||||
reported map[string]bool
|
||||
}
|
||||
|
||||
// NewSchemaMismatchTracker creates a new SchemaMismatchTracker.
|
||||
func NewSchemaMismatchTracker() *SchemaMismatchTracker {
|
||||
return &SchemaMismatchTracker{
|
||||
reported: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldReport returns true if the given model and fields have not been reported before.
|
||||
// It marks them as reported so subsequent calls return false for the same key.
|
||||
func (t *SchemaMismatchTracker) ShouldReport(modelName string, fields []string) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
+12
-6
@@ -1,3 +1,4 @@
|
||||
// Package utils provides utility functions for the Yandex Music API client.
|
||||
package utils
|
||||
|
||||
import (
|
||||
@@ -10,21 +11,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSignKey is the signing key from the Android app
|
||||
// DefaultSignKey is the signing key from the Android app.
|
||||
DefaultSignKey = "p93jhgh689SBReK6ghtw62"
|
||||
// DesktopSignKey is the signing key from the desktop app
|
||||
// DesktopSignKey is the signing key from the desktop app.
|
||||
DesktopSignKey = "kzqU4XhfCaY6B6JTHODeq5"
|
||||
// DesktopClient is the X-Yandex-Music-Client header value for desktop
|
||||
// DesktopClient is the X-Yandex-Music-Client header value for desktop.
|
||||
DesktopClient = "YandexMusicDesktopAppWindows/5.95.0"
|
||||
)
|
||||
|
||||
// Sign represents a request signature
|
||||
// Sign represents a request signature for signed API requests.
|
||||
type Sign struct {
|
||||
Timestamp int64
|
||||
Value string
|
||||
}
|
||||
|
||||
// GetSignRequest creates a signature for a track request
|
||||
// GetSignRequest creates a signature for a track request using HMAC-SHA256.
|
||||
// It returns a Sign with the timestamp and base64-encoded signature value.
|
||||
// If key is empty, DefaultSignKey is used.
|
||||
func GetSignRequest(trackID string, key string) Sign {
|
||||
if key == "" {
|
||||
key = DefaultSignKey
|
||||
@@ -43,7 +46,9 @@ func GetSignRequest(trackID string, key string) Sign {
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileInfoSign creates a signature for file info requests
|
||||
// GetFileInfoSign creates a signature for file info requests using HMAC-SHA256.
|
||||
// It returns a Sign with the timestamp and base64-encoded signature value (last character removed as in Python).
|
||||
// If key is empty, DesktopSignKey is used.
|
||||
func GetFileInfoSign(trackID, quality string, codecs []string, transport, key string) Sign {
|
||||
if key == "" {
|
||||
key = DesktopSignKey
|
||||
@@ -66,6 +71,7 @@ func GetFileInfoSign(trackID, quality string, codecs []string, transport, key st
|
||||
}
|
||||
}
|
||||
|
||||
// joinCodecs joins a list of codec strings into a single string.
|
||||
func joinCodecs(codecs []string) string {
|
||||
result := ""
|
||||
for _, c := range codecs {
|
||||
|
||||
Reference in new issue
Block a user