Muhammad Noman
Deploy OpenWA to Hugging Face Spaces
46252cd
Raw
History Blame Contribute Delete
4.96 kB
/**
* Typed error hierarchy for the OpenWA SDK.
*
* The OpenWA API returns NestJS-default errors of the shape:
* `{ statusCode: number, message: string | string[], error: string }`
* This module maps that to a typed, ergonomic error tree so callers can
* `instanceof`-check or branch on `.status`.
*
* @packageDocumentation
*/
/** Base class for every error thrown by the SDK. */
export class OpenWAError extends Error {
constructor(message: string) {
super(message);
this.name = 'OpenWAError';
}
}
/**
* Thrown when the API responds with a non-2xx status. Carries the HTTP status
* code and the parsed error body (or the raw text if the body was not JSON).
*
* Use the static {@link OpenWAApiError.fromResponse} factory in most cases.
*/
export class OpenWAApiError extends OpenWAError {
/** HTTP status code (e.g. 400, 404, 409, 429, 501). */
readonly status: number;
/** Parsed JSON body if available, otherwise the raw response text. */
readonly body: unknown;
/** Value of the `error` field in the NestJS error envelope, if present. */
readonly errorKind?: string;
constructor(message: string, status: number, body: unknown, errorKind?: string) {
super(message);
this.name = 'OpenWAApiError';
this.status = status;
this.body = body;
this.errorKind = errorKind;
}
/** Build an {@link OpenWAApiError} from a fetch Response, awaiting its body. */
static async fromResponse(res: Response, context: string): Promise<OpenWAApiError> {
// An opaque unfollowed redirect (we set `redirect: 'manual'`) surfaces as status 0, not a 3xx.
// Give it a clear message instead of "OpenWA API 0": the redirect was deliberately not followed
// so the API key is never re-sent to the redirect target.
if (res.status === 0) {
return new OpenWAApiError(
`Unexpected redirect (not followed; the API key is never re-sent to a redirect target) β€” ${context}`,
0,
undefined,
);
}
let body: unknown = undefined;
const text = await res.text().catch(() => '');
if (text) {
try {
body = JSON.parse(text);
} catch {
body = text;
}
}
const env = isNestEnvelope(body) ? body : undefined;
const messageText = describeMessage(env?.message ?? body ?? res.statusText);
const message = `OpenWA API ${res.status} ${res.statusText} β€” ${context}: ${messageText}`;
return new OpenWAApiError(message, res.status, body, env?.error);
}
}
/** 401 Unauthorized β€” missing or invalid API key. */
export class OpenWAAuthError extends OpenWAApiError {}
/** 403 Forbidden β€” the API key's role is insufficient for this endpoint. */
export class OpenWAForbiddenError extends OpenWAApiError {}
/** 404 Not Found. */
export class OpenWANotFoundError extends OpenWAApiError {}
/** 409 Conflict β€” typically an {@link EngineNotReadyError} from the backend. */
export class OpenWAConflictError extends OpenWAApiError {}
/** 429 Too Many Requests β€” rate limited. */
export class OpenWARateLimitError extends OpenWAApiError {}
/** 501 Not Implemented β€” the active engine does not support this operation. */
export class OpenWANotImplementedError extends OpenWAApiError {}
/** Thrown when a request exceeds the configured timeout. */
export class OpenWATimeoutError extends OpenWAError {
constructor(timeoutMs: number) {
super(`Request timed out after ${timeoutMs}ms`);
this.name = 'OpenWATimeoutError';
}
}
/**
* Construct the most specific {@link OpenWAApiError} subclass for a status code.
* Falls back to the generic {@link OpenWAApiError} for unmapped statuses.
*/
export function classifyApiError(status: number, message: string, body: unknown, errorKind?: string): OpenWAApiError {
switch (status) {
case 401:
return new OpenWAAuthError(message, status, body, errorKind);
case 403:
return new OpenWAForbiddenError(message, status, body, errorKind);
case 404:
return new OpenWANotFoundError(message, status, body, errorKind);
case 409:
return new OpenWAConflictError(message, status, body, errorKind);
case 429:
return new OpenWARateLimitError(message, status, body, errorKind);
case 501:
return new OpenWANotImplementedError(message, status, body, errorKind);
default:
return new OpenWAApiError(message, status, body, errorKind);
}
}
/** Narrow the NestJS error envelope shape: `{ statusCode, message, error }`. */
interface NestErrorEnvelope {
statusCode: number;
message: string | string[];
error: string;
}
function isNestEnvelope(body: unknown): body is NestErrorEnvelope {
return typeof body === 'object' && body !== null && 'statusCode' in body && 'message' in body && 'error' in body;
}
function describeMessage(message: string | string[] | unknown): string {
if (Array.isArray(message)) return message.join(', ');
if (typeof message === 'string') return message;
return String(message);
}