typia
Version:
Superfast runtime validators with only one line
695 lines • 29.4 kB
JavaScript
;
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