diff --git a/app/api/route.ts b/app/api/route.ts index f779a8af..d3dcde6d 100644 --- a/app/api/route.ts +++ b/app/api/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import { calculationSchema } from "../schemas/calculationSchema"; import * as calculationService from "../services/calculationService"; import calculateFairhold from "../models/testClasses"; +import { APIError } from "../lib/exceptions"; export async function POST(req: Request) { try { @@ -11,9 +12,22 @@ export async function POST(req: Request) { const householdData = await calculationService.getHouseholdData(input); const processedData = calculateFairhold(householdData); return NextResponse.json(processedData); - } catch (err) { - console.log("ERROR: API - ", (err as Error).message); - const response = { error: (err as Error).message }; + } catch (error) { + console.log("ERROR: API - ", (error as Error).message); + + if (error instanceof APIError) { + return NextResponse.json( + { error }, + { status: error.status }, + ); + } + + const response = { + error: { + message: (error as Error).message, + code: "UNHANDLED_EXCEPTION" + }, + }; return NextResponse.json(response, { status: 500 }); } } diff --git a/app/components/ui/CalculatorInput.tsx b/app/components/ui/CalculatorInput.tsx index 53c64c21..841234e3 100644 --- a/app/components/ui/CalculatorInput.tsx +++ b/app/components/ui/CalculatorInput.tsx @@ -21,6 +21,7 @@ import { import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { APIError } from "@/app/lib/exceptions"; type View = "form" | "loading" | "dashboard"; @@ -68,10 +69,31 @@ const CalculatorInput = () => { body: JSON.stringify(data), }); const processedData = await response.json(); + if (!response.ok) return handleErrors(processedData.error); + setData(processedData); setView("dashboard"); }; + const handleErrors = (error: APIError) => { + switch (error.code) { + case "ITL3_NOT_FOUND": + case "INSUFFICIENT_PRICES_PAID_DATA": + methods.setError( + "housePostcode", + { message: + "Insufficient data for this postcode. Please try again with a different postcode" + } + ); + break; + case "UNHANDLED_EXCEPTION": + default: + console.error(error) + }; + + setView("form"); + } + if (view === "form") { return (
diff --git a/app/data/itlRepo.test.ts b/app/data/itlRepo.test.ts index 21031a4e..fe005af1 100644 --- a/app/data/itlRepo.test.ts +++ b/app/data/itlRepo.test.ts @@ -46,7 +46,7 @@ describe("itlRepo", () => { await expect( itlRepo.getItl3ByPostcodeDistrict(postcodeDistrict) ).rejects.toThrow( - `Data error: Unable get get itl3 for postcode district ${postcodeDistrict}` + `Data error: Unable get itl3 for postcode district ${postcodeDistrict}` ); }); }); diff --git a/app/data/itlRepo.ts b/app/data/itlRepo.ts index 792e3670..1e2982e5 100644 --- a/app/data/itlRepo.ts +++ b/app/data/itlRepo.ts @@ -1,4 +1,5 @@ import prisma from "./db"; +import { APIError } from "../lib/exceptions"; const getItl3ByPostcodeDistrict = async ( postcodeDistrict: string @@ -15,10 +16,11 @@ const getItl3ByPostcodeDistrict = async ( return itl3; } catch (error) { - throw new Error( - `Data error: Unable get get itl3 for postcode district ${postcodeDistrict}` - ); - + throw new APIError({ + status: 500, + message: `Data error: Unable get itl3 for postcode district ${postcodeDistrict}`, + code: "ITL3_NOT_FOUND" + }); } }; diff --git a/app/data/pricesPaidRepo.ts b/app/data/pricesPaidRepo.ts index fac7a8f3..2dd8b995 100644 --- a/app/data/pricesPaidRepo.ts +++ b/app/data/pricesPaidRepo.ts @@ -1,3 +1,4 @@ +import { APIError } from "../lib/exceptions"; import prisma from "./db"; const MINIMUM_NUMBER_OF_POSTCODES = 30; @@ -32,7 +33,11 @@ const getPricesPaidByPostcodeAndHouseType = async ( }); if (!summary) { - throw new Error("Unable to find sufficient transaction data"); + throw new APIError({ + status: 500, + message: "Unable to find sufficient transaction data", + code: "INSUFFICIENT_PRICES_PAID_DATA", + }); } return { diff --git a/app/lib/exceptions.ts b/app/lib/exceptions.ts new file mode 100644 index 00000000..48ef7a13 --- /dev/null +++ b/app/lib/exceptions.ts @@ -0,0 +1,22 @@ +export type APIErrorCode = + | "ITL3_NOT_FOUND" + | "INSUFFICIENT_PRICES_PAID_DATA" + | "UNHANDLED_EXCEPTION"; + +interface APIErrorParams { + code: APIErrorCode; + message: string; + status?: number; +} + +export class APIError extends Error { + public code: APIErrorCode; + public status: number; + + constructor({ code, message, status = 500 }: APIErrorParams) { + super(message); + this.name = "APIError"; + this.code = code; + this.status = status; + } +} diff --git a/app/services/calculationService.ts b/app/services/calculationService.ts index 0ed97184..3f3539e2 100644 --- a/app/services/calculationService.ts +++ b/app/services/calculationService.ts @@ -10,6 +10,7 @@ import { socialRentEarningsService } from "./socialRentEarningsService"; import { rentService } from "./rentService" import { Calculation } from "../schemas/calculationSchema"; +import { APIError } from "../lib/exceptions"; const prisma = new PrismaClient(); @@ -63,6 +64,8 @@ export const getHouseholdData = async (input: Calculation) => { }; } catch (err) { + if (err instanceof APIError) throw err; + throw Error( `Service error: Unable to generate household. Message: ${(err as Error).message}` );