All files / lib response.ts

100% Statements 13/13
100% Branches 11/11
100% Functions 4/4
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                                                    1702x                                                 192x                               1409x                             1104x         1104x 2x                 1102x 844x                 258x 1x       257x 16x         236x    
// Standard API response helpers
import { whatsAppErrorCopy } from "@interioring/utils/whatsapp/errors";
import type { Context } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import { AppError, ConfigurationError } from "./errors";
import { WhatsAppApiError, WhatsAppConfigError } from "./whatsapp/client";
 
export interface ApiResponse<T = unknown> {
	success: boolean;
	data?: T;
	error?: {
		code: string;
		message: string;
	};
	meta?: {
		total?: number;
		page?: number;
		limit?: number;
		offset?: number;
		totalPages?: number;
		hasNext?: boolean;
		hasPrev?: boolean;
	};
}
 
/**
 * Send a success response
 */
export function success<T>(
	c: Context,
	data: T,
	status: ContentfulStatusCode = 200,
	meta?: ApiResponse["meta"],
) {
	return c.json<ApiResponse<T>>(
		{
			success: true,
			data,
			...(meta && { meta }),
		},
		status,
	);
}
 
/**
 * Send a success response with pagination
 */
export function successWithPagination<T>(
	c: Context,
	data: T[],
	meta: {
		total: number;
		page: number;
		limit: number;
		totalPages: number;
		hasNext: boolean;
		hasPrev: boolean;
	},
) {
	return c.json<ApiResponse<T[]>>({
		success: true,
		data,
		meta,
	});
}
 
/**
 * Send an error response
 */
export function error(
	c: Context,
	code: string,
	message: string,
	statusCode: ContentfulStatusCode,
) {
	return c.json<ApiResponse>(
		{
			success: false,
			error: { code, message },
		},
		statusCode,
	);
}
 
/**
 * Handle errors consistently
 * Note: Internal error messages are not exposed to clients for security
 */
export function handleError(c: Context, err: unknown) {
	// Log the full error for debugging (server-side only)
	console.error("API Error:", err);
 
	// Configuration errors carry operator-only diagnostics (env var names,
	// deployment commands). Scrub before sending to client; the full message
	// is already logged above for debugging.
	if (err instanceof ConfigurationError) {
		return error(
			c,
			"CONFIGURATION_ERROR",
			"Service temporarily unavailable. Please contact support.",
			503,
		);
	}
 
	// Handle known application errors - these have safe, user-facing messages
	if (err instanceof AppError) {
		return error(
			c,
			err.code,
			err.message,
			err.statusCode as ContentfulStatusCode,
		);
	}
 
	// Handle WhatsApp config errors — service not configured
	if (err instanceof WhatsAppConfigError) {
		return error(c, "CONFIG_ERROR", "WhatsApp not configured", 500);
	}
 
	// Handle WhatsApp API errors — map to safe user-facing messages
	if (err instanceof WhatsAppApiError) {
		return error(c, "WHATSAPP_API_ERROR", whatsAppErrorCopy(err.code), 400);
	}
 
	// For unknown errors, don't expose internal details to clients
	// The actual error is logged above for debugging
	return error(c, "INTERNAL_ERROR", "An unexpected error occurred", 500);
}