inspannings-monitor/lib/forms/parse.ts

189 lines
3.4 KiB
TypeScript

const TIME_VALUE_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;
const EMAIL_VALUE_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const INTEGER_VALUE_PATTERN = /^-?\d+$/;
const UUID_VALUE_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
export class FormDataValidationError extends Error {
code: string;
constructor(code: string) {
super(code);
this.code = code;
this.name = "FormDataValidationError";
}
}
function fail(code: string): never {
throw new FormDataValidationError(code);
}
export function getOptionalString(formData: FormData, key: string): string | null {
const value = formData.get(key);
if (typeof value !== "string") {
return null;
}
return value.trim();
}
export function getRequiredString(
formData: FormData,
key: string,
errorCode: string,
): string {
const value = formData.get(key);
if (typeof value !== "string") {
fail(errorCode);
}
const trimmedValue = value.trim();
if (!trimmedValue) {
fail(errorCode);
}
return trimmedValue;
}
export function getBooleanValue(
formData: FormData,
key: string,
errorCode: string,
): boolean {
const value = formData.get(key);
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
fail(errorCode);
}
export function getEnumValue<const TValue extends string>(
formData: FormData,
key: string,
allowedValues: readonly TValue[],
errorCode: string,
): TValue {
const value = getRequiredString(formData, key, errorCode);
if (!allowedValues.includes(value as TValue)) {
fail(errorCode);
}
return value as TValue;
}
export function getOptionalTimeValue(
formData: FormData,
key: string,
errorCode: string,
): string | null {
const value = getOptionalString(formData, key);
if (!value) {
return null;
}
if (!TIME_VALUE_PATTERN.test(value)) {
fail(errorCode);
}
return value;
}
export function assertEmail(value: string, errorCode: string): string {
if (!EMAIL_VALUE_PATTERN.test(value)) {
fail(errorCode);
}
return value;
}
export function assertMinLength(
value: string,
minimumLength: number,
errorCode: string,
): string {
if (value.length < minimumLength) {
fail(errorCode);
}
return value;
}
export function assertMaxLength(
value: string,
maximumLength: number,
errorCode: string,
): string {
if (value.length > maximumLength) {
fail(errorCode);
}
return value;
}
export function getIntegerValue(
formData: FormData,
key: string,
range: { min: number; max: number },
errorCode: string,
): number {
const value = getRequiredString(formData, key, errorCode);
if (!INTEGER_VALUE_PATTERN.test(value)) {
fail(errorCode);
}
const parsedValue = Number.parseInt(value, 10);
if (
Number.isNaN(parsedValue) ||
parsedValue < range.min ||
parsedValue > range.max
) {
fail(errorCode);
}
return parsedValue;
}
export function getUuidValue(
formData: FormData,
key: string,
errorCode: string,
): string {
const value = getRequiredString(formData, key, errorCode);
if (!UUID_VALUE_PATTERN.test(value)) {
fail(errorCode);
}
return value;
}
export function getOptionalUuidValue(
formData: FormData,
key: string,
errorCode: string,
): string | null {
const value = getOptionalString(formData, key);
if (!value) {
return null;
}
if (!UUID_VALUE_PATTERN.test(value)) {
fail(errorCode);
}
return value;
}