'Genericise' errors ready for +error.svelte upgrade to handle known errors

This commit is contained in:
2026-05-16 18:19:47 +01:00
parent 808b576df1
commit b51845528f
7 changed files with 97 additions and 60 deletions

View File

@@ -1,11 +1,11 @@
import type { HandleClientError } from "@sveltejs/kit"; import type { HandleClientError } from '@sveltejs/kit';
export const handleError: HandleClientError = ({ error }) => { export const handleError: HandleClientError = ({ error }) => {
const err = error as any; const err = error as any;
return { return {
message: err?.message || 'An unexpected application error occurred.', message: err?.message || 'An unexpected application error occurred.',
code: err?.code || 'CRITICAL', code: err?.code || 'CRITICAL',
msg: err?.msg || 'UNHANDLED_EXCEPTION' name: err?.name || 'UNHANDLED_EXCEPTION'
}; };
}; };

View File

@@ -1,5 +1,6 @@
import { OwlBoardClient, ValidationError, ApiError } from '@owlboard/owlboard-ts'; import { OwlBoardClient, ValidationError, ApiError } from '@owlboard/owlboard-ts';
import { browser, dev } from '$app/environment'; import { browser, dev } from '$app/environment';
import { error } from '@sveltejs/kit';
// Import the runes containing the API Key config Here... // Import the runes containing the API Key config Here...
@@ -18,6 +19,46 @@ export const OwlClient = new OwlBoardClient(
// API Key Here when ready!!! // API Key Here when ready!!!
); );
export function ThrowApiError(e: any): void {} export function ThrowApiError(e: unknown): never {
// Handle Request failure
if (e instanceof TypeError && e.message === 'Failed to fetch') {
throw error(503, {
message: 'Unable to connect to the OwlBoard server',
name: 'NETWORK_ERROR',
code: 'NETWORK_UNREACHABLE'
});
}
// Map ApiError 'code' values
if (e instanceof ApiError) {
let status = 500;
if (e.code === 'AUTH') {
status = 401;
} else if (e.code === 'NOT_FOUND') {
status = 404;
} else if (e.code === 'RATE_LIMIT') {
status = 421;
} else if (e.code === 'NETWORK_DISCONNECTED') {
status = 503;
} else if (e.code === 'VALIDATION') {
status = 400;
}
throw error(status, {
message: e.message || 'An operational error has occurred',
name: e.name || 'API_ERROR',
code: e.code || 'UNKNOWN_API_ERROR'
});
}
// Fallback for other error kind
const genericMessage = e instanceof Error ? e.message : 'An unexpected error occurred.';
throw error(500, {
message: genericMessage,
name: 'CRITICAL_ERROR',
code: 'INTERNAL_ERROR',
msg: 'UNHANDLED_EXCEPTION'
});
}
export { ValidationError, ApiError }; export { ValidationError, ApiError };

View File

