217 lines
5.6 KiB
TypeScript

/**
* Domain Toolkit - Validation Helpers
*
* Common validation utilities that can be reused across all domains.
*/
import { z } from "zod";
// ============================================================================
// ID Validation
// ============================================================================
/**
* Validate that an ID is a positive integer
*/
export function isValidPositiveId(id: number): boolean {
return Number.isInteger(id) && id > 0;
}
/**
* Validate Salesforce ID (18 characters, alphanumeric)
*/
export function isValidSalesforceId(id: string): boolean {
return /^[A-Za-z0-9]{18}$/.test(id);
}
// ============================================================================
// Pagination Validation
// ============================================================================
/**
* Validate and sanitize pagination parameters
*/
export function sanitizePagination(options: {
page?: number;
limit?: number;
minLimit?: number;
maxLimit?: number;
defaultLimit?: number;
}): {
page: number;
limit: number;
} {
const {
page = 1,
limit = options.defaultLimit ?? 10,
minLimit = 1,
maxLimit = 100,
} = options;
return {
page: Math.max(1, Math.floor(page)),
limit: Math.max(minLimit, Math.min(maxLimit, Math.floor(limit))),
};
}
/**
* Check if pagination offset is valid
*/
export function isValidPaginationOffset(offset: number): boolean {
return Number.isInteger(offset) && offset >= 0;
}
// ============================================================================
// String Validation
// ============================================================================
/**
* Check if string is non-empty after trimming
*/
export function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
/**
* Check if value is a valid enum member
*/
export function isValidEnumValue<T extends Record<string, string | number>>(
value: unknown,
enumObj: T
): value is T[keyof T] {
return Object.values(enumObj).includes(value as any);
}
// ============================================================================
// Array Validation
// ============================================================================
/**
* Check if array is non-empty
*/
export function isNonEmptyArray<T>(value: unknown): value is T[] {
return Array.isArray(value) && value.length > 0;
}
/**
* Check if all array items are unique
*/
export function hasUniqueItems<T>(items: T[]): boolean {
return new Set(items).size === items.length;
}
// ============================================================================
// Number Validation
// ============================================================================
/**
* Check if number is within range (inclusive)
*/
export function isInRange(
value: number,
min: number,
max: number
): boolean {
return value >= min && value <= max;
}
/**
* Check if number is a valid positive integer
*/
export function isPositiveInteger(value: unknown): value is number {
return typeof value === "number" && Number.isInteger(value) && value > 0;
}
/**
* Check if number is a valid non-negative integer
*/
export function isNonNegativeInteger(value: unknown): value is number {
return typeof value === "number" && Number.isInteger(value) && value >= 0;
}
// ============================================================================
// Date Validation
// ============================================================================
/**
* Check if string is a valid ISO date time
*/
export function isValidIsoDateTime(value: string): boolean {
const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
if (!isoRegex.test(value)) return false;
const date = new Date(value);
return !isNaN(date.getTime());
}
/**
* Check if string is a valid date in YYYYMMDD format
*/
export function isValidYYYYMMDD(value: string): boolean {
return /^\d{8}$/.test(value);
}
// ============================================================================
// Zod Schema Helpers
// ============================================================================
/**
* Create a schema for a positive integer ID
*/
export const positiveIdSchema = z.number().int().positive();
/**
* Create a schema for pagination parameters
*/
export function createPaginationSchema(options?: {
minLimit?: number;
maxLimit?: number;
defaultLimit?: number;
}) {
const { minLimit = 1, maxLimit = 100, defaultLimit = 10 } = options ?? {};
return z.object({
page: z.coerce.number().int().positive().optional().default(1),
limit: z.coerce
.number()
.int()
.min(minLimit)
.max(maxLimit)
.optional()
.default(defaultLimit),
offset: z.coerce.number().int().nonnegative().optional(),
});
}
/**
* Create a schema for sortable queries
*/
export const sortableQuerySchema = z.object({
sortBy: z.string().optional(),
sortOrder: z.enum(["asc", "desc"]).optional(),
});
// ============================================================================
// Type Guards
// ============================================================================
/**
* Type guard for checking if value is a record
*/
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Type guard for checking if error is a Zod error
*/
export function isZodError(error: unknown): error is z.ZodError {
return (
typeof error === "object" &&
error !== null &&
"issues" in error &&
Array.isArray((error as any).issues)
);
}