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                                                                    1477x                                                 185x                               1305x                             998x         998x 1x                 997x 751x                 246x 1x       245x 16x         224x    
// 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);
}