old version placed
This commit is contained in:
78
src/lib/renderZodValidation.ts
Normal file
78
src/lib/renderZodValidation.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import * as z from "zod";
|
||||
import { ZodDecimal } from "./zodDecimal";
|
||||
|
||||
function convertApiValidationToZodValidation(apiValidation: any) {
|
||||
let zodValidation: any = {};
|
||||
Object.entries(apiValidation).forEach(([key, value]: any) => {
|
||||
const fieldType: String = value.fieldType || "string";
|
||||
const required = value.required || false;
|
||||
if (fieldType === "string") {
|
||||
zodValidation[key] = required
|
||||
? z
|
||||
.string()
|
||||
.min(1)
|
||||
.refine((val) => val !== "" || val !== null)
|
||||
: z
|
||||
.string()
|
||||
.min(1)
|
||||
.optional()
|
||||
.refine((val) => val !== "" || val !== null);
|
||||
} else if (fieldType === "integer") {
|
||||
zodValidation[key] = required
|
||||
? z.preprocess((value) => {
|
||||
try {
|
||||
const parsedValue = Number(value);
|
||||
return isNaN(parsedValue) ? undefined : parsedValue;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}, z.number().min(1))
|
||||
: z.preprocess((value) => {
|
||||
try {
|
||||
const parsedValue = Number(value);
|
||||
return isNaN(parsedValue) ? undefined : parsedValue;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}, z.number().min(1).optional());
|
||||
} else if (fieldType === "boolean") {
|
||||
zodValidation[key] = required ? z.boolean() : z.boolean().optional();
|
||||
} else if (fieldType === "datetime") {
|
||||
zodValidation[key] = required ? z.date() : z.date().optional();
|
||||
} else if (fieldType === "float") {
|
||||
zodValidation[key] = required
|
||||
? ZodDecimal.create({ coerce: true })
|
||||
: ZodDecimal.create({ coerce: true }).optional();
|
||||
}
|
||||
});
|
||||
const validSchemaZod = z.object({
|
||||
...zodValidation,
|
||||
});
|
||||
return {
|
||||
validSchemaZod: validSchemaZod,
|
||||
zodValidation: zodValidation,
|
||||
apiValidation: apiValidation,
|
||||
};
|
||||
}
|
||||
|
||||
function retrieveDataWhichHaveValidation(data: any, apiValidation: any) {
|
||||
const apiValidated = apiValidation?.validated || {};
|
||||
Object.entries(apiValidated).forEach(([key, value]: any) => {
|
||||
const fieldType: String = value.fieldType || "string";
|
||||
const required = value.required || false;
|
||||
if (fieldType === "string") {
|
||||
data[key] = required ? data[key] : data[key] || "";
|
||||
} else if (fieldType === "integer") {
|
||||
data[key] = required ? data[key] : data[key] || 0;
|
||||
} else if (fieldType === "boolean") {
|
||||
data[key] = required ? data[key] : data[key] || false;
|
||||
} else if (fieldType === "datetime") {
|
||||
data[key] = required ? data[key] : new Date(data[key]) || "";
|
||||
} else if (fieldType === "float") {
|
||||
data[key] = required ? data[key] : data[key] || 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { convertApiValidationToZodValidation, retrieveDataWhichHaveValidation };
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
306
src/lib/zodDecimal.ts
Normal file
306
src/lib/zodDecimal.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import {
|
||||
INVALID,
|
||||
ParseContext,
|
||||
ParseInput,
|
||||
ParseReturnType,
|
||||
ParseStatus,
|
||||
RawCreateParams,
|
||||
ZodIssueCode,
|
||||
ZodParsedType,
|
||||
ZodType,
|
||||
ZodTypeDef,
|
||||
addIssueToContext,
|
||||
} from "zod";
|
||||
|
||||
export type ZodDecimalCheck =
|
||||
| { kind: "precision"; value: number; message?: string }
|
||||
| { kind: "wholeNumber"; value: number; message?: string }
|
||||
| { kind: "min"; value: number; inclusive: boolean; message?: string }
|
||||
| { kind: "max"; value: number; inclusive: boolean; message?: string }
|
||||
| { kind: "finite"; message?: string };
|
||||
|
||||
const zodDecimalKind = "ZodDecimal";
|
||||
|
||||
export interface ZodDecimalDef extends ZodTypeDef {
|
||||
checks: ZodDecimalCheck[];
|
||||
typeName: typeof zodDecimalKind;
|
||||
coerce: boolean;
|
||||
}
|
||||
|
||||
const precisionRegex = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export class ZodDecimal extends ZodType<number, ZodDecimalDef, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_parse(input: ParseInput): ParseReturnType<number> {
|
||||
// detect decimal js object
|
||||
if (
|
||||
input.data !== null &&
|
||||
typeof input.data === "object" &&
|
||||
"toNumber" in input.data
|
||||
) {
|
||||
input.data = input.data.toNumber();
|
||||
}
|
||||
if (this._def.coerce) {
|
||||
input.data = Number(input.data);
|
||||
}
|
||||
|
||||
const parsedType = this._getType(input);
|
||||
if (parsedType !== ZodParsedType.number) {
|
||||
const ctx = this._getOrReturnCtx(input);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.invalid_type,
|
||||
expected: ZodParsedType.number,
|
||||
received: ctx.parsedType,
|
||||
});
|
||||
return INVALID;
|
||||
}
|
||||
|
||||
let ctx: undefined | ParseContext = undefined;
|
||||
const status = new ParseStatus();
|
||||
|
||||
for (const check of this._def.checks) {
|
||||
if (check.kind === "precision") {
|
||||
const parts = input.data.toString().match(precisionRegex);
|
||||
const decimals = Math.max(
|
||||
(parts[1] ? parts[1].length : 0) -
|
||||
(parts[2] ? parseInt(parts[2], 10) : 0),
|
||||
0
|
||||
);
|
||||
if (decimals > check.value) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.custom,
|
||||
message: check.message,
|
||||
params: {
|
||||
precision: check.value,
|
||||
},
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
} else if (check.kind === "wholeNumber") {
|
||||
const wholeNumber = input.data.toString().split(".")[0];
|
||||
const tooLong = wholeNumber.length > check.value;
|
||||
|
||||
if (tooLong) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.custom,
|
||||
message: check.message,
|
||||
params: {
|
||||
wholeNumber: check.value,
|
||||
},
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
} else if (check.kind === "min") {
|
||||
const tooSmall = check.inclusive
|
||||
? input.data < check.value
|
||||
: input.data <= check.value;
|
||||
if (tooSmall) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.too_small,
|
||||
minimum: check.value,
|
||||
type: "number",
|
||||
inclusive: check.inclusive,
|
||||
exact: false,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
} else if (check.kind === "max") {
|
||||
const tooBig = check.inclusive
|
||||
? input.data > check.value
|
||||
: input.data >= check.value;
|
||||
if (tooBig) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.too_big,
|
||||
maximum: check.value,
|
||||
type: "number",
|
||||
inclusive: check.inclusive,
|
||||
exact: false,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
} else if (check.kind === "finite") {
|
||||
if (!Number.isFinite(input.data)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
addIssueToContext(ctx, {
|
||||
code: ZodIssueCode.not_finite,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { status: status.value, value: input.data };
|
||||
}
|
||||
|
||||
static create = (
|
||||
params?: RawCreateParams & { coerce?: true }
|
||||
): ZodDecimal => {
|
||||
return new ZodDecimal({
|
||||
checks: [],
|
||||
typeName: zodDecimalKind,
|
||||
coerce: params?.coerce ?? false,
|
||||
});
|
||||
};
|
||||
|
||||
protected setLimit(
|
||||
kind: "min" | "max",
|
||||
value: number,
|
||||
inclusive: boolean,
|
||||
message?: string
|
||||
): ZodDecimal {
|
||||
return new ZodDecimal({
|
||||
...this._def,
|
||||
checks: [
|
||||
...this._def.checks,
|
||||
{
|
||||
kind,
|
||||
value,
|
||||
inclusive,
|
||||
message,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
_addCheck(check: ZodDecimalCheck): ZodDecimal {
|
||||
return new ZodDecimal({
|
||||
...this._def,
|
||||
checks: [...this._def.checks, check],
|
||||
});
|
||||
}
|
||||
|
||||
lte(value: number, message?: string): ZodDecimal {
|
||||
return this.setLimit("max", value, true, message);
|
||||
}
|
||||
|
||||
lt(value: number, message?: string): ZodDecimal {
|
||||
return this.setLimit("max", value, false, message);
|
||||
}
|
||||
max = this.lte;
|
||||
|
||||
gt(value: number, message?: string): ZodDecimal {
|
||||
return this.setLimit("min", value, false, message);
|
||||
}
|
||||
gte(value: number, message?: string): ZodDecimal {
|
||||
return this.setLimit("min", value, true, message);
|
||||
}
|
||||
|
||||
min = this.gte;
|
||||
|
||||
precision(value: number, message?: string): ZodDecimal {
|
||||
return this._addCheck({
|
||||
kind: "precision",
|
||||
value,
|
||||
message,
|
||||
});
|
||||
}
|
||||
wholeNumber(value: number, message?: string): ZodDecimal {
|
||||
return this._addCheck({
|
||||
kind: "wholeNumber",
|
||||
value,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
get minValue() {
|
||||
let min: number | null = null;
|
||||
for (const ch of this._def.checks) {
|
||||
if (ch.kind === "min") {
|
||||
if (min === null || ch.value > min) min = ch.value;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
get maxValue() {
|
||||
let max: number | null = null;
|
||||
for (const ch of this._def.checks) {
|
||||
if (ch.kind === "max") {
|
||||
if (max === null || ch.value < max) max = ch.value;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
positive(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
value: 0,
|
||||
inclusive: false,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
negative(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "max",
|
||||
value: 0,
|
||||
inclusive: false,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
nonpositive(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "max",
|
||||
value: 0,
|
||||
inclusive: true,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
nonnegative(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
value: 0,
|
||||
inclusive: true,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
finite(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "finite",
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
safe(message?: string) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
inclusive: true,
|
||||
value: Number.MIN_SAFE_INTEGER,
|
||||
message,
|
||||
})._addCheck({
|
||||
kind: "max",
|
||||
inclusive: true,
|
||||
value: Number.MAX_SAFE_INTEGER,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
get isFinite() {
|
||||
let max: number | null = null,
|
||||
min: number | null = null;
|
||||
for (const ch of this._def.checks) {
|
||||
if (ch.kind === "finite") {
|
||||
return true;
|
||||
} else if (ch.kind === "min") {
|
||||
if (min === null || ch.value > min) min = ch.value;
|
||||
} else if (ch.kind === "max") {
|
||||
if (max === null || ch.value < max) max = ch.value;
|
||||
}
|
||||
}
|
||||
return Number.isFinite(min) && Number.isFinite(max);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const zodDecimal = ZodDecimal.create;
|
||||
Reference in New Issue
Block a user