UNPKG

prostgles-types

Version:

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

230 lines 8.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertJSONBObjectAgainstSchema = exports.validateJSONBObjectAgainstSchema = exports.getJSONBObjectSchemaValidationError = exports.getFieldTypeObj = void 0; const util_1 = require("../util"); 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 = []) => { 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) { throw new Error(`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, subSchema] of (0, util_1.getObjectEntries)(type)) { const error = getPropertyValidationError(value[subKey], subSchema, [...path, subKey]); if (error !== undefined) { return error; } } 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 + " to be an array"; } const error = value .map((element, i) => { return getPropertyValidationError(element, arrayOf, [...path, `${i}`]); }) .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); 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, propValue] of Object.entries(value)) { const valError = getPropertyValidationError(propValue, valuesSchema, [...path, propKey]); 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, optional = false) => { 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` }; } for (const [k, objSchema] of Object.entries(schema)) { const error = getPropertyValidationError(obj[k], objSchema, [k]); if (error) { return { error }; } } return { data: obj }; }; exports.getJSONBObjectSchemaValidationError = getJSONBObjectSchemaValidationError; 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