feat: add shared validation for chainage and coordinates

This commit is contained in:
2026-06-23 18:32:38 +05:30
parent 066374bebc
commit b282fdf56d
7 changed files with 219 additions and 95 deletions

View File

@@ -0,0 +1,127 @@
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'],
});
}