All files / src/entities utils.ts

96.42% Statements 27/28
93.75% Branches 15/16
100% Functions 5/5
96.29% Lines 26/27

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 12471x   71x           71x         71x                                               71x       514x 1x       513x                         71x 179x 93x   86x 86x 86x   2x   84x     71x                 858x                               71x 14x 2x                     71x         12x 6x   6x 6x        
import { z } from "zod";
 
const DEFAULT_NULL = process.env.DEFAULT_NULL === "true";
 
/**
 * GitLab REST API default pagination value.
 * @see https://docs.gitlab.com/ee/api/rest/index.html#pagination
 */
export const GITLAB_DEFAULT_PER_PAGE = 20;
 
/**
 * Maximum items per page allowed by GitLab API.
 */
export const GITLAB_MAX_PER_PAGE = 100;
 
/**
 * Creates pagination fields for Zod schemas with dynamic descriptions.
 * The description automatically includes the default value.
 *
 * @param defaultPerPage - Default items per page (default: 20)
 * @param maxPerPage - Maximum items per page (default: 100)
 * @returns Object with page and per_page Zod fields
 *
 * @example
 * // In schema definition:
 * const ListSchema = z.object({
 *   action: z.literal("list"),
 *   ...paginationFields(),  // Uses GitLab defaults (20, max 100)
 * });
 *
 * @example
 * // With custom default:
 * const ListSchema = z.object({
 *   action: z.literal("list"),
 *   ...paginationFields(50),  // Default 50, max 100
 * });
 */
export function paginationFields(
  defaultPerPage: number = GITLAB_DEFAULT_PER_PAGE,
  maxPerPage: number = GITLAB_MAX_PER_PAGE
) {
  if (defaultPerPage > maxPerPage) {
    throw new Error(
      `Invalid pagination config: defaultPerPage (${defaultPerPage}) cannot exceed maxPerPage (${maxPerPage})`
    );
  }
  return {
    per_page: z
      .number()
      .int()
      .min(1)
      .max(maxPerPage)
      .optional()
      .default(defaultPerPage)
      .describe(`Number of items per page (default: ${defaultPerPage}, max: ${maxPerPage})`),
    page: z.number().int().min(1).optional().describe("Page number"),
  };
}
 
export const flexibleBoolean = z.preprocess(val => {
  if (typeof val === "boolean") {
    return val;
  }
  let result = "false";
  try {
    result = String(val).toLowerCase();
  } catch {
    return false;
  }
  return ["true", "t", "1"].includes(result);
}, z.boolean());
 
export const flexibleBooleanNullable = DEFAULT_NULL
  ? flexibleBoolean.nullable().default(null)
  : flexibleBoolean.nullable();
 
/**
 * Required ID field that accepts string or number input.
 * Unlike z.coerce.string(), this properly rejects undefined/null values
 * instead of coercing them to the literal string "undefined"/"null".
 */
export const requiredId = z.preprocess(val => val ?? "", z.coerce.string().min(1));
 
/**
 * Asserts that a value is defined (not undefined).
 * Used for fields validated by Zod .refine() where TypeScript cannot
 * automatically narrow the type after runtime validation.
 *
 * Note: This intentionally only checks for undefined, not empty strings.
 * Empty string validation is handled by Zod schema .refine() checks which
 * run during Schema.parse(args) BEFORE handler code executes. This function
 * exists solely for TypeScript type narrowing after validation passes.
 *
 * @param value - The value to assert
 * @param fieldName - Name of the field for error messages
 * @throws Error if value is undefined
 */
export function assertDefined<T>(value: T | undefined, fieldName: string): asserts value is T {
  if (value === undefined) {
    throw new Error(`${fieldName} is required but was not provided`);
  }
}
 
/**
 * Validates that the appropriate ID field is provided based on scope.
 * Used by webhook schemas to ensure projectId or groupId is present.
 *
 * @param data - Object containing scope, projectId, and groupId fields
 * @returns true if validation passes
 */
export function validateScopeId(data: {
  scope: "project" | "group";
  projectId?: string;
  groupId?: string;
}): boolean {
  if (data.scope === "project") {
    return !!data.projectId;
  }
  Eif (data.scope === "group") {
    return !!data.groupId;
  }
  return true;
}