import { z } from 'zod'; interface ChainageRangeValues { chainage_start_km: string; chainage_end_km: string; } function isNonNegativeWholeNumberText(value: string) { return /^\d+$/.test(value.trim()); } function isFiniteNumberText(value: string) { return Number.isFinite(Number(value.trim())); } function coordinateSchema({ requiredMessage, rangeMessage, min, max, }: { requiredMessage?: string; rangeMessage: string; min: number; max: number; }) { const schema = requiredMessage ? z.string().trim().min(1, requiredMessage) : z.string(); return schema .refine( (value) => { const trimmedValue = value.trim(); return !trimmedValue || isFiniteNumberText(trimmedValue); }, { message: 'Enter a valid number' }, ) .refine( (value) => { const trimmedValue = value.trim(); if (!trimmedValue || !isFiniteNumberText(trimmedValue)) { return true; } const numberValue = Number(trimmedValue); return numberValue >= min && numberValue <= max; }, { message: rangeMessage }, ); } export const optionalLatitude = coordinateSchema({ min: -90, max: 90, rangeMessage: 'Latitude must be between -90 and 90', }); export const optionalLongitude = coordinateSchema({ min: -180, max: 180, rangeMessage: 'Longitude must be between -180 and 180', }); export const requiredLatitude = coordinateSchema({ requiredMessage: 'Latitude is required', min: -90, max: 90, rangeMessage: 'Latitude must be between -90 and 90', }); export const requiredLongitude = coordinateSchema({ requiredMessage: 'Longitude is required', min: -180, max: 180, rangeMessage: 'Longitude must be between -180 and 180', }); export const optionalIntegerNumber = z.string().refine( (value) => { const trimmedValue = value.trim(); if (!trimmedValue) { return true; } return isNonNegativeWholeNumberText(trimmedValue); }, { message: 'Enter a whole number 0 or above' }, ); export const requiredIntegerNumber = (message: string) => z.string().trim().min(1, message).refine(isNonNegativeWholeNumberText, { message: 'Enter a whole number 0 or above', }); export function validateChainageRange( values: ChainageRangeValues, ctx: z.RefinementCtx, ) { const startValue = values.chainage_start_km.trim(); const endValue = values.chainage_end_km.trim(); if (!startValue || !endValue) { return; } const start = Number(startValue); const end = Number(endValue); if ( !Number.isFinite(start) || !Number.isFinite(end) || !isNonNegativeWholeNumberText(startValue) || !isNonNegativeWholeNumberText(endValue) || start <= end ) { return; } ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Start km cannot be greater than end km', path: ['chainage_start_km'], }); }