prostgles-types
Version:
Shared TypeScript object definitions for prostgles-client and prostgles-server
230 lines • 8.8 kB
JavaScript
;
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