UNPKG

prostgles-types

Version:

Shared TypeScript object definitions for prostgles-client and prostgles-server

252 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertJSONBObjectAgainstSchema = exports.validateJSONBObjectAgainstSchema = exports.getJSONBSchemaValidationError = exports.getJSONBObjectSchemaValidationError = exports.getFieldTypeObj = void 0; const util_1 = require("../util"); const utils_1 = require("./utils"); const getFieldTypeObj = (rawFieldType) => { if (typeof rawFieldType === "string") return { type: rawFieldType }; return rawFieldType; }; exports.getFieldTypeObj = getFieldTypeObj; const PRIMITIVE_VALIDATORS = { string: (val) => typeof val === "string", number: (val) => typeof val === "number" && Number.isFinite(val), integer: (val) => typeof val === "number" && Number.isInteger(val), boolean: (val) => typeof val === "boolean", time: (val) => typeof val === "string", timestamp: (val) => typeof val === "string", any: (val) => typeof val !== "function" && typeof val !== "symbol", Date: (val) => typeof val === "string", Lookup: () => { throw new Error("Lookup type is not supported for validation"); }, }; const PRIMITIVE_VALIDATORS_KEYS = (0, util_1.getKeys)(PRIMITIVE_VALIDATORS); const getElementType = (type) => { if (typeof type === "string" && type.endsWith("[]")) { const elementType = type.slice(0, -2); if (!PRIMITIVE_VALIDATORS_KEYS.includes(elementType)) { throw new Error(`Unknown array field type ${type}`); } return elementType; } }; const getValidator = (type) => { const elem = getElementType(type); if (elem) { const validator = PRIMITIVE_VALIDATORS[elem]; return { isArray: true, validator: (v) => Array.isArray(v) && v.every((v) => validator(v)), }; } const validator = PRIMITIVE_VALIDATORS[type]; if (!validator) { throw new Error(`Unknown field type ${type}`); } return { isArray: false, validator }; }; const getPropertyValidationError = (value, rawFieldType, path = [], opts) => { const err = `${path.join(".")} is of invalid type. Expecting ${getTypeDescription(rawFieldType).replaceAll("\n", "")}`; const fieldType = (0, exports.getFieldTypeObj)(rawFieldType); const { type, allowedValues, nullable, optional } = fieldType; if (nullable && value === null) return; if (optional && value === undefined) return; if (allowedValues) { return `${path.join(".")} Allowed values are not supported for validation`; } if (type) { if ((0, util_1.isObject)(type)) { if (!(0, util_1.isObject)(value)) { return err; } for (const subKey of (0, utils_1.safeGetKeys)(type)) { const subSchema = (0, utils_1.safeGetProperty)(type, subKey); const propIsOptional = (0, util_1.isObject)(subSchema) && subSchema.optional; if (!propIsOptional && !(0, utils_1.safeHasOwn)(value, subKey)) { return `${[...path, subKey].join(".")} is missing but required`; } const error = getPropertyValidationError((0, utils_1.safeGetProperty)(value, subKey), subSchema, [...path, subKey], opts); if (error !== undefined) { return error; } } if (!opts?.allowExtraProperties) { /** Check for extra properties */ const valueKeys = (0, utils_1.safeGetKeys)(value); const schemaKeys = (0, utils_1.safeGetKeys)(type); const extraKeys = valueKeys.filter((key) => !schemaKeys.includes(key)); if (extraKeys.length) { return `${path.join(".")} has extra properties: ${extraKeys.join(", ")}`; } } return; } const { validator } = getValidator(type); const isValid = validator(value); if (!isValid) { return err; } return; } if (fieldType.enum) { const otherOptions = []; if (fieldType.nullable) otherOptions.push(null); if (fieldType.optional) otherOptions.push(undefined); // err += `one of: ${JSON.stringify([...fieldType.enum, ...otherOptions]).slice(1, -1)}`; if (!fieldType.enum.includes(value)) return err; return; } const arrayOf = fieldType.arrayOf ?? (fieldType.arrayOfType ? { type: fieldType.arrayOfType } : undefined); if (arrayOf) { if (!Array.isArray(value)) { return err + " an array"; } const error = value .map((element, i) => { return getPropertyValidationError(element, arrayOf, [...path, `${i}`], opts); }) .filter(util_1.isDefined)[0]; if (error !== undefined) { return `${err}. Error at index ${path.length > 0 ? path.join(".") + "." : ""}\n\n${error}`; } return; } const oneOf = fieldType.oneOf ?? fieldType.oneOfType?.map((type) => ({ type })); if (oneOf) { if (!oneOf.length) { return err + "to not be empty"; } let firstError; const validMember = oneOf.find((member) => { const error = getPropertyValidationError(value, member, path, opts); firstError ?? (firstError = error); return error === undefined; }); if (validMember) { return; } return err; } if (fieldType.record) { const { keysEnum, partial, values: valuesSchema } = fieldType.record; if (!(0, util_1.isObject)(value)) { return err + "object"; } if (partial && (0, util_1.isEmpty)(value)) { return; } const valueKeys = (0, util_1.getKeys)(value); const missingKey = partial ? undefined : keysEnum?.find((key) => !valueKeys.includes(key)); if (missingKey !== undefined) { return `${err} to have key ${missingKey}`; } const extraKeys = keysEnum && valueKeys.filter((key) => !keysEnum.includes(key)); if (extraKeys?.length) { return `${err} has extra keys: ${extraKeys}`; } if (valuesSchema) { for (const propKey of (0, utils_1.safeGetKeys)(value)) { const propValue = (0, utils_1.safeGetProperty)(value, propKey); const valError = getPropertyValidationError(propValue, valuesSchema, [...path, propKey], opts); if (valError !== undefined) { return `${valError}`; } } } return; } return `Could not validate field type. Some logic might be missing: ${JSON.stringify(fieldType)}`; }; const getTypeDescription = (schema) => { const schemaObj = (0, exports.getFieldTypeObj)(schema); const { type, nullable, optional, record } = schemaObj; const oneOf = schemaObj.oneOf ?? schemaObj.oneOfType?.map((type) => ({ type })); const allowedTypes = []; if (nullable) allowedTypes.push("null"); if (optional) allowedTypes.push("undefined"); if (typeof type === "string") { allowedTypes.push(type); } else if (type) { if ((0, util_1.isObject)(type)) { const keyOpts = []; Object.entries(type).forEach(([key, value]) => { keyOpts.push(`${key}: ${getTypeDescription(value)}`); }); allowedTypes.push(`{ ${keyOpts.join("; ")} }`); } } schemaObj.enum?.forEach((v) => { if (v === null) { allowedTypes.push("null"); } else if (v === undefined) { allowedTypes.push("undefined"); } else if (typeof v === "string") { allowedTypes.push(JSON.stringify(v)); } else { allowedTypes.push(v); } }); oneOf?.forEach((v) => { const type = getTypeDescription(v); allowedTypes.push(type); }); if (record) { const { keysEnum, partial, values } = record; const optional = partial ? "?" : ""; const valueType = !values ? "any" : getTypeDescription(values); if (keysEnum) { allowedTypes.push(`{ [${keysEnum.join(" | ")}]${optional}: ${valueType} }`); } else { allowedTypes.push(`{ [key: string]${optional}: ${valueType} }`); } } return allowedTypes.join(" | "); }; const getJSONBObjectSchemaValidationError = (schema, obj, objName = "input", optional = false, opts) => { if (obj === undefined && !optional) return { error: `Expecting ${objName} to be defined` }; if (!(0, util_1.isObject)(obj)) { return { error: `Expecting ${objName} to be an object` }; } const error = getPropertyValidationError(obj, { type: schema }, [], opts); if (error) { return { error }; } return { data: obj }; }; exports.getJSONBObjectSchemaValidationError = getJSONBObjectSchemaValidationError; const getJSONBSchemaValidationError = (schema, obj, opts) => { const error = getPropertyValidationError(obj, schema, undefined, opts); if (error) { return { error }; } return { data: obj }; }; exports.getJSONBSchemaValidationError = getJSONBSchemaValidationError; const validateJSONBObjectAgainstSchema = (schema, obj, objName, optional = false) => { const { error } = (0, exports.getJSONBObjectSchemaValidationError)(schema, obj, objName, optional); return error === undefined; }; exports.validateJSONBObjectAgainstSchema = validateJSONBObjectAgainstSchema; const assertJSONBObjectAgainstSchema = (schema, obj, objName, optional = false) => { const { error } = (0, exports.getJSONBObjectSchemaValidationError)(schema, obj, objName, optional); if (error) { throw new Error(error); } }; exports.assertJSONBObjectAgainstSchema = assertJSONBObjectAgainstSchema; //# sourceMappingURL=JSONBSchemaValidation.js.map