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">
import type { ClassValue } from 'svelte/elements';
import type { ErrorMessage } from './error';
import { ErrorMessage, type RawError } from './error';
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;
}
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>
{#if error}
<!-- eslint-disable svelte/no-at-html-tags -->
{#if errorMessage && errorMessage.hasError()}
<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>
{/if}

View File

@@ -10,20 +10,41 @@ export interface GraphError {
export type RawError = Error | string | GraphError[];
export class ErrorMessage {
private _message: string;
private _lines: string[] = [];
/** converts a RawError to a string and stores it for later access */
constructor(raw: RawError) {
this._message = ErrorMessage.rawErrorToString(raw);
/**
* Converts a RawError to an array of lines and stores it for later access,
* 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 */
get message(): string {
return this._message;
/** returns the stored lines */
get lines(): string[] {
return this._lines;
}
/** returns the error as a string */
/** returns the error lines as a string, separated by newlines */
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 */
@@ -32,28 +53,27 @@ export class ErrorMessage {
return new ErrorMessage(raw);
}
/** converts a RawError to a string */
static rawErrorToString(raw: RawError | null | undefined): string {
if (!raw) return 'No error';
/** converts a RawError to an array of lines */
static rawErrorToLines(raw: RawError | null | undefined): string[] {
if (!raw) return ['No error'];
let errorString: string;
let errorLines: string[];
if (typeof raw === 'string') {
errorString = raw;
errorLines = [raw];
} else if (raw instanceof Error) {
errorString = raw.message;
errorLines = [raw.message];
} else if (Array.isArray(raw)) {
errorString = raw
.flatMap((e) => {
errorLines = raw.map((e) => {
const messageString = e.message || 'Unknown error';
if (e.path && e.path.length > 0) {
return `"${messageString}" at ${e.path.join('.')}`;
}
})
.join('<br />');
return messageString;
});
} else {
throw `Bad error value ${raw}`;
}
return errorString;
return errorLines;
}
}