diff --git a/package.json b/package.json index e51d789..850d786 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "jsonwebtoken": "^8.5.1", "nodemailer": "^6.7.0", "redis": "^3.1.2", - "winston": "^3.7.2" + "winston": "^3.7.2", + "zod": "^3.14.4" }, "devDependencies": { "@types/chai": "^4.2.22", diff --git a/src/Middleware/error/SchemaError.ts b/src/Middleware/error/SchemaError.ts new file mode 100644 index 0000000..6d4026d --- /dev/null +++ b/src/Middleware/error/SchemaError.ts @@ -0,0 +1,9 @@ +import ForwardableError from "./ForwardableError"; + +export default class SchemaError extends ForwardableError { + protected __oid = "SCHEMA_ERROR"; + + constructor(message: string) { + super(400, message); + } +} diff --git a/src/lib/result_schema.ts b/src/lib/result_schema.ts new file mode 100644 index 0000000..53bcc38 --- /dev/null +++ b/src/lib/result_schema.ts @@ -0,0 +1,65 @@ +import { Schema, z, ZodError } from "zod"; +import SchemaError from "../Middleware/error/SchemaError"; + +// -- Schema definitions -- + +const SchemaVersion = z.enum(["1.0"]); + +const TimeUnit = z.enum(["days", "hours", "minutes", "seconds", "milliseconds"]); + +const DurationSchema = z + .object({ + type: z.literal("duration"), + min: z.number().int({ message: "min must be an integer (relative to smallestUnit)" }), + max: z.number().int({ message: "max must be an integer (relative to smallestUnit)" }), + smallestUnit: TimeUnit, + higherIsBetter: z.boolean(), + }) + .refine(({ min, max }) => min < max, { message: "min must be smaller than max" }); + +const PointSchema = z + .object({ + type: z.literal("points"), + min: z.number(), + max: z.number(), + step: z.number(), + start: z.number(), + + unit: z.string(), + unitSign: z.string(), + + higherIsBetter: z.boolean(), + }) + .refine(({ min, max }) => min < max, { message: "min must be smaller than max" }) + .refine(({ min, max, start }) => min <= start && max >= start, { + message: "start must be larger than or equal to min and smaller than or equal to max", + }); + +export type DurationSchemaT = z.infer; +export type PointSchemaT = z.infer; + +// -- Parser -- + +export function parseSchema(schema: any): DurationSchemaT | PointSchemaT { + try { + if (schema.type === "duration") { + return DurationSchema.parse(schema); + } else if (schema.type === "points") { + return PointSchema.parse(schema); + } + } catch (e) { + if (e instanceof ZodError) { + const issue = e.issues.at(0); + + if (issue) { + throw new SchemaError(`${issue.path}: ${issue.message}`); + } + + throw new SchemaError("Unknown error occured while validating the schema"); + } + + throw e; + } + + throw new SchemaError("type must be either 'duration' or 'points'"); +}