UNPKG

specified

Version:

Type-safe typescript data specification verification

382 lines (381 loc) 15.7 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); var objectDefinition = function (schema, typeName) { return ({ type: typeName, nested: Object.keys(schema).reduce(function (o, a) { o[a] = schema[a].definition; return o; }, {}), descriptions: Object.keys(schema).reduce(function (d, a) { if (schema[a].hasOwnProperty("description")) { d[a] = schema[a].description; } return d; }, {}) }); }; var objectEval = function (schema, defaultStrict, typeName) { return function (data, options) { var settings = __assign({ strict: defaultStrict, failEarly: false }, options.global, options.local); if (typeof data !== "object" || data === null || data instanceof Array) { return { err: { code: "type." + typeName + ".not_a_regular_object", value: data, message: "Not a regular object." } }; } var nestedErrors = []; if (settings.strict) { var extraKeys = Object.keys(data).filter(function (k) { return !(k in schema); }); extraKeys.forEach(function (ek) { nestedErrors.push({ code: "type." + typeName + ".extra_attribute", key: ek, value: data[ek], message: "Data has attribute that is not part of the strict schema: \"" + ek + "\"." }); }); } var attributes = Object.keys(schema); var model = {}; for (var attrIndex = 0; attrIndex < attributes.length && (!settings.failEarly || !nestedErrors.length); ++attrIndex) { var attr = attributes[attrIndex]; var attrSpec = schema[attr]; if (!(attr in data) && !attrSpec.optional && !attrSpec.hasOwnProperty("defaultValue")) { nestedErrors.push({ code: "type." + typeName + ".missing_attribute", value: data, message: "Missing attribute: \"" + attr + "\".", key: attr }); } else { var pass = Symbol(); var value = data.hasOwnProperty(attr) ? data[attr] : attrSpec.optional ? pass : attrSpec.hasOwnProperty("defaultValue") ? attrSpec.defaultValue : undefined; if (value !== pass) { var attrResult = attrSpec.eval(value, { global: options.global }); if (attrResult.err) { nestedErrors.push({ code: "type." + typeName + ".invalid_attribute", value: value, message: "Evaluation of attribute \"" + attr + "\" failed.", key: attr, nestedErrors: [attrResult.err] }); } else { model[attr] = attrResult.value; } } } } if (nestedErrors.length) { return { err: { code: "type." + typeName + ".invalid_attribute_data", value: data, message: "Invalid attribute data.", nestedErrors: nestedErrors } }; } return { err: null, value: model }; }; }; exports.Type = { unknown: { version: 1, definition: { type: "unknown" }, eval: function (value) { return { err: null, value: value }; } }, null: { version: 1, definition: { type: "null" }, eval: function (value) { if (value !== null) { return { err: { code: "type.null.not_null", value: value, allowed: null, message: "Not null." } }; } return { err: null, value: null }; } }, string: { version: 1, definition: { type: "string" }, eval: function (value) { if (typeof value !== "string") { return { err: { code: "type.string.not_a_string", value: value, message: "Not a string." } }; } return { err: null, value: value }; } }, number: { version: 1, definition: { type: "number" }, eval: function (value) { if (typeof value !== "number") { return { err: { code: "type.number.not_a_number", value: value, message: "Not a number." } }; } return { err: null, value: value }; } }, boolean: { version: 1, definition: { type: "boolean" }, eval: function (value) { if (typeof value !== "boolean") { return { err: { code: "type.boolean.not_a_boolean", value: value, message: "Not a boolean." } }; } return { err: null, value: value }; } }, symbol: { version: 1, definition: { type: "symbol" }, eval: function (value) { if (typeof value !== "symbol") { return { err: { code: "type.symbol.not_a_symbol", value: value, message: "Not a symbol." } }; } return { err: null, value: value }; } }, literal: function (def) { return ({ version: 1, definition: { type: "literal", settings: { values: Object.keys(def) } }, eval: function (value) { if (typeof value !== "string" || !def.hasOwnProperty(value)) { return { err: { code: "type.literal.incorrect_literal", value: value, allowed: Object.keys(def), message: "Incorrect literal." } }; } return { err: null, value: value }; } }); }, literalValue: function () { var values = []; for (var _i = 0; _i < arguments.length; _i++) { values[_i] = arguments[_i]; } return ({ version: 1, definition: { type: "literalValue", settings: { values: values } }, eval: function (value) { for (var _i = 0, values_1 = values; _i < values_1.length; _i++) { var v = values_1[_i]; if (value === v) { return { err: null, value: value }; } } return { err: { code: "type.literalValue.incorrect_literal_value", value: value, allowed: values, message: "Incorrect literal value." } }; } }); }, array: function (spec) { return ({ version: 1, definition: { type: "array", nested: { element: spec.definition } }, eval: function (data, options) { var settings = __assign({ failEarly: false, skipInvalid: false }, options.global, options.local); if (!(data instanceof Array)) { return { err: { code: "type.array.not_an_array", value: data, message: "Not an array." } }; } var values = []; var nestedErrors = []; for (var i = 0; i < data.length && (!settings.failEarly || !nestedErrors.length); ++i) { var elementValue = data[i]; var elementResult = spec.eval(elementValue, { global: options.global }); if (elementResult.err) { if (!settings.skipInvalid) { nestedErrors.push({ code: "type.array.invalid_element", value: elementValue, message: "Evaluation of array element at index \"" + i + "\" failed.", key: i, nestedErrors: [elementResult.err] }); } } else { values.push(elementResult.value); } } if (nestedErrors.length) { return { err: { code: "type.array.invalid_elements", value: data, message: "Array validation failed.", nestedErrors: nestedErrors } }; } return { err: null, value: values }; } }); }, object: function (schema) { return { version: 1, definition: objectDefinition(schema, "object"), eval: objectEval(schema, true, "object") }; }, interface: function (schema) { return { version: 1, definition: objectDefinition(schema, "interface"), eval: objectEval(schema, false, "interface") }; }, map: function (keySpec, valueSpec) { return { version: 1, definition: { type: "map", nested: { key: keySpec.definition, value: valueSpec.definition } }, eval: function (data, options) { var settings = __assign({ failEarly: false, skipInvalidKeys: false, skipInvalidValues: false }, options.global, options.local); if (typeof data !== "object" || data === null || data instanceof Array) { return { err: { code: "type.map.not_a_regular_object", value: data, message: "Not a regular object." } }; } var result = {}; var nestedErrors = []; var dataKeys = Object.keys(data); for (var dataKeyIndex = 0; dataKeyIndex < dataKeys.length && (!settings.failEarly || !nestedErrors.length); ++dataKeyIndex) { var dk = dataKeys[dataKeyIndex]; var keyResult = keySpec.eval(dk, { global: options.global }); if (keyResult.err) { if (!settings.skipInvalidKeys) { nestedErrors.push({ code: "type.map.invalid_key", value: dk, message: "Evaluation of map key \"" + dk + "\" failed.", key: dk, nestedErrors: [keyResult.err] }); } } else { var value = data[dk]; var valueResult = valueSpec.eval(value, { global: options.global }); if (valueResult.err) { if (!settings.skipInvalidValues) { nestedErrors.push({ code: "type.map.invalid_value", value: value, message: "Evaluation of map value for key \"" + dk + "\" failed.", key: dk, nestedErrors: [valueResult.err] }); } } else { result[keyResult.value] = valueResult.value; } } } if (nestedErrors.length) { return { err: { code: "type.map.invalid_data", value: data, message: "Invalid map data.", nestedErrors: nestedErrors } }; } return { err: null, value: result }; } }; }, tuple: function () { var specs = []; for (var _i = 0; _i < arguments.length; _i++) { specs[_i] = arguments[_i]; } return { version: 1, definition: { type: "tuple", nested: specs.reduce(function (n, spec, i) { n[i] = spec.definition; return n; }, {}) }, eval: function (data, options) { var settings = __assign({ failEarly: false }, options.global, options.local); if (!(data instanceof Array)) { return { err: { code: "type.tuple.not_a_tuple", value: data, message: "Not an tuple." } }; } if (data.length !== specs.length) { return { err: { code: "type.tuple.incorrect_length", value: data, message: "Data does not have correct tuple length " + specs.length + "." } }; } var values = []; var nestedErrors = []; for (var i = 0; i < data.length && (!settings.failEarly || !nestedErrors.length); ++i) { var elementValue = data[i]; var elementResult = specs[i].eval(elementValue, { global: options.global }); if (elementResult.err) { nestedErrors.push({ code: "type.tuple.invalid_element", value: elementValue, message: "Evaluation of tuple element at index \"" + i + "\" failed.", key: i, nestedErrors: [elementResult.err] }); } else { values.push(elementResult.value); } } if (nestedErrors.length) { return { err: { code: "type.tuple.invalid_elements", value: data, message: "Tuple validation failed.", nestedErrors: nestedErrors } }; } return { err: null, value: values }; } }; }, instance: function (ctor) { return { version: 1, definition: { type: "instance", settings: { className: "name" in ctor ? ctor.name : "" } }, eval: function (value) { if (!(value instanceof ctor)) { return { err: { code: "type.instance.not_an_instance_of", value: value, message: "Not an instance of \"" + ctor.name + "\"." } }; } return { err: null, value: value }; } }; }, numeric: { version: 1, definition: { type: "numeric" }, eval: function (value) { var num = Number(value) + 0; if (!Number.isFinite(num)) { return { err: { code: "type.numeric.not_a_finite_number", value: value, message: "Not a finite number." } }; } return { err: null, value: num }; } }, booleanKey: function (keys, options) { var caseInsensitive = options && options.caseInsensitive; var truthy = keys.truthy.reduce(function (r, k) { r[caseInsensitive ? k.toLowerCase() : k] = true; return r; }, {}); var falsy = (keys.falsy || []).reduce(function (r, k) { r[caseInsensitive ? k.toLowerCase() : k] = true; return r; }, {}); return { version: 1, definition: { type: "booleanKey", settings: { keys: keys, caseInsensitive: caseInsensitive } }, eval: function (value) { var valueStr = caseInsensitive ? ("" + value).toLowerCase() : "" + value; if (truthy.hasOwnProperty(valueStr)) { return { err: null, value: true }; } else if (!keys.falsy || falsy.hasOwnProperty(valueStr)) { return { err: null, value: false }; } else { return { err: { code: "type.booleanKey.invalid_key", value: value, allowed: keys.truthy.concat(keys.falsy), message: "Not a valid boolean value." } }; } } }; } };