From 48a600572502116046dbc2587e632d67702ab70f Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 30 Mar 2026 20:08:16 -0400 Subject: [PATCH] feat(mal): add comprehensive error handling MALFunctions.go: - Update MALHelper to return (json.RawMessage, string, error) - Add network error handling and proper request error checking - Update GetMyAnimeList to return (MALWatchlist, error) - Update MyAnimeListUpdate to return (MalListStatus, error) - Update GetMyAnimeListAnime to return (MALAnime, error) - Update DeleteMyAnimeListEntry to return (bool, error) MALUserFunctions.go: - Replace log.Fatalf with log.Printf in server error handling - Prevent server shutdown on OAuth callback errors All MAL API calls now properly propagate errors to frontend. --- MALFunctions.go | 82 +++++++++++++++++++++++++++++++-------------- MALUserFunctions.go | 2 +- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/MALFunctions.go b/MALFunctions.go index 5c91f0d..416c45c 100644 --- a/MALFunctions.go +++ b/MALFunctions.go @@ -11,10 +11,19 @@ import ( "strings" ) -func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) { +func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) { client := &http.Client{} - req, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode())) + req, err := http.NewRequest(method, malUrl, strings.NewReader(body.Encode())) + + if err != nil { + message, _ := json.Marshal(struct { + Message string `json:"message"` + }{ + Message: "Failed to create request: " + err.Error(), + }) + return message, "", fmt.Errorf("failed to create request: %w", err) + } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken) @@ -22,47 +31,57 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, resp, err := client.Do(req) if err != nil { - fmt.Println("Errored when sending request to the server") message, _ := json.Marshal(struct { - Message string `json:"message" ts_type:"message"` + Message string `json:"message"` }{ - Message: "Errored when sending request to the server" + err.Error(), + Message: "Network error: " + err.Error(), }) - - return message, resp.Status + return message, "", fmt.Errorf("network error: %w", err) } defer resp.Body.Close() - respBody, _ := io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + + if err != nil { + return nil, resp.Status, fmt.Errorf("failed to read response: %w", err) + } if resp.Status == "401 Unauthorized" { refreshMyAnimeListAuthorizationToken() - MALHelper(method, malUrl, body) + return MALHelper(method, malUrl, body) } - return respBody, resp.Status + if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" { + return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status) + } + return respBody, resp.Status, nil } -func (a *App) GetMyAnimeList(count int) MALWatchlist { +func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) { limit := strconv.Itoa(count) user := a.GetMyAnimeListLoggedInUser() malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit var malList MALWatchlist - respBody, resStatus := MALHelper("GET", malUrl, nil) + respBody, resStatus, err := MALHelper("GET", malUrl, nil) + + if err != nil { + return malList, fmt.Errorf("failed to get MAL watchlist: %w", err) + } if resStatus == "200 OK" { err := json.Unmarshal(respBody, &malList) if err != nil { log.Printf("Failed to unmarshal json response, %s\n", err) + return malList, fmt.Errorf("failed to parse response: %w", err) } } - return malList + return malList, nil } -func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus { +func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) { if update.NumTimesRewatched >= 1 { update.IsRewatching = true } else { @@ -78,39 +97,52 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListS malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status" var status MalListStatus - respBody, respStatus := MALHelper("PATCH", malUrl, body) + respBody, respStatus, err := MALHelper("PATCH", malUrl, body) + + if err != nil { + return status, fmt.Errorf("failed to update MAL entry: %w", err) + } if respStatus == "200 OK" { err := json.Unmarshal(respBody, &status) if err != nil { log.Printf("Failed to unmarshal json response, %s\n", err) + return status, fmt.Errorf("failed to parse response: %w", err) } } - - return status + return status, nil } -func (a *App) GetMyAnimeListAnime(id int) MALAnime { +func (a *App) GetMyAnimeListAnime(id int) (MALAnime, error) { malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics" - respBody, respStatus := MALHelper("GET", malUrl, nil) var malAnime MALAnime + respBody, respStatus, err := MALHelper("GET", malUrl, nil) + + if err != nil { + return malAnime, fmt.Errorf("failed to get MAL anime: %w", err) + } if respStatus == "200 OK" { err := json.Unmarshal(respBody, &malAnime) if err != nil { log.Printf("Failed to unmarshal json response, %s\n", err) + return malAnime, fmt.Errorf("failed to parse response: %w", err) } } - - return malAnime + return malAnime, nil } -func (a *App) DeleteMyAnimeListEntry(id int) bool { +func (a *App) DeleteMyAnimeListEntry(id int) (bool, error) { malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status" - _, respStatus := MALHelper("DELETE", malUrl, nil) + _, respStatus, err := MALHelper("DELETE", malUrl, nil) + + if err != nil { + return false, fmt.Errorf("failed to delete MAL entry: %w", err) + } + if respStatus == "200 OK" { - return true + return true, nil } else { - return false + return false, fmt.Errorf("delete failed with status: %s", respStatus) } } diff --git a/MALUserFunctions.go b/MALUserFunctions.go index 284d9b1..0593dbb 100644 --- a/MALUserFunctions.go +++ b/MALUserFunctions.go @@ -176,7 +176,7 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi go func() { defer wg.Done() if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatalf("listen: %s\n", err) + log.Printf("Server error: %s\n", err) } fmt.Println("Shutting down...") }()