@@ -9,8 +9,8 @@
<!-- Will need to check error type, using the upstream code combined with response code --> <!-- Will need to check error type, using the upstream code combined with response code -->
{#if page.error} {#if page.error}
{JSON.stringify(page.error)} {JSON.stringify(page.error)}
STATUS: {page.status} STATUS: {page.status}
{/if} {/if}
<div class="error-wrapper"> <div class="error-wrapper">

View File

@@ -15,7 +15,13 @@
import logoPlain from '$lib/assets/round-logo.svg'; import logoPlain from '$lib/assets/round-logo.svg';
import favicon from '$lib/assets/round-logo.svg'; import favicon from '$lib/assets/round-logo.svg';
import { IconHome, IconDialpad, IconSettings, IconHelp, IconDots } from '@tabler/icons-svelte-runes'; import {
IconHome,
IconDialpad,
IconSettings,
IconHelp,
IconDots
} from '@tabler/icons-svelte-runes';
onMount(async () => { onMount(async () => {
LOCATIONS.init(); LOCATIONS.init();

View File

@@ -11,19 +11,15 @@
</script> </script>
<div class="logo-container"> <div class="logo-container">
<button <button
type="button" type="button"
class="logo-btn" class="logo-btn"
onclick={handleLogoTap} onclick={handleLogoTap}
class:animate={isSpinning} class:animate={isSpinning}
aria-label="Spin the OwlBoard logo" aria-label="Spin the OwlBoard logo"
> >
<img <img class="logo-img" src={logo} alt="OwlBoard Logo" />
class="logo-img" </button>
src={logo}
alt="OwlBoard Logo"
/>
</button>
</div> </div>
<section class="about"> <section class="about">
@@ -66,17 +62,15 @@
} }
.logo-btn { .logo-btn {
background: none; background: none;
border: none; border: none;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
padding-top: 25px; padding-top: 25px;
margin: auto; margin: auto;
width: clamp(80px, 20vw, 200px); width: clamp(80px, 20vw, 200px);
} }
@keyframes owl-spin { @keyframes owl-spin {
0% { 0% {
@@ -91,14 +85,14 @@
} }
.logo-img { .logo-img {
width: 100%; width: 100%;
height: auto; height: auto;
display: block; display: block;
} }
.logo-btn.animate { .logo-btn.animate {
animation: owl-spin 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; animation: owl-spin 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
} }
section { section {
margin: auto; margin: auto;

View File

@@ -25,7 +25,10 @@
console.log(e); console.log(e);
errorState = { status: 20, message: e.message }; errorState = { status: 20, message: e.message };
} else { } else {
errorState = { status: 0, message: `Unknown Error: ${e instanceof Error ? e.message : String(e)}` }; errorState = {
status: 0,
message: `Unknown Error: ${e instanceof Error ? e.message : String(e)}`
};
} }
} finally { } finally {
resultsLoaded = true; resultsLoaded = true;
@@ -45,7 +48,10 @@
console.log(e); console.log(e);
errorState = { status: 20, message: e.message }; errorState = { status: 20, message: e.message };
} else { } else {
errorState = { status: 0, message: `Unknown Error: ${e instanceof Error ? e.message : String(e)}` }; errorState = {
status: 0,
message: `Unknown Error: ${e instanceof Error ? e.message : String(e)}`
};
} }
} finally { } finally {
resultsLoaded = true; resultsLoaded = true;

View File

@@ -1,4 +1,4 @@
import { OwlClient, ApiError, ValidationError } from '$lib/owlClient'; import { OwlClient, ApiError, ValidationError, ThrowApiError } from '$lib/owlClient';
import type { ApiTrainsTrainByHeadcode } from '@owlboard/owlboard-ts'; import type { ApiTrainsTrainByHeadcode } from '@owlboard/owlboard-ts';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
@@ -10,31 +10,21 @@ export const load: PageLoad = async ({ fetch, url }) => {
const date: string | Date = dateParam === '' || dateParam === null ? new Date() : dateParam; const date: string | Date = dateParam === '' || dateParam === null ? new Date() : dateParam;
if (!headcode) { // Validation removed, handled by the API Client Lib
throw error(400, {
message: 'Headcode not provided',
owlCode: 'INVALID_DATA'
});
}
// Declared outside of the try so that it can be used in both the try and catch blocks // Declared outside of the try so that it can be used in both the try and catch blocks
let results: ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]; let results: ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[];
try { try {
const response = await OwlClient.trains.getByHeadcode(headcode, date, toc, fetch); const response = await OwlClient.trains.getByHeadcode(headcode || '', date, toc, fetch);
let title = !!headcode ? headcode.toUpperCase() : 'Error';
results = response.data; results = response.data;
return { return {
title: headcode.toUpperCase(), title: title,
results: results results: results
}; };
} catch (e: unknown) { } catch (e: unknown) {
if (e instanceof TypeError && e.message === 'Failed to fetch') { ThrowApiError(e);
error(503, {
message: 'Cannot connect to the OwlBoard server',
owlCode: 'NETWORK_DISCONNECTED'
});
}
throw e;
} }
}; };