All files / src/utils apiResponse.ts

82.5% Statements 165/200
72% Branches 18/25
75% Functions 9/12
83.06% Lines 152/183

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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 1842x 2x 2x 2x 2x 2x 2x       2x 2x 2x   2x 2x 2x 2x 6x 6x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 108x 108x 108x 108x 108x   108x 2x 4x 871x         871x 1x     871x         2x                 39x       2x 6x 6x 6x 6x 6x 14x 14x 6x 14x           2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 2x 2x 2x 2x 2x 2x 2x 2x 4x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 79x 2x 2x 2x 2x 2x 2x 2x 79x 14x 2x 2x 79x 2x 2x 2x 79x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 2x 2x 2x 2x  
// src/utils/apiResponse.ts
// ============================================================================
// API RESPONSE HELPERS
// ============================================================================
// Utility functions for creating standardized API responses per ADR-028.
// Use these helpers in all route handlers for consistent response formats.
// ============================================================================

import { Response } from 'express';
import {
  ApiSuccessResponse,
  ApiErrorResponse,
  PaginationInput,
  PaginationMeta,
  ResponseMeta,
  ErrorCodeType,
  ErrorCode,
} from '../types/api';
 
/**
 * Send a successful response with data.
 *
 * @param res - Express response object
 * @param data - The response data
 * @param statusCode - HTTP status code (default: 200)
 * @param meta - Optional metadata (requestId, timestamp)
 *
 * @example
 * // Simple success response
 * sendSuccess(res, { id: 1, name: 'Item' });
 *
 * @example
 * // Success with 201 Created
 * sendSuccess(res, newUser, 201);
 */
export function sendSuccess<T>(
  res: Response,
  data: T,
  statusCode: number = 200,
  meta?: Omit<ResponseMeta, 'pagination'>,
): Response<ApiSuccessResponse<T>> {
  const response: ApiSuccessResponse<T> = {
    success: true,
    data,
  };

  if (meta) {
    response.meta = meta;
  }

  return res.status(statusCode).json(response);
}

/**
 * Send a successful response with no content (204).
 * Used for DELETE operations or actions that don't return data.
 *
 * @param res - Express response object
 *
 * @example
 * // After deleting a resource
 * sendNoContent(res);
 */
export function sendNoContent(res: Response): Response {
  return res.status(204).send();
}

/**
 * Calculate pagination metadata from input parameters.
 *
 * @param input - Pagination input (page, limit, total)
 * @returns Calculated pagination metadata
 */
export function calculatePagination(input: PaginationInput): PaginationMeta {
  const { page, limit, total } = input;
  const totalPages = Math.ceil(total / limit);
 
  return {
    page,
    limit,
    total,
    totalPages,
    hasNextPage: page < totalPages,
    hasPrevPage: page > 1,
  };
}
 
/**
 * Send a paginated success response.
 *
 * @param res - Express response object
 * @param data - The array of items for the current page
 * @param pagination - Pagination input (page, limit, total)
 * @param meta - Optional additional metadata
 *
 * @example
 * const { flyers, total } = await flyerService.getFlyers({ page, limit });
 * sendPaginated(res, flyers, { page, limit, total });
 */
export function sendPaginated<T>(
  res: Response,
  data: T[],
  pagination: PaginationInput,
  meta?: Omit<ResponseMeta, 'pagination'>,
): Response<ApiSuccessResponse<T[]>> {
  const response: ApiSuccessResponse<T[]> = {
    success: true,
    data,
    meta: {
      ...meta,
      pagination: calculatePagination(pagination),
    },
  };
 
  return res.status(200).json(response);
}
 
/**
 * Send an error response.
 *
 * @param res - Express response object
 * @param code - Machine-readable error code
 * @param message - Human-readable error message
 * @param statusCode - HTTP status code (default: 400)
 * @param details - Optional error details (validation errors, etc.)
 * @param meta - Optional metadata (requestId for error tracking)
 *
 * @example
 * // Validation error
 * sendError(res, ErrorCode.VALIDATION_ERROR, 'Invalid email format', 400, errors);
 *
 * @example
 * // Not found error
 * sendError(res, ErrorCode.NOT_FOUND, 'User not found', 404);
 */
export function sendError(
  res: Response,
  code: ErrorCodeType | string,
  message: string,
  statusCode: number = 400,
  details?: unknown,
  meta?: Pick<ResponseMeta, 'requestId' | 'timestamp'>,
): Response<ApiErrorResponse> {
  const response: ApiErrorResponse = {
    success: false,
    error: {
      code,
      message,
    },
  };
 
  if (details !== undefined) {
    response.error.details = details;
  }
 
  if (meta) {
    response.meta = meta;
  }
 
  return res.status(statusCode).json(response);
}
 
/**
 * Send a message-only success response.
 * Useful for operations that complete successfully but don't return data.
 *
 * @param res - Express response object
 * @param message - Success message
 * @param statusCode - HTTP status code (default: 200)
 *
 * @example
 * sendMessage(res, 'Password updated successfully');
 */
export function sendMessage(
  res: Response,
  message: string,
  statusCode: number = 200,
): Response<ApiSuccessResponse<{ message: string }>> {
  return sendSuccess(res, { message }, statusCode);
}
 
// Re-export ErrorCode for convenience
export { ErrorCode };