UNPKG

typia

Version:

Superfast runtime validators with only one line

695 lines 29.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProtobufFactory = void 0; const ProtobufUtil_1 = require("../programmers/helpers/ProtobufUtil"); const TransformerError_1 = require("../transformers/TransformerError"); const MetadataFactory_1 = require("./MetadataFactory"); var ProtobufFactory; (function (ProtobufFactory) { /* ----------------------------------------------------------- METADATA COMPOSER ----------------------------------------------------------- */ ProtobufFactory.metadata = (props) => { // COMPOSE METADATA WITH INDIVIDUAL VALIDATIONS const result = MetadataFactory_1.MetadataFactory.analyze(Object.assign(Object.assign({}, props), { transformer: props.transformer, options: { escape: false, constant: true, absorb: true, validate: validate(), } })); if (result.success === false) throw TransformerError_1.TransformerError.from({ code: `typia.protobuf.${props.method}`, errors: result.errors, }); return result.data; }; /** @internal */ ProtobufFactory.emplaceObject = (object) => { for (const p of object.properties) emplaceProperty(p); const properties = object.properties .map((p) => p.of_protobuf_) .filter((p) => p !== undefined); const unique = new Set(properties .filter((p) => p !== undefined) .filter((p) => p.fixed === true) .map((p) => p.union.map((u) => u.index)) .flat()); let index = 1; properties.forEach((schema) => { if (schema.fixed === true) index = Math.max(index, Math.max(...schema.union.map((u) => u.index)) + 1); else { for (const u of schema.union) { while (unique.has(index) === true) ++index; u.index = index; unique.add(index); } ++index; } }); }; const emplaceProperty = (prop) => { var _a, _b, _c, _d, _e; const union = []; for (const native of prop.value.natives) if (native.name === "Uint8Array") union.push({ type: "bytes", index: ProtobufUtil_1.ProtobufUtil.getSequence((_a = native.tags[0]) !== null && _a !== void 0 ? _a : []), }); union.push(...emplaceAtomic(prop.value).values()); for (const array of prop.value.arrays) union.push({ type: "array", array: array.type, value: emplaceSchema(array.type.value), index: ProtobufUtil_1.ProtobufUtil.getSequence((_b = array.tags[0]) !== null && _b !== void 0 ? _b : []), }); for (const obj of prop.value.objects) if (isDynamicObject(obj.type)) union.push({ type: "map", map: obj.type, key: emplaceSchema(obj.type.properties[0].key), value: emplaceSchema(obj.type.properties[0].value), index: ProtobufUtil_1.ProtobufUtil.getSequence((_c = obj.tags[0]) !== null && _c !== void 0 ? _c : []), }); else union.push({ type: "object", object: obj.type, index: ProtobufUtil_1.ProtobufUtil.getSequence((_d = obj.tags[0]) !== null && _d !== void 0 ? _d : []), }); for (const map of prop.value.maps) union.push({ type: "map", map, key: emplaceSchema(map.key), value: emplaceSchema(map.value), index: ProtobufUtil_1.ProtobufUtil.getSequence((_e = map.tags[0]) !== null && _e !== void 0 ? _e : []), }); prop.of_protobuf_ = { union, fixed: union.every((p) => p.index !== null), }; }; const emplaceSchema = (metadata) => { for (const native of metadata.natives) if (native.name === "Uint8Array") return { type: "bytes", }; const atomic = emplaceAtomic(metadata); if (atomic.size) return atomic.values().next().value; for (const array of metadata.arrays) return { type: "array", array: array.type, value: emplaceSchema(array.type.value), }; for (const obj of metadata.objects) if (isDynamicObject(obj.type)) return { type: "map", map: obj.type, key: emplaceSchema(obj.type.properties[0].key), value: emplaceSchema(obj.type.properties[0].value), }; else return { type: "object", object: obj.type, }; for (const map of metadata.maps) return { type: "map", map, key: emplaceSchema(map.key), value: emplaceSchema(map.value), }; throw new Error("Error on ProtobufFactory.emplaceSchema(): any type detected."); }; const emplaceAtomic = (meta) => { var _a, _b, _c, _d, _e, _f, _g, _h; const map = new Map(); // CONSTANTS for (const c of meta.constants) if (c.type === "boolean") map.set("bool", { type: "bool", index: getSequence((_b = (_a = c.values[0]) === null || _a === void 0 ? void 0 : _a.tags[0]) !== null && _b !== void 0 ? _b : []), }); else if (c.type === "bigint") { const init = getBigintType(c.values.map((v) => BigInt(v.value))); for (const value of c.values) emplaceBigint({ map, tags: value.tags, init, }); } else if (c.type === "number") { const init = getNumberType(c.values.map((v) => v.value)); for (const value of c.values) emplaceNumber({ map, tags: value.tags, init, }); } else if (c.type === "string") map.set("string", { type: "string", index: getSequence((_d = (_c = c.values[0]) === null || _c === void 0 ? void 0 : _c.tags[0]) !== null && _d !== void 0 ? _d : []), }); // TEMPLATE if (meta.templates.length) map.set("string", { type: "string", index: getSequence((_f = (_e = meta.templates[0]) === null || _e === void 0 ? void 0 : _e.tags[0]) !== null && _f !== void 0 ? _f : []), }); // ATOMICS for (const atomic of meta.atomics) if (atomic.type === "boolean") map.set("bool", { type: "bool", index: getSequence((_g = atomic.tags[0]) !== null && _g !== void 0 ? _g : []), }); else if (atomic.type === "bigint") emplaceBigint({ map, tags: atomic.tags, init: "int64", }); else if (atomic.type === "number") emplaceNumber({ map, tags: atomic.tags, init: "double", }); else if (atomic.type === "string") map.set("string", { type: "string", index: getSequence((_h = atomic.tags[0]) !== null && _h !== void 0 ? _h : []), }); // SORTING FOR VALIDATION REASON return new Map(Array.from(map).sort((x, y) => ProtobufUtil_1.ProtobufUtil.compare(x[0], y[0]))); }; const emplaceBigint = (next) => { var _a, _b; if (next.tags.length === 0) { next.map.set(next.init, { type: "bigint", name: next.init, index: null, }); return; } for (const row of next.tags) { const value = (_b = (_a = row.find((tag) => tag.kind === "type" && (tag.value === "int64" || tag.value === "uint64"))) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : next.init; next.map.set(next.init, { type: "bigint", name: value, index: ProtobufUtil_1.ProtobufUtil.getSequence(row), }); } }; const emplaceNumber = (next) => { var _a, _b; if (next.tags.length === 0) { next.map.set(next.init, { type: "number", name: next.init, index: null, }); return; } for (const row of next.tags) { const value = (_b = (_a = row.find((tag) => tag.kind === "type" && (tag.value === "int32" || tag.value === "uint32" || tag.value === "int64" || tag.value === "uint64" || tag.value === "float" || tag.value === "double"))) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : next.init; next.map.set(value, { type: "number", name: value, index: ProtobufUtil_1.ProtobufUtil.getSequence(row), }); } }; const getBigintType = (values) => values.some((v) => v < 0) ? "int64" : "uint64"; const getNumberType = (values) => values.every((v) => Math.floor(v) === v) ? values.every((v) => -2147483648 <= v && v <= 2147483647) ? "int32" : "int64" : "double"; const getSequence = (tags) => { const sequence = tags.find((t) => { var _a; return t.kind === "sequence" && typeof ((_a = t.schema) === null || _a === void 0 ? void 0 : _a["x-protobuf-sequence"]) === "number"; }); if (sequence === undefined) return null; const value = Number(sequence.schema["x-protobuf-sequence"]); return Number.isNaN(value) ? null : value; }; /* ----------------------------------------------------------- VALIDATORS ----------------------------------------------------------- */ const validate = () => { const visited = new WeakSet(); return (meta, explore) => { const errors = []; const insert = (msg) => errors.push(msg); if (explore.top === true) { const onlyObject = meta.size() === 1 && meta.objects.length === 1 && meta.objects[0].type.properties.every((p) => p.key.isSoleLiteral()) && meta.isRequired() === true && meta.nullable === false; if (onlyObject === false) insert("target type must be a sole and static object type"); } for (const obj of meta.objects) { if (visited.has(obj.type)) continue; visited.add(obj.type); validateObject({ object: obj.type, errors, }); try { ProtobufFactory.emplaceObject(obj.type); } catch (_a) { } } //---- // NOT SUPPORTED TYPES //---- const noSupport = (msg) => insert(`does not support ${msg}`); // PROHIBIT ANY TYPE if (meta.any) noSupport("any type"); // PROHIBIT FUNCTIONAL TYPE if (meta.functions.length) noSupport("functional type"); // PROHIBIT TUPLE TYPE if (meta.tuples.length) noSupport("tuple type"); // PROHIBIT SET TYPE if (meta.sets.length) noSupport("Set type"); // NATIVE TYPE, BUT NOT Uint8Array if (meta.natives.length) for (const native of meta.natives) { if (native.name === "Uint8Array") continue; const instead = BANNED_NATIVE_TYPES.get(native.name); if (instead === undefined) noSupport(`${native.name} type`); else noSupport(`${native.name} type. Use ${instead} type instead.`); } //---- // ATOMIC CASES //---- if (meta.atomics.length) { const numbers = ProtobufUtil_1.ProtobufUtil.getNumbers(meta); const bigints = ProtobufUtil_1.ProtobufUtil.getBigints(meta); for (const type of ["int64", "uint64"]) if (numbers.has(type) && bigints.has(type)) insert(`tags.Type<"${type}"> cannot be used in both number and bigint types. Recommend to remove from number type`); } //---- // ARRAY CASES //---- // DO NOT ALLOW MULTI-DIMENSIONAL ARRAY if (meta.arrays.length && meta.arrays.some((array) => !!array.type.value.arrays.length)) noSupport("over two dimensional array type"); // CHILD OF ARRAY TYPE MUST BE REQUIRED if (meta.arrays.length && meta.arrays.some((array) => array.type.value.isRequired() === false || array.type.value.nullable === true)) noSupport("optional type in array"); // UNION IN ARRAY if (meta.arrays.length && meta.arrays.some((a) => { var _a; return a.type.value.size() > 1 && a.type.value.constants.length !== 1 && ((_a = a.type.value.constants[0]) === null || _a === void 0 ? void 0 : _a.values.length) !== a.type.value.size(); })) noSupport("union type in array"); // DO DYNAMIC OBJECT IN ARRAY if (meta.arrays.length && meta.arrays.some((a) => a.type.value.maps.length || (a.type.value.objects.length && a.type.value.objects.some((o) => ProtobufUtil_1.ProtobufUtil.isStaticObject(o.type) === false)))) noSupport("dynamic object in array"); // UNION WITH ARRAY if (meta.size() > 1 && meta.arrays.length) noSupport("union type with array type"); //---- // OBJECT CASES //---- // EMPTY PROPERTY if (meta.objects.length && meta.objects.some((obj) => obj.type.properties.length === 0)) noSupport("empty object type"); // MULTIPLE DYNAMIC KEY TYPED PROPERTIES if (meta.objects.length && meta.objects.some((obj) => obj.type.properties.filter((p) => !p.key.isSoleLiteral()).length > 1)) noSupport("object type with multiple dynamic key typed properties. Keep only one."); // STATIC AND DYNAMIC PROPERTIES ARE COMPATIBLE if (meta.objects.length && meta.objects.some((obj) => obj.type.properties.some((p) => p.key.isSoleLiteral()) && obj.type.properties.some((p) => !p.key.isSoleLiteral()))) noSupport("object type with mixed static and dynamic key typed properties. Keep statics or dynamic only."); // DYNAMIC OBJECT, BUT PROPERTY VALUE TYPE IS ARRAY if (meta.objects.length && isDynamicObject(meta.objects[0].type) && meta.objects[0].type.properties.some((p) => !!p.value.arrays.length)) noSupport("dynamic object with array value type"); // UNION WITH DYNAMIC OBJECTTa if (meta.size() > 1 && meta.objects.length && isDynamicObject(meta.objects[0].type)) noSupport("union type with dynamic object type"); // UNION IN DYNAMIC PROPERTY VALUE if (meta.objects.length && meta.objects.some((obj) => isDynamicObject(obj.type) && obj.type.properties.some((p) => ProtobufUtil_1.ProtobufUtil.isUnion(p.value)))) noSupport("union type in dynamic property"); //---- // MAP CASES //---- // KEY TYPE IS UNION if (meta.maps.length && meta.maps.some((m) => ProtobufUtil_1.ProtobufUtil.isUnion(m.key))) noSupport("union key typed map"); // KEY TYPE IS NOT ATOMIC if (meta.maps.length && meta.maps.some((m) => ProtobufUtil_1.ProtobufUtil.getAtomics(m.key).size !== 1)) noSupport("non-atomic key typed map"); // MAP TYPE, BUT PROPERTY KEY TYPE IS OPTIONAL if (meta.maps.length && meta.maps.some((m) => m.key.isRequired() === false || m.key.nullable)) noSupport("optional key typed map"); // MAP TYPE, BUT VALUE TYPE IS ARRAY if (meta.maps.length && meta.maps.some((m) => !!m.value.arrays.length)) noSupport("map type with array value type"); // UNION WITH MAP if (meta.size() > 1 && meta.maps.length) noSupport("union type with map type"); // UNION IN MAP if (meta.maps.length && meta.maps.some((m) => ProtobufUtil_1.ProtobufUtil.isUnion(m.value))) noSupport("union type in map value type"); return errors; }; }; /* ----------------------------------------------------------- SEQUENE VALIDATOR ----------------------------------------------------------- */ const validateObject = (next) => { for (const property of next.object.properties) validateProperty({ metadata: property.value, errors: next.errors, }); const entire = new Map(); const visitProperty = (p) => { const local = new Set(); const tagger = (matrix) => { matrix.forEach((tags) => { const value = ProtobufUtil_1.ProtobufUtil.getSequence(tags); if (value !== null) local.add(value); }); }; for (const c of p.value.constants) for (const v of c.values) tagger(v.tags); for (const a of p.value.atomics) tagger(a.tags); for (const t of p.value.templates) tagger(t.tags); for (const o of p.value.objects) tagger(o.tags); for (const a of p.value.arrays) tagger(a.tags); for (const s of local) if (entire.has(s)) next.errors.push(`The Sequence<${s}> tag is duplicated in two properties (${JSON.stringify(entire.get(s))} and ${JSON.stringify(p.key.getSoleLiteral())})`); else entire.set(s, p.key.getSoleLiteral()); }; for (const p of next.object.properties) visitProperty(p); }; const validateProperty = (next) => { let expected = 0; const sequences = new Set(); const add = (value) => { if (sequences.has(value)) return false; sequences.add(value); ++expected; return true; }; for (const validator of [ validateBooleanSequence, validateNumericSequences({ type: "bigint", default: "int64", categories: BIGINT_TYPES, }), validateNumericSequences({ type: "number", default: "double", categories: NUMBER_TYPES, }), validateStringSequence, ]) validator({ metadata: next.metadata, errors: next.errors, add }); for (const array of next.metadata.arrays) validateInstanceSequence({ type: "array", tags: array.tags, errors: next.errors, add, }); for (const object of next.metadata.objects) validateInstanceSequence({ type: "object", tags: object.tags, errors: next.errors, add, }); for (const map of next.metadata.maps) validateInstanceSequence({ type: "map", tags: map.tags, errors: next.errors, add, }); for (const native of next.metadata.natives) if (native.name === "Uint8Array") validateInstanceSequence({ type: "Uint8Array", tags: native.tags, errors: next.errors, add, }); }; const validateBooleanSequence = (next) => { // PREPARE EMPLACER const unique = new Set(); let expected = 0; let actual = 0; const emplace = (matrix) => { for (const tags of matrix) for (const tag of tags) { const sequence = ProtobufUtil_1.ProtobufUtil.getSequence([tag]); if (sequence !== null) { unique.add(sequence); ++actual; } ++expected; } }; // GATHER SEQUENCE TAGS for (const atomic of next.metadata.atomics) if (atomic.type === "boolean") emplace(atomic.tags); for (const constant of next.metadata.constants) if (constant.type === "boolean") for (const value of constant.values) emplace(value.tags); // PREDICATE if (unique.size && actual !== expected) next.errors.push(`The sequence tag must be declared in every union type members`); else if (unique.size > 1) next.errors.push(`The sequence tag value must be the same in boolean type (including literal types)`); else if (unique.size === 1) { const value = unique.values().next().value; if (next.add(value) === false) next.errors.push(`The sequence tag value ${value} in boolean type is duplicated with other types`); } }; const validateNumericSequences = (config) => (next) => { // FIND TYPE CATEGORIES const categories = new Set(); const getType = (tags) => { var _a; const found = tags.find((t) => t.kind === "type" && config.categories.has(t.value)); return (_a = found === null || found === void 0 ? void 0 : found.value) !== null && _a !== void 0 ? _a : config.default; }; const exploreCategory = (matrix) => { for (const tags of matrix) categories.add(getType(tags)); }; for (const atomic of next.metadata.atomics) if (atomic.type === config.type) exploreCategory(atomic.tags); for (const constant of next.metadata.constants) if (constant.type === config.type) for (const value of constant.values) exploreCategory(value.tags); // ITERATE TYPE CATEGORIES for (const category of categories) { const unique = new Set(); let expected = 0; let actual = 0; const emplace = (tags) => { const sequence = ProtobufUtil_1.ProtobufUtil.getSequence(tags); if (sequence !== null) { unique.add(sequence); ++actual; } ++expected; }; for (const atomic of next.metadata.atomics) if (atomic.type === config.type) for (const tags of atomic.tags) if (getType(tags) === category) emplace(tags); for (const constant of next.metadata.constants) if (constant.type === config.type) for (const value of constant.values) for (const tags of value.tags) if (getType(tags) === category) emplace(tags); if (unique.size && actual !== expected) { next.errors.push(`The sequence tag must be declared in every union type members`); } else if (unique.size > 1) next.errors.push(`The sequence tag value must be the same in ${config.type} type (including literal types)`); else if (unique.size === 1) { const value = unique.values().next().value; if (next.add(value) === false) next.errors.push(`The sequence tag value ${value} in ${config.type} type is duplicated with other types`); } } }; const validateStringSequence = (next) => { const unique = new Set(); let expected = 0; let actual = 0; const emplace = (matrix) => { for (const tags of matrix) for (const tag of tags) { const sequence = ProtobufUtil_1.ProtobufUtil.getSequence([tag]); if (sequence !== null) { unique.add(sequence); ++actual; } ++expected; } }; for (const atomic of next.metadata.atomics) if (atomic.type === "string") emplace(atomic.tags); for (const constant of next.metadata.constants) if (constant.type === "string") for (const value of constant.values) emplace(value.tags); for (const template of next.metadata.templates) emplace(template.tags); if (unique.size && actual !== expected) next.errors.push(`The sequence tag must be declared in every union type members`); else if (unique.size > 1) next.errors.push(`The sequence tag value must be the same in string types including literal and template types`); else if (unique.size === 1) { const value = unique.values().next().value; if (next.add(value) === false) next.errors.push(`The sequence tag value ${value} in string type is duplicated with other types`); } }; const validateInstanceSequence = (next) => { const unique = new Set(); let count = 0; for (const tags of next.tags) { const value = ProtobufUtil_1.ProtobufUtil.getSequence(tags); if (value === null) continue; unique.add(value); ++count; } if (unique.size && count !== next.tags.length) next.errors.push(`The sequence tag must be declared in every union type members`); else if (unique.size > 1) next.errors.push(`The sequence tag value must be the same in ${next.type === "array" ? "an array" : "object"} type.`); else if (unique.size === 1) { const value = unique.values().next().value; if (next.add(value) === false) next.errors.push(`The sequence tag value ${value} in ${next.type} type is duplicated with other types`); } }; })(ProtobufFactory || (exports.ProtobufFactory = ProtobufFactory = {})); const isDynamicObject = (obj) => obj.properties[0].key.isSoleLiteral() === false; const BANNED_NATIVE_TYPES = new Map([ ["Date", "string"], ["Boolean", "boolean"], ["BigInt", "bigint"], ["Number", "number"], ["String", "string"], ...[ "Buffer", "Uint8ClampedArray", "Uint16Array", "Uint32Array", "BigUint64Array", "Int8Array", "Int16Array", "Int32Array", "BigInt64Array", "Float32Array", "Float64Array", "DataView", "ArrayBuffer", "SharedArrayBuffer", ].map((name) => [name, "Uint8Array"]), ["WeakSet", "Array"], ["WeakMap", "Map"], ]); const NUMBER_TYPES = new Set([ "int32", "uint32", "int64", "uint64", "float", "double", ]); const BIGINT_TYPES = new Set(["int64", "uint64"]); //# sourceMappingURL=ProtobufFactory.js.map