feat: add shared validation for chainage and coordinates
This commit is contained in:
127
src/lib/validation/numberField.ts
Normal file
127
src/lib/validation/numberField.ts
Normal 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'],
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user