128 lines
2.8 KiB
TypeScript
128 lines
2.8 KiB
TypeScript
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'],
|
|
});
|
|
}
|