refactor: wrap bare errors with context in anime package

This commit is contained in:
2026-06-16 10:42:30 +02:00
committed by Milas Holsting
parent ff54e9c5db
commit 290dc36298
4 changed files with 52 additions and 28 deletions

View File

@@ -35,17 +35,19 @@ type browseQuery struct {
func producerQueryParams(c *gin.Context) (string, int, int, error) {
q := strings.TrimSpace(c.Query("q"))
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
rawPage := c.DefaultQuery("page", "1")
page, err := strconv.Atoi(rawPage)
if err != nil {
return "", 0, 0, fmt.Errorf("invalid page")
return "", 0, 0, fmt.Errorf("invalid page %q: %w", rawPage, err)
}
if page < 1 {
page = 1
}
limit, err := strconv.Atoi(c.DefaultQuery("limit", "50"))
rawLimit := c.DefaultQuery("limit", "50")
limit, err := strconv.Atoi(rawLimit)
if err != nil {
return "", 0, 0, fmt.Errorf("invalid limit")
return "", 0, 0, fmt.Errorf("invalid limit %q: %w", rawLimit, err)
}
if limit < 1 || limit > 12 {
limit = 12
@@ -137,8 +139,11 @@ func parseBrowseQuery(c *gin.Context) (browseQuery, error) {
studioID := 0
if raw := strings.TrimSpace(c.Query("studio")); raw != "" {
id, err := strconv.Atoi(raw)
if err != nil || id < 0 {
return browseQuery{}, fmt.Errorf("invalid studio id")
if err != nil {
return browseQuery{}, fmt.Errorf("invalid studio id %q: %w", raw, err)
}
if id < 0 {
return browseQuery{}, fmt.Errorf("invalid studio id %d", id)
}
studioID = id
}
@@ -147,16 +152,17 @@ func parseBrowseQuery(c *gin.Context) (browseQuery, error) {
for _, g := range c.QueryArray("genres") {
id, err := strconv.Atoi(g)
if err != nil {
return browseQuery{}, fmt.Errorf("invalid genre id")
return browseQuery{}, fmt.Errorf("invalid genre id %q: %w", g, err)
}
if id > 0 {
genres = append(genres, id)
}
}
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
rawPage := c.DefaultQuery("page", "1")
page, err := strconv.Atoi(rawPage)
if err != nil {
return browseQuery{}, fmt.Errorf("invalid page")
return browseQuery{}, fmt.Errorf("invalid page %q: %w", rawPage, err)
}
if page < 1 {
page = 1

View File

@@ -2,6 +2,7 @@ package recommendations
import (
"context"
"fmt"
"mal/integrations/jikan"
"mal/internal/domain"
"mal/internal/observability"
@@ -35,7 +36,7 @@ func (e engine) getTopPicksForYou(ctx context.Context, userID string, resultLimi
watchlist, err := e.repo.GetUserWatchList(ctx, userID)
if err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("get user watchlist for %q: %w", userID, err)
}
now := time.Now()
@@ -46,17 +47,17 @@ func (e engine) getTopPicksForYou(ctx context.Context, userID string, resultLimi
seedAnimes, err := e.fetchSeedAnimes(ctx, seedPool)
if err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("fetch seed animes: %w", err)
}
profile := buildTasteProfile(now, seedPool, seedAnimes)
store := newCandidateStore(watchlist)
if err := e.collectCollaborativeCandidates(ctx, seedPool, store); err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("collect collaborative candidates: %w", err)
}
if err := e.collectProfileSearchCandidates(ctx, profile, store); err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("collect profile search candidates: %w", err)
}
ranked := store.ranked()
@@ -66,7 +67,7 @@ func (e engine) getTopPicksForYou(ctx context.Context, userID string, resultLimi
candidates, err := e.scoreRankedCandidates(ctx, now, profile, ranked, resultLimit)
if err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("score ranked candidates: %w", err)
}
return domain.CatalogSectionData{
@@ -83,7 +84,7 @@ func (e engine) fetchSeedAnimes(ctx context.Context, seedPool []recommendationSe
g.Go(func() error {
anime, err := e.jikan.GetAnimeByID(ctx, seed.animeID)
if err != nil {
return err
return fmt.Errorf("get seed anime %d: %w", seed.animeID, err)
}
seedAnimes[i] = anime
return nil
@@ -91,7 +92,7 @@ func (e engine) fetchSeedAnimes(ctx context.Context, seedPool []recommendationSe
}
if err := g.Wait(); err != nil {
return nil, err
return nil, fmt.Errorf("wait for seed anime fetches: %w", err)
}
return seedAnimes, nil
@@ -131,7 +132,10 @@ func (e engine) collectCollaborativeCandidates(ctx context.Context, seedPool []r
})
}
return g.Wait()
if err := g.Wait(); err != nil {
return fmt.Errorf("wait for collaborative candidate fetches: %w", err)
}
return nil
}
func (e engine) collectProfileSearchCandidates(ctx context.Context, profile userTasteProfile, store *candidateStore) error {
@@ -183,7 +187,10 @@ func (e engine) collectProfileSearchCandidates(ctx context.Context, profile user
})
}
return g.Wait()
if err := g.Wait(); err != nil {
return fmt.Errorf("wait for profile search candidate fetches: %w", err)
}
return nil
}
func (e engine) scoreRankedCandidates(
@@ -233,7 +240,7 @@ func (e engine) scoreRankedCandidates(
}
if err := g.Wait(); err != nil {
return nil, err
return nil, fmt.Errorf("wait for candidate scoring: %w", err)
}
sort.Slice(candidates, func(i, j int) bool {

View File

@@ -15,14 +15,19 @@ type reviewsQuery struct {
}
func parseReviewsQuery(c *gin.Context) (reviewsQuery, error) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil || id <= 0 {
return reviewsQuery{}, fmt.Errorf("invalid anime id")
rawID := c.Param("id")
id, err := strconv.Atoi(rawID)
if err != nil {
return reviewsQuery{}, fmt.Errorf("invalid anime id %q: %w", rawID, err)
}
if id <= 0 {
return reviewsQuery{}, fmt.Errorf("invalid anime id %d", id)
}
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
rawPage := c.DefaultQuery("page", "1")
page, err := strconv.Atoi(rawPage)
if err != nil {
return reviewsQuery{}, fmt.Errorf("invalid page")
return reviewsQuery{}, fmt.Errorf("invalid page %q: %w", rawPage, err)
}
if page < 1 {
page = 1

View File

@@ -47,19 +47,25 @@ func (s *animeService) GetCatalogSection(ctx context.Context, userID string, sec
case "Popular":
res, err = s.jikan.GetTopAnime(gCtx, 1)
}
return err
if err != nil {
return fmt.Errorf("get catalog section %q: %w", section, err)
}
return nil
})
if userID != "" && section == "Continue" {
g.Go(func() error {
var err error
cw, err = s.repo.GetContinueWatchingEntries(gCtx, userID)
return err
if err != nil {
return fmt.Errorf("get continue watching entries for %q: %w", userID, err)
}
return nil
})
}
if err := g.Wait(); err != nil {
return domain.CatalogSectionData{}, err
return domain.CatalogSectionData{}, fmt.Errorf("wait for catalog section %q: %w", section, err)
}
animes := wrapAnimes(res.Animes)
@@ -300,7 +306,7 @@ func (s *animeService) GetRandomAnime(ctx context.Context) (domain.Anime, error)
return domain.Anime{Anime: res.Animes[r.Intn(len(res.Animes))]}, nil
}
return domain.Anime{}, err
return domain.Anime{}, fmt.Errorf("get random anime: %w", err)
}
func (s *animeService) GetAllEpisodes(ctx context.Context, id int) ([]domain.EpisodeData, error) {