feat: Implement typed error contracts in generic hooks
Introduce discriminated FetchError union type to replace weak string error handling in API calls and hooks. Enables actionable error diagnostics. Changes: - Create types/api.ts with FetchError discriminated union (api_error, network_error, abort_error) - Export type guards: isAuthError, isAbortError, isNetworkError, isApiError - Update useListData and usePolledData to expose typed FetchError instead of string - Add getErrorMessage() helper to extract displayable messages from FetchError - Add createStringErrorAdapter() for backward compatibility with string error state - Update handleFetchError() to work with both FetchError and string setters - Update all consumer hooks to expose typed errors - Update components to use getErrorMessage() when displaying errors - Update tests to mock FetchError instead of strings - Add comprehensive typed error model documentation to Web-Development.md This enables better error handling patterns: - Check error.type to distinguish between API, network, and abort errors - Extract status codes for specific handling (401/403 auth, 50x server errors) - Maintain backward compatibility with existing string-based error states All TypeScript compilation passes with no errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
106
frontend/src/types/api.ts
Normal file
106
frontend/src/types/api.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Typed API error contracts and models.
|
||||
*
|
||||
* Provides discriminated error types for standardized error handling across
|
||||
* API calls and hooks, enabling actionable error reporting and diagnostics.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discriminated error type representing an HTTP API error response.
|
||||
*
|
||||
* Thrown when the server returns a non-2xx HTTP status code.
|
||||
* Use the `type` discriminator to handle different error categories.
|
||||
*/
|
||||
export interface ApiErrorPayload {
|
||||
type: "api_error";
|
||||
/** HTTP status code returned by the server. */
|
||||
status: number;
|
||||
/** Raw response body text as returned by the server. */
|
||||
body: string;
|
||||
/** User-friendly error message derived from status and body. */
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discriminated error type representing a network error.
|
||||
*
|
||||
* Thrown when the request fails due to network issues (DNS lookup failure,
|
||||
* connection timeout, offline, CORS error, etc.) or when parsing JSON fails.
|
||||
*/
|
||||
export interface NetworkErrorPayload {
|
||||
type: "network_error";
|
||||
/** Underlying error message (network stack or JSON parse error). */
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discriminated error type representing a request abort.
|
||||
*
|
||||
* Thrown when a fetch request is aborted (e.g., component unmounts, user cancels).
|
||||
* These are expected and should typically be silently ignored.
|
||||
*/
|
||||
export interface AbortErrorPayload {
|
||||
type: "abort_error";
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of all possible fetch error types.
|
||||
*
|
||||
* Use the `type` discriminator to narrow and handle each error case:
|
||||
*
|
||||
* ```ts
|
||||
* const error: FetchError = ...;
|
||||
* if (error.type === "api_error") {
|
||||
* // Handle HTTP error — check status for 401/403/50x etc.
|
||||
* if (error.status === 401) redirectToLogin();
|
||||
* } else if (error.type === "network_error") {
|
||||
* // Handle network/connectivity issues
|
||||
* showNetworkMessage();
|
||||
* } else if (error.type === "abort_error") {
|
||||
* // Request was cancelled — typically silent
|
||||
* return;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type FetchError = ApiErrorPayload | NetworkErrorPayload | AbortErrorPayload;
|
||||
|
||||
/**
|
||||
* Type guard to check if an error is an authentication error (401/403).
|
||||
*
|
||||
* @param error - The error to check (typically a FetchError)
|
||||
* @returns `true` if the error is a 401 or 403 API error
|
||||
*/
|
||||
export function isAuthError(error: FetchError): error is ApiErrorPayload {
|
||||
return error.type === "api_error" && (error.status === 401 || error.status === 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an error is an abort error (request cancelled).
|
||||
*
|
||||
* @param error - The error to check (typically a FetchError)
|
||||
* @returns `true` if the error is an abort error
|
||||
*/
|
||||
export function isAbortError(error: FetchError): error is AbortErrorPayload {
|
||||
return error.type === "abort_error";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an error is a network error.
|
||||
*
|
||||
* @param error - The error to check (typically a FetchError)
|
||||
* @returns `true` if the error is a network error
|
||||
*/
|
||||
export function isNetworkError(error: FetchError): error is NetworkErrorPayload {
|
||||
return error.type === "network_error";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an error is an API error.
|
||||
*
|
||||
* @param error - The error to check (typically a FetchError)
|
||||
* @returns `true` if the error is an API error
|
||||
*/
|
||||
export function isApiError(error: FetchError): error is ApiErrorPayload {
|
||||
return error.type === "api_error";
|
||||
}
|
||||
Reference in New Issue
Block a user