Skip to content

Commit e69bebf

Browse files
committed
consistent error handler and user invitation
1 parent a8fd2de commit e69bebf

File tree

7 files changed

+286
-26
lines changed

7 files changed

+286
-26
lines changed

src/lib/components/BankSelectWidget.svelte

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
import { Building2, ChevronDown } from "@lucide/svelte";
33
import { onMount } from "svelte";
44
import { trackedFetch } from "$lib/utils/trackedFetch";
5+
import {
6+
extractErrorFromResponse,
7+
formatErrorForDisplay,
8+
logErrorDetails,
9+
} from "$lib/utils/errorHandler";
510
611
interface Bank {
712
id: string;
@@ -43,7 +48,13 @@
4348
const response = await trackedFetch("/api/banks");
4449
4550
if (!response.ok) {
46-
throw new Error("Failed to fetch banks");
51+
const errorDetails = await extractErrorFromResponse(
52+
response,
53+
"Failed to fetch banks",
54+
);
55+
logErrorDetails("Fetch Banks", errorDetails);
56+
error = formatErrorForDisplay(errorDetails);
57+
return;
4758
}
4859
4960
const data = await response.json();

src/lib/components/UserSearchWidget.svelte

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<script lang="ts">
22
import { Search, User, X } from "@lucide/svelte";
33
import { trackedFetch } from "$lib/utils/trackedFetch";
4+
import {
5+
extractErrorFromResponse,
6+
formatErrorForDisplay,
7+
logErrorDetails,
8+
} from "$lib/utils/errorHandler";
49
510
interface UserResult {
611
user_id: string;
@@ -47,7 +52,14 @@
4752
);
4853
4954
if (!response.ok) {
50-
throw new Error("Failed to search users");
55+
const errorDetails = await extractErrorFromResponse(
56+
response,
57+
"Failed to search users",
58+
);
59+
logErrorDetails("Search Users", errorDetails);
60+
searchError = formatErrorForDisplay(errorDetails);
61+
searchResults = [];
62+
return;
5163
}
5264
5365
const data = await response.json();

src/lib/utils/errorHandler.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* Error Handler Utility
3+
*
4+
* Provides consistent error extraction and formatting across the application.
5+
* For API Manager, we want to show FULL error details to users since this is an admin tool.
6+
*/
7+
8+
export interface OBPErrorDetails {
9+
message: string;
10+
code?: string;
11+
status?: number;
12+
fullError?: any;
13+
}
14+
15+
/**
16+
* Extracts full OBP error details from a fetch Response
17+
*
18+
* This function attempts to parse JSON error responses and extract:
19+
* - error.message or error.error field
20+
* - OBP error code (if present)
21+
* - HTTP status code
22+
* - Full error object for logging
23+
*
24+
* @param response - The fetch Response object
25+
* @param fallbackMessage - Default message if parsing fails
26+
* @returns Promise<OBPErrorDetails>
27+
*/
28+
export async function extractErrorFromResponse(
29+
response: Response,
30+
fallbackMessage: string = "Operation failed"
31+
): Promise<OBPErrorDetails> {
32+
const details: OBPErrorDetails = {
33+
message: fallbackMessage,
34+
status: response.status,
35+
};
36+
37+
try {
38+
// Try to parse JSON error response
39+
const errorData = await response.json();
40+
41+
// Extract error message (try multiple possible fields)
42+
details.message =
43+
errorData.error ||
44+
errorData.message ||
45+
errorData.error_message ||
46+
fallbackMessage;
47+
48+
// Extract OBP error code if present
49+
if (errorData.code) {
50+
details.code = errorData.code;
51+
} else if (errorData.error_code) {
52+
details.code = errorData.error_code;
53+
} else if (errorData.obpErrorCode) {
54+
details.code = errorData.obpErrorCode;
55+
}
56+
57+
// Store full error for logging
58+
details.fullError = errorData;
59+
60+
// If we have an error code, prepend it to the message
61+
if (details.code && !details.message.includes(details.code)) {
62+
details.message = `${details.code}: ${details.message}`;
63+
}
64+
65+
} catch (parseError) {
66+
// If JSON parsing fails, try to get text response
67+
try {
68+
const text = await response.text();
69+
if (text) {
70+
details.message = `${fallbackMessage} (${response.status} ${response.statusText}): ${text}`;
71+
} else {
72+
details.message = `${fallbackMessage}: ${response.status} ${response.statusText}`;
73+
}
74+
} catch (textError) {
75+
// If even text parsing fails, use status info
76+
details.message = `${fallbackMessage}: ${response.status} ${response.statusText}`;
77+
}
78+
}
79+
80+
return details;
81+
}
82+
83+
/**
84+
* Extracts error message from a caught exception
85+
*
86+
* @param error - The caught error/exception
87+
* @param fallbackMessage - Default message if extraction fails
88+
* @returns string - The error message
89+
*/
90+
export function extractErrorMessage(
91+
error: unknown,
92+
fallbackMessage: string = "An error occurred"
93+
): string {
94+
if (error instanceof Error) {
95+
return error.message;
96+
}
97+
98+
if (typeof error === "string") {
99+
return error;
100+
}
101+
102+
if (error && typeof error === "object") {
103+
const err = error as any;
104+
return err.message || err.error || err.error_message || fallbackMessage;
105+
}
106+
107+
return fallbackMessage;
108+
}
109+
110+
/**
111+
* Formats error details for display to user
112+
*
113+
* @param details - OBPErrorDetails object
114+
* @returns string - Formatted error message
115+
*/
116+
export function formatErrorForDisplay(details: OBPErrorDetails): string {
117+
let message = details.message;
118+
119+
// Add status code if not already in message
120+
if (details.status && !message.includes(details.status.toString())) {
121+
message = `${message} (Status: ${details.status})`;
122+
}
123+
124+
return message;
125+
}
126+
127+
/**
128+
* Logs error details to console with full information
129+
*
130+
* @param context - Context string (e.g., "Delete User", "Create Entity")
131+
* @param details - OBPErrorDetails object
132+
*/
133+
export function logErrorDetails(context: string, details: OBPErrorDetails): void {
134+
console.error(`[${context}] Error occurred:`);
135+
console.error(` Message: ${details.message}`);
136+
if (details.code) {
137+
console.error(` Code: ${details.code}`);
138+
}
139+
if (details.status) {
140+
console.error(` Status: ${details.status}`);
141+
}
142+
if (details.fullError) {
143+
console.error(` Full Error:`, details.fullError);
144+
}
145+
}
146+
147+
/**
148+
* Complete error handler for fetch operations
149+
* Extracts error, logs it, and returns formatted message
150+
*
151+
* @param response - The fetch Response object
152+
* @param context - Context string for logging
153+
* @param fallbackMessage - Default message if parsing fails
154+
* @returns Promise<string> - Formatted error message ready to display
155+
*/
156+
export async function handleFetchError(
157+
response: Response,
158+
context: string,
159+
fallbackMessage: string = "Operation failed"
160+
): Promise<string> {
161+
const details = await extractErrorFromResponse(response, fallbackMessage);
162+
logErrorDetails(context, details);
163+
return formatErrorForDisplay(details);
164+
}
165+
166+
/**
167+
* Wrapper for fetch calls with automatic error handling
168+
*
169+
* Usage:
170+
* ```typescript
171+
* const result = await fetchWithErrorHandling(
172+
* '/api/endpoint',
173+
* { method: 'POST', body: JSON.stringify(data) },
174+
* 'Create Record'
175+
* );
176+
*
177+
* if (result.success) {
178+
* // Use result.data
179+
* } else {
180+
* // Show result.error to user
181+
* }
182+
* ```
183+
*/
184+
export async function fetchWithErrorHandling<T = any>(
185+
url: string,
186+
options: RequestInit,
187+
context: string
188+
): Promise<{ success: true; data: T } | { success: false; error: string }> {
189+
try {
190+
const response = await fetch(url, options);
191+
192+
if (!response.ok) {
193+
const errorMessage = await handleFetchError(response, context);
194+
return { success: false, error: errorMessage };
195+
}
196+
197+
const data = await response.json();
198+
return { success: true, data };
199+
200+
} catch (error) {
201+
const errorMessage = extractErrorMessage(error, "Network error occurred");
202+
console.error(`[${context}] Exception:`, error);
203+
return { success: false, error: errorMessage };
204+
}
205+
}

src/routes/(protected)/dynamic-entities/system/+page.svelte

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<script lang="ts">
22
import { goto } from "$app/navigation";
33
import type { PageData } from "./$types";
4+
import {
5+
extractErrorFromResponse,
6+
formatErrorForDisplay,
7+
logErrorDetails,
8+
} from "$lib/utils/errorHandler";
49
510
export let data: PageData;
611
@@ -62,27 +67,23 @@
6267
});
6368
6469
if (!response.ok) {
65-
let errorMessage = "Failed to delete entity";
66-
try {
67-
const errorData = await response.json();
68-
errorMessage = errorData.error || errorData.message || errorMessage;
69-
console.error("Delete error response:", errorData);
70-
} catch (e) {
71-
const text = await response.text();
72-
console.error("Delete error text:", text);
73-
errorMessage = `${errorMessage}: ${response.status} ${response.statusText}`;
74-
}
70+
const errorDetails = await extractErrorFromResponse(
71+
response,
72+
"Failed to delete entity",
73+
);
74+
logErrorDetails("Delete System Dynamic Entity", errorDetails);
75+
const errorMessage = formatErrorForDisplay(errorDetails);
7576
throw new Error(errorMessage);
7677
}
7778
7879
alert("System dynamic entity deleted successfully");
7980
window.location.reload();
8081
} catch (error) {
81-
const fullMessage =
82+
const errorMsg =
8283
error instanceof Error
8384
? error.message
8485
: "Failed to delete system dynamic entity";
85-
alert(`Failed to delete system dynamic entity:\n\n${fullMessage}`);
86+
alert(`Error: ${errorMsg}`);
8687
console.error("Delete error:", error);
8788
}
8889
}

