From c35c9b9e095bbd93136c77d0971da36d825bd7dc Mon Sep 17 00:00:00 2001 From: Rushil Perera Date: Fri, 7 Jun 2024 23:13:30 -0400 Subject: [PATCH] feat: add more error handling --- .../episodes/getByAniListId/index.spec.ts | 12 +-- .../episodes/getByAniListId/index.ts | 12 +-- src/controllers/search/index.spec.ts | 5 +- src/controllers/search/index.ts | 9 ++- src/controllers/title/amvstrm.ts | 76 ++++++++----------- src/controllers/title/anilist.ts | 9 ++- src/controllers/title/index.ts | 10 ++- src/mocks/amvstrm/search.ts | 2 +- src/mocks/anilist/search.ts | 2 +- 9 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/controllers/episodes/getByAniListId/index.spec.ts b/src/controllers/episodes/getByAniListId/index.spec.ts index 513c111..91146e9 100644 --- a/src/controllers/episodes/getByAniListId/index.spec.ts +++ b/src/controllers/episodes/getByAniListId/index.spec.ts @@ -29,8 +29,8 @@ describe('requests the "/episodes" route', () => { }, ); - expect(response.json()).resolves.toEqual({ success: false }); - expect(response.status).toBe(404); + expect(response.json()).resolves.toEqual({ success: true, result: [] }); + expect(response.status).toBe(200); }); it("Anify is disabled, returns no episode list from Anify", async () => { @@ -42,8 +42,8 @@ describe('requests the "/episodes" route', () => { }, ); - expect(response.json()).resolves.toEqual({ success: false }); - expect(response.status).toBe(404); + expect(response.json()).resolves.toEqual({ success: true, result: [] }); + expect(response.status).toBe(200); }); it("with list of episodes from Consumet", async () => { @@ -100,7 +100,7 @@ describe('requests the "/episodes" route', () => { it("with no episodes from all sources", async () => { const response = await app.request("/episodes/-1"); - expect(response.json()).resolves.toEqual({ success: false }); - expect(response.status).toBe(404); + expect(response.json()).resolves.toEqual({ success: true, result: [] }); + expect(response.status).toBe(200); }); }); diff --git a/src/controllers/episodes/getByAniListId/index.ts b/src/controllers/episodes/getByAniListId/index.ts index 38efc1e..b301349 100644 --- a/src/controllers/episodes/getByAniListId/index.ts +++ b/src/controllers/episodes/getByAniListId/index.ts @@ -33,13 +33,13 @@ const route = createRoute({ }, description: "Returns a list of episodes", }, - 404: { + 500: { content: { "application/json": { schema: ErrorResponseSchema, }, }, - description: "Returns an empty list because episodes not found", + description: "Error fetching episodes", }, }, }); @@ -49,7 +49,7 @@ const app = new OpenAPIHono(); app.openapi(route, async (c) => { const aniListId = Number(c.req.param("aniListId")); - const episodes = await fetchFromMultipleSources([ + const { result: episodes, errors } = await fetchFromMultipleSources([ () => { const isAnifyEnabled = readEnvVariable(c.env, "ENABLE_ANIFY"); return getEpisodesFromAnify(isAnifyEnabled, aniListId); @@ -64,13 +64,13 @@ app.openapi(route, async (c) => { ), ]); - if (!episodes) { - return c.json(ErrorResponse, { status: 404 }); + if (errors?.length > 0) { + return c.json(ErrorResponse, { status: 500 }); } return c.json({ success: true, - result: episodes, + result: episodes ?? [], }); }); diff --git a/src/controllers/search/index.spec.ts b/src/controllers/search/index.spec.ts index d406ca5..5de9336 100644 --- a/src/controllers/search/index.spec.ts +++ b/src/controllers/search/index.spec.ts @@ -19,12 +19,13 @@ describe('requests the "/search" route', () => { }); it("query that returns no results", async () => { - const response = await app.request("/search?query="); + const response = await app.request("/search?query=a"); expect(response.json()).resolves.toEqual({ - success: false, + success: true, results: [], hasNextPage: false, }); + expect(response.status).toBe(200); }); }); diff --git a/src/controllers/search/index.ts b/src/controllers/search/index.ts index cddc6c4..a602c53 100644 --- a/src/controllers/search/index.ts +++ b/src/controllers/search/index.ts @@ -39,12 +39,17 @@ app.openapi(route, async (c) => { const page = Number(c.req.query("page") ?? 1); const limit = Number(c.req.query("limit") ?? 10); - const response = await fetchFromMultipleSources([ + const { result: response, errors } = await fetchFromMultipleSources([ () => fetchSearchResultsFromAnilist(query, page, limit), () => fetchSearchResultsFromAmvstrm(query, page, limit), ]); + if (!response) { - return c.json({ success: false, results: [], hasNextPage: false }, 200); + return c.json({ + success: (errors ?? []).length === 0, + results: [], + hasNextPage: false, + }); } return c.json( diff --git a/src/controllers/title/amvstrm.ts b/src/controllers/title/amvstrm.ts index 8756dd7..ddddb6a 100644 --- a/src/controllers/title/amvstrm.ts +++ b/src/controllers/title/amvstrm.ts @@ -11,52 +11,38 @@ export async function fetchTitleFromAmvstrm( console.error("Failed to get missing information from Anify", err); return null; }), - ]).then( - async ([ - { - id, - idMal, - title: { english: englishTitle, userPreferred: userPreferredTitle }, - description, - episodes, - genres, - status, - bannerImage, - coverImage: { - extraLarge: extraLargeCoverImage, - large: largeCoverImage, - medium: mediumCoverImage, - }, - countryOfOrigin, - nextair: nextAiringEpisode, - score: { averageScore }, + ]).then(async ([amvstrmInfo, anifyInfo]) => { + if (amvstrmInfo.code >= 400) { + console.error( + `Error trying to load title from amvstrm; aniListId: ${aniListId}, code: ${amvstrmInfo.code}, message: ${amvstrmInfo.message}`, + ); + return undefined; + } + + return { + id: amvstrmInfo.id, + idMal: amvstrmInfo.idMal, + title: { + userPreferred: amvstrmInfo.title.userPreferred, + english: amvstrmInfo.title.english, }, - anifyInfo, - ]) => { - return { - id, - idMal, - title: { - userPreferred: userPreferredTitle, - english: englishTitle, - }, - description, - episodes, - genres, - status, - averageScore, - bannerImage: bannerImage ?? anifyInfo?.bannerImage, - coverImage: { - extraLarge: extraLargeCoverImage, - large: largeCoverImage, - medium: mediumCoverImage, - }, - countryOfOrigin: countryOfOrigin ?? anifyInfo?.countryOfOrigin, - nextAiringEpisode, - mediaListEntry: null, - }; - }, - ); + description: amvstrmInfo.description, + episodes: amvstrmInfo.episodes, + genres: amvstrmInfo.genres, + status: amvstrmInfo.status, + averageScore: amvstrmInfo.score.averageScore, + bannerImage: amvstrmInfo.bannerImage ?? anifyInfo?.bannerImage, + coverImage: { + extraLarge: amvstrmInfo.coverImage.extraLarge, + large: amvstrmInfo.coverImage.large, + medium: amvstrmInfo.coverImage.medium, + }, + countryOfOrigin: + amvstrmInfo.countryOfOrigin ?? anifyInfo?.countryOfOrigin, + nextAiringEpisode: amvstrmInfo.nextair, + mediaListEntry: null, + }; + }); } type AnifyInformation = { diff --git a/src/controllers/title/anilist.ts b/src/controllers/title/anilist.ts index fb8fd21..172357c 100644 --- a/src/controllers/title/anilist.ts +++ b/src/controllers/title/anilist.ts @@ -28,5 +28,12 @@ export async function fetchTitleFromAnilist( return client .request(GetTitleQuery, { id }, headers) - .then((data) => data?.Media ?? undefined); + .then((data) => data?.Media ?? undefined) + .catch((error) => { + if (error.message.includes("Not Found")) { + return undefined; + } + + throw error; + }); } diff --git a/src/controllers/title/index.ts b/src/controllers/title/index.ts index 37a7ed1..d3d56c6 100644 --- a/src/controllers/title/index.ts +++ b/src/controllers/title/index.ts @@ -3,6 +3,7 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; import { fetchFromMultipleSources } from "~/libs/fetchFromMultipleSources"; import { AniListIdQuerySchema, + ErrorResponse, ErrorResponseSchema, SuccessResponseSchema, } from "~/types/schema"; @@ -47,12 +48,17 @@ app.openapi(route, async (c) => { const aniListId = Number(c.req.query("id")); const aniListToken = c.req.header("X-AniList-Token"); - const title = await fetchFromMultipleSources([ + const { result: title, errors } = await fetchFromMultipleSources([ () => fetchTitleFromAnilist(aniListId, aniListToken ?? undefined), () => fetchTitleFromAmvstrm(aniListId), ]); + + if (errors?.length > 0) { + return c.json(ErrorResponse, { status: 500 }); + } + if (!title) { - return c.json({ success: false }, 404); + return c.json(ErrorResponse, 404); } return c.json({ success: true, result: title }, 200); diff --git a/src/mocks/amvstrm/search.ts b/src/mocks/amvstrm/search.ts index a02c994..b79e0ca 100644 --- a/src/mocks/amvstrm/search.ts +++ b/src/mocks/amvstrm/search.ts @@ -7,7 +7,7 @@ export function getAmvstrmSearchResults() { const url = new URL(urlString); const query = url.searchParams.get("q"); - if (!query) { + if (!query || query === "a") { return HttpResponse.json({ code: 200, message: "success", diff --git a/src/mocks/anilist/search.ts b/src/mocks/anilist/search.ts index 979aaee..7d43c49 100644 --- a/src/mocks/anilist/search.ts +++ b/src/mocks/anilist/search.ts @@ -4,7 +4,7 @@ export function getAnilistSearchResults() { return graphql.query("Search", ({ variables: { query, page } }) => { console.log(`Intercepting Search query with ${query} and page ${page}`); - if (!query || query === "amvstrm") { + if (!query || query === "a" || query === "amvstrm") { return HttpResponse.json({ data: { Page: {