improve error handling with 'builder'-type structure

This commit is contained in:
Elijah Duffy
2026-02-13 16:28:25 -08:00
parent 3b659c1e2d
commit 1c66bc0fcf
2 changed files with 60 additions and 30 deletions

View File

@@ -1,18 +1,28 @@
<script lang="ts"> <script lang="ts">
import type { ClassValue } from 'svelte/elements'; import type { ClassValue } from 'svelte/elements';
import type { ErrorMessage } from './error'; import { ErrorMessage, type RawError } from './error';
interface Props { interface Props {
error: ErrorMessage | null; /** Error in the form of an ErrorMessage */
error?: ErrorMessage | null;
/** Raw error that can be converted to an ErrorMessage */
rawError?: RawError | null;
/** Additional CSS classes for the error box */
class?: ClassValue | null; class?: ClassValue | null;
} }
let { error, class: classValue }: Props = $props(); let { error, rawError, class: classValue }: Props = $props();
let errorMessage = $derived.by(() => {
if (error) return error;
if (rawError) return new ErrorMessage(rawError);
});
</script> </script>
{#if error} {#if errorMessage && errorMessage.hasError()}
<!-- eslint-disable svelte/no-at-html-tags -->
<div class={['bg-sui-accent text-sui-background my-4 rounded-xs px-6 py-4', classValue]}> <div class={['bg-sui-accent text-sui-background my-4 rounded-xs px-6 py-4', classValue]}>
{@html error.message} {#each errorMessage.lines as line}
<p>{line}</p>
{/each}
</div> </div>
{/if} {/if}

View File

@@ -10,20 +10,41 @@ export interface GraphError {
export type RawError = Error | string | GraphError[]; export type RawError = Error | string | GraphError[];
export class ErrorMessage { export class ErrorMessage {
private _message: string; private _lines: string[] = [];
/** converts a RawError to a string and stores it for later access */ /**
constructor(raw: RawError) { * Converts a RawError to an array of lines and stores it for later access,
this._message = ErrorMessage.rawErrorToString(raw); * or initializes without any errors if the input is null or undefined.
* @param raw The raw error to convert and store, or null/undefined for no error.
* @throws If the raw error is of an unsupported type.
*/
constructor(raw: RawError | null | undefined) {
if (raw) {
this._lines = ErrorMessage.rawErrorToLines(raw);
}
} }
/** returns the stored message */ /** returns the stored lines */
get message(): string { get lines(): string[] {
return this._message; return this._lines;
} }
/** returns the error as a string */ /** returns the error lines as a string, separated by newlines */
toString(): string { toString(): string {
return this._message; return this._lines.join('\n');
}
/** returns the error lines as an HTML string, separated by <br /> */
toHTML(): string {
return this._lines.join('<br />');
}
/** returns true if there are any error lines */
hasError(): boolean {
return this._lines.length > 0;
}
/** adds a new line to the error message */
addLine(line: string): void {
this._lines.push(line);
} }
/** optionally returns a new ErrorMessage only if the RawError is not empty */ /** optionally returns a new ErrorMessage only if the RawError is not empty */
@@ -32,28 +53,27 @@ export class ErrorMessage {
return new ErrorMessage(raw); return new ErrorMessage(raw);
} }
/** converts a RawError to a string */ /** converts a RawError to an array of lines */
static rawErrorToString(raw: RawError | null | undefined): string { static rawErrorToLines(raw: RawError | null | undefined): string[] {
if (!raw) return 'No error'; if (!raw) return ['No error'];
let errorString: string; let errorLines: string[];
if (typeof raw === 'string') { if (typeof raw === 'string') {
errorString = raw; errorLines = [raw];
} else if (raw instanceof Error) { } else if (raw instanceof Error) {
errorString = raw.message; errorLines = [raw.message];
} else if (Array.isArray(raw)) { } else if (Array.isArray(raw)) {
errorString = raw errorLines = raw.map((e) => {
.flatMap((e) => {
const messageString = e.message || 'Unknown error'; const messageString = e.message || 'Unknown error';
if (e.path && e.path.length > 0) { if (e.path && e.path.length > 0) {
return `"${messageString}" at ${e.path.join('.')}`; return `"${messageString}" at ${e.path.join('.')}`;
} }
}) return messageString;
.join('<br />'); });
} else { } else {
throw `Bad error value ${raw}`; throw `Bad error value ${raw}`;
} }
return errorString; return errorLines;
} }
} }