src/routes/(protected)/dynamic-entities/system/[id]/+page.svelte

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<script lang="ts">
22
import { goto } from "$app/navigation";
33
import type { PageData } from "./$types";
4+
import {
5+
extractErrorFromResponse,
6+
formatErrorForDisplay,
7+
logErrorDetails,
8+
} from "$lib/utils/errorHandler";
49
510
export let data: PageData;
611
@@ -51,13 +56,21 @@
5156
);
5257
5358
if (!response.ok) {
54-
throw new Error("Failed to delete entity");
59+
const errorDetails = await extractErrorFromResponse(
60+
response,
61+
"Failed to delete entity",
62+
);
63+
logErrorDetails("Delete Dynamic Entity", errorDetails);
64+
const errorMessage = formatErrorForDisplay(errorDetails);
65+
throw new Error(errorMessage);
5566
}
5667
5768
alert("System dynamic entity deleted successfully");
5869
goto("/dynamic-entities/system");
5970
} catch (error) {
60-
alert(error instanceof Error ? error.message : "Failed to delete entity");
71+
const errorMsg =
72+
error instanceof Error ? error.message : "Failed to delete entity";
73+
alert(`Error: ${errorMsg}`);
6174
console.error("Delete error:", error);
6275
}
6376
}

src/routes/(protected)/dynamic-entities/system/create/+page.svelte

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<script lang="ts">
22
import { goto } from "$app/navigation";
3+
import {
4+
extractErrorFromResponse,
5+
formatErrorForDisplay,
6+
logErrorDetails,
7+
} from "$lib/utils/errorHandler";
38
49
let entityName = $state("");
510
let entityDescription = $state("");
@@ -78,16 +83,21 @@
7883
});
7984
8085
if (!response.ok) {
81-
const error = await response.json();
82-
throw new Error(error.error || error.message || "Failed to create entity");
86+
const errorDetails = await extractErrorFromResponse(
87+
response,
88+
"Failed to create entity",
89+
);
90+
logErrorDetails("Create System Dynamic Entity", errorDetails);
91+
const errorMessage = formatErrorForDisplay(errorDetails);
92+
throw new Error(errorMessage);
8393
}
8494
8595
alert("System dynamic entity created successfully");
8696
goto("/dynamic-entities/system");
8797
} catch (error) {
88-
alert(
89-
error instanceof Error ? error.message : "Failed to create entity",
90-
);
98+
const errorMsg =
99+
error instanceof Error ? error.message : "Failed to create entity";
100+
alert(`Error: ${errorMsg}`);
91101
console.error("Create error:", error);
92102
} finally {
93103
isSubmitting = false;
@@ -207,9 +217,7 @@
207217
<h3 class="text-sm font-semibold text-blue-900 dark:text-blue-100">
208218
Schema Format Guide
209219
</h3>
210-
<ul
211-
class="mt-2 space-y-1 text-xs text-blue-800 dark:text-blue-200"
212-
>
220+
<ul class="mt-2 space-y-1 text-xs text-blue-800 dark:text-blue-200">
213221
<li>
214222
<strong>properties:</strong> Define fields with type, description, and
215223
validation rules

0 commit comments

Comments
 (0)