typia
Version:
Superfast runtime validators with only one line
556 lines (553 loc) • 21.1 kB
JavaScript
import { ArrayUtil } from '../../utils/ArrayUtil.mjs';
import { MetadataAlias } from './MetadataAlias.mjs';
import { MetadataArray } from './MetadataArray.mjs';
import { MetadataAtomic } from './MetadataAtomic.mjs';
import { MetadataConstant } from './MetadataConstant.mjs';
import { MetadataEscaped } from './MetadataEscaped.mjs';
import { MetadataFunction } from './MetadataFunction.mjs';
import { MetadataMap } from './MetadataMap.mjs';
import { MetadataNative } from './MetadataNative.mjs';
import { MetadataObject } from './MetadataObject.mjs';
import { MetadataObjectType } from './MetadataObjectType.mjs';
import { MetadataSet } from './MetadataSet.mjs';
import { MetadataTemplate } from './MetadataTemplate.mjs';
import { MetadataTuple } from './MetadataTuple.mjs';
class Metadata {
any;
required;
optional;
nullable;
escaped;
atomics;
constants;
templates;
rest;
aliases;
arrays;
tuples;
objects;
functions;
natives;
sets;
maps;
/** @internal */ name_;
/** @internal */ parent_resolved_ = false;
/** @internal */ union_index;
/** @internal */ fixed_;
/** @internal */ boolean_literal_intersected_;
/** @internal */ is_sequence_;
/* -----------------------------------------------------------
CONSTRUCTORS
----------------------------------------------------------- */
/** @ignore */
constructor(props) {
this.any = props.any;
this.required = props.required;
this.optional = props.optional;
this.nullable = props.nullable;
this.functions = props.functions;
this.escaped = props.escaped;
this.atomics = props.atomics;
this.constants = props.constants;
this.templates = props.templates;
this.rest = props.rest;
this.arrays = props.arrays;
this.tuples = props.tuples;
this.objects = props.objects;
this.aliases = props.aliases;
this.natives = props.natives;
this.sets = props.sets;
this.maps = props.maps;
}
/** @internal */
static create(props) {
return new Metadata(props);
}
/** @internal */
static initialize(parentResolved = false) {
const meta = Metadata.create({
any: false,
nullable: false,
required: true,
optional: false,
escaped: null,
constants: [],
atomics: [],
templates: [],
arrays: [],
tuples: [],
objects: [],
aliases: [],
functions: [],
rest: null,
natives: [],
sets: [],
maps: [],
});
meta.parent_resolved_ = parentResolved;
return meta;
}
toJSON() {
return {
any: this.any,
required: this.required,
optional: this.optional,
nullable: this.nullable,
functions: this.functions.map((f) => f.toJSON()),
atomics: this.atomics.map((a) => a.toJSON()),
constants: this.constants.map((c) => c.toJSON()),
templates: this.templates.map((tpl) => tpl.toJSON()),
escaped: this.escaped ? this.escaped.toJSON() : null,
rest: this.rest ? this.rest.toJSON() : null,
arrays: this.arrays.map((array) => array.toJSON()),
tuples: this.tuples.map((tuple) => tuple.toJSON()),
objects: this.objects.map((obj) => obj.toJSON()),
aliases: this.aliases.map((alias) => alias.toJSON()),
natives: this.natives.map((native) => native.toJSON()),
sets: this.sets.map((set) => set.toJSON()),
maps: this.maps.map((map) => map.toJSON()),
};
}
static from(meta, dict) {
return Metadata.create({
any: meta.any,
required: meta.required,
optional: meta.optional,
nullable: meta.nullable,
functions: meta.functions.map((f) => MetadataFunction.from(f, dict)),
constants: meta.constants.map(MetadataConstant.from),
atomics: meta.atomics.map(MetadataAtomic.from),
templates: meta.templates.map((tpl) => MetadataTemplate.from(tpl, dict)),
escaped: meta.escaped ? MetadataEscaped.from(meta.escaped, dict) : null,
rest: meta.rest ? this.from(meta.rest, dict) : null,
arrays: meta.arrays.map((ref) => {
const type = dict.arrays.get(ref.name);
if (type === undefined)
throw new RangeError(`Error on Metadata.from(): failed to find array "${ref.name}".`);
return MetadataArray.create({
type,
tags: ref.tags.map((row) => row.slice()),
});
}),
tuples: meta.tuples.map((t) => {
const type = dict.tuples.get(t.name);
if (type === undefined)
throw new RangeError(`Error on Metadata.from(): failed to find tuple "${t.name}".`);
return MetadataTuple.create({
type,
tags: t.tags.map((r) => r.slice()),
});
}),
objects: meta.objects.map((obj) => {
const found = dict.objects.get(obj.name);
if (found === undefined)
throw new RangeError(`Error on Metadata.from(): failed to find object "${name}".`);
return MetadataObject.create({
type: found,
tags: obj.tags.map((r) => r.slice()),
});
}),
aliases: meta.aliases.map((alias) => {
const type = dict.aliases.get(alias.name);
if (type === undefined)
throw new RangeError(`Error on Metadata.from(): failed to find alias "${alias}".`);
return MetadataAlias.create({
type,
tags: alias.tags.map((r) => r.slice()),
});
}),
natives: meta.natives.map((native) => MetadataNative.create(native)),
sets: meta.sets.map((set) => MetadataSet.create({
value: Metadata.from(set.value, dict),
tags: set.tags.map((r) => r.slice()),
})),
maps: meta.maps.map((map) => MetadataMap.create({
key: this.from(map.key, dict),
value: this.from(map.value, dict),
tags: map.tags.map((r) => r.slice()),
})),
});
}
/* -----------------------------------------------------------
ACCESSORS
----------------------------------------------------------- */
getName() {
return (this.name_ ??= getName(this));
}
empty() {
return this.bucket() === 0 || this.size() === 0;
}
size() {
return ((this.any ? 1 : 0) +
(this.escaped ? 1 : 0) +
(this.rest ? this.rest.size() : 0) +
this.templates.length +
this.atomics.length +
this.constants.map((c) => c.values.length).reduce((x, y) => x + y, 0) +
this.arrays.length +
this.tuples.length +
this.natives.length +
this.maps.length +
this.sets.length +
this.objects.length +
this.functions.length +
this.aliases.length);
}
bucket() {
return ((this.any ? 1 : 0) +
(this.escaped ? 1 : 0) +
(this.templates.length ? 1 : 0) +
(this.atomics.length ? 1 : 0) +
(this.constants.length ? 1 : 0) +
(this.rest ? this.rest.size() : 0) +
(this.arrays.length ? 1 : 0) +
(this.tuples.length ? 1 : 0) +
(this.natives.length ? 1 : 0) +
(this.sets.length ? 1 : 0) +
(this.maps.length ? 1 : 0) +
(this.objects.length ? 1 : 0) +
(this.functions.length ? 1 : 0) +
(this.aliases.length ? 1 : 0));
}
/** @internal */
isSequence() {
return (this.is_sequence_ ??= (() => {
const exists = (tags) => tags.some((row) => {
const sequence = row.find((t) => t.kind === "sequence" &&
typeof t.schema?.["x-protobuf-sequence"] === "number");
if (sequence === undefined)
return false;
const value = Number(sequence.schema["x-protobuf-sequence"]);
return !Number.isNaN(value);
});
return (this.atomics.some((atomic) => exists(atomic.tags)) &&
this.constants.some((c) => c.values.some((v) => exists(v.tags ?? []))) &&
this.templates.some((tpl) => exists(tpl.tags)) &&
this.arrays.some((array) => exists(array.tags)) &&
this.objects.some((object) => exists(object.tags)) &&
this.natives.some((native) => native.name === "Uint8Array" && exists(native.tags)));
})());
}
isConstant() {
return this.bucket() === (this.constants.length ? 1 : 0);
}
isRequired() {
return this.required === true && this.optional === false;
}
/** @internal */
isUnionBucket() {
const size = this.bucket();
const emended = !!this.atomics.length && !!this.constants.length ? size - 1 : size;
return emended > 1;
}
/** @internal */
getSoleLiteral() {
if (this.size() === 1 &&
this.constants.length === 1 &&
this.constants[0].type === "string" &&
this.constants[0].values.length === 1)
return this.constants[0].values[0].value;
else
return null;
}
isSoleLiteral() {
return this.getSoleLiteral() !== null;
}
/** @internal */
isParentResolved() {
return this.parent_resolved_;
}
}
(function (Metadata) {
Metadata.intersects = (x, y) => {
// CHECK ANY & OPTIONAL
if (x.any || y.any)
return true;
if (x.isRequired() === false && false === y.isRequired())
return true;
if (x.nullable === true && true === y.nullable)
return true;
if (!!x.functions.length && !!y.functions.length === true)
return true;
//----
// INSTANCES
//----
// ARRAYS
if (x.arrays.length && y.arrays.length)
return true;
if (x.tuples.length && y.tuples.length)
return true;
if (x.objects.length && y.objects.length)
return true;
if (x.aliases.length && y.aliases.length)
return true;
// NATIVES
if (x.natives.length && y.natives.length)
if (x.natives.some((xn) => y.natives.some((yn) => xn === yn)))
return true;
// ESCAPED
if (x.escaped && y.escaped)
return (Metadata.intersects(x.escaped.original, y.escaped.original) ||
Metadata.intersects(x.escaped.returns, y.escaped.returns));
//----
// VALUES
//----
// ATOMICS
for (const atomic of x.atomics) {
if (y.atomics.some((ya) => atomic.type === ya.type))
return true;
if (y.constants.some((yc) => atomic.type === yc.type))
return true;
}
// CONSTANTS
for (const constant of x.constants) {
const atomic = y.atomics.find((elem) => elem.type === constant.type);
if (atomic !== undefined)
return true;
const opposite = y.constants.find((elem) => elem.type === constant.type);
if (opposite === undefined)
continue;
const values = new Set([
...constant.values.map((e) => e.value),
...opposite.values.map((e) => e.value),
]);
if (values.size !== constant.values.length + opposite.values.length)
return true;
}
// TEMPLATES
if (!!x.templates.length && y.atomics.some((ya) => ya.type === "string"))
return true;
else if (!!y.templates.length &&
x.atomics.some((xa) => xa.type === "string"))
return true;
return false;
};
Metadata.covers = (x, y, level = 0, escaped = false) => {
// CHECK ANY
if (x === y)
return false;
else if (x.any)
return true;
else if (y.any)
return false;
if (escaped === false) {
if (x.escaped === null && y.escaped !== null)
return false;
else if (x.escaped !== null &&
y.escaped !== null &&
(!Metadata.covers(x.escaped.original, y.escaped.original, level, true) ||
!Metadata.covers(x.escaped.returns, y.escaped.returns, level, true)))
return false;
}
//----
// INSTANCES
//----
if (level === 0) {
// ARRAYS
for (const ya of y.arrays)
if (!x.arrays.some((xa) => Metadata.covers(xa.type.value, ya.type.value, level + 1))) {
return false;
}
// TUPLES
for (const yt of y.tuples)
if (yt.type.elements.length !== 0 &&
x.tuples.some((xt) => xt.type.elements.length >= yt.type.elements.length &&
xt.type.elements
.slice(yt.type.elements.length)
.every((xv, i) => Metadata.covers(xv, yt.type.elements[i], level + 1))) === false)
return false;
}
// OBJECTS
for (const yo of y.objects)
if (x.objects.some((xo) => MetadataObjectType.covers(xo.type, yo.type)) ===
false)
return false;
// ALIASES
for (const yd of y.aliases)
if (x.aliases.some((xd) => xd.type.name === yd.type.name) === false)
return false;
// NATIVES
for (const yn of y.natives)
if (x.natives.some((xn) => xn === yn) === false)
return false;
// SETS
for (const ys of y.sets)
if (x.sets.some((xs) => Metadata.covers(xs.value, ys.value)) === false)
return false;
//----
// VALUES
//----
// ATOMICS
if (y.atomics.some((ya) => x.atomics.some((xa) => xa.type === ya.type) === false))
return false;
// CONSTANTS
for (const yc of y.constants) {
if (x.atomics.some((atom) => yc.type === atom.type))
continue;
const xc = x.constants.find((elem) => elem.type === yc.type);
if (xc === undefined)
return false;
else if (yc.values.map((e) => e.value).some((yv) => xc.values.includes(yv) === false))
return false;
}
// FUNCTIONAL
if (!!x.functions.length === false && !!y.functions.length)
return false;
// SUCCESS
return true;
};
/** @internal */
Metadata.merge = (x, y) => {
const output = Metadata.create({
any: x.any || y.any,
nullable: x.nullable || y.nullable,
required: x.required && y.required,
optional: x.optional || y.optional,
functions: x.functions.length ? x.functions : y.functions, // @todo
escaped: x.escaped !== null && y.escaped !== null
? MetadataEscaped.create({
original: Metadata.merge(x.escaped.original, y.escaped.original),
returns: Metadata.merge(x.escaped.returns, y.escaped.returns),
})
: (x.escaped ?? y.escaped),
atomics: mergeTaggedTypes({
container: x.atomics,
equals: (x, y) => x.type === y.type,
getter: (x) => x.tags,
})(y.atomics),
constants: [...x.constants],
templates: x.templates.slice(),
rest: x.rest !== null && y.rest !== null
? Metadata.merge(x.rest, y.rest)
: (x.rest ?? y.rest),
arrays: mergeTaggedTypes({
container: x.arrays,
equals: (x, y) => x.type.name === y.type.name,
getter: (x) => x.tags,
})(y.arrays),
tuples: mergeTaggedTypes({
container: x.tuples,
equals: (x, y) => x.type.name === y.type.name,
getter: (x) => x.tags,
})(y.tuples),
objects: mergeTaggedTypes({
container: x.objects,
equals: (x, y) => x.type.name === y.type.name,
getter: (x) => x.tags,
})(y.objects),
aliases: mergeTaggedTypes({
container: x.aliases,
equals: (x, y) => x.type.name === y.type.name,
getter: (x) => x.tags,
})(y.aliases),
natives: mergeTaggedTypes({
container: x.natives,
equals: (x, y) => x.name === y.name,
getter: (x) => x.tags,
})(y.natives),
sets: mergeTaggedTypes({
container: x.sets,
equals: (x, y) => x.value.getName() === y.value.getName(),
getter: (x) => x.tags,
})(y.sets),
maps: mergeTaggedTypes({
container: x.maps,
equals: (x, y) => x.key.getName() === y.key.getName() &&
x.value.getName() === y.value.getName(),
getter: (x) => x.tags,
})(y.maps),
});
for (const constant of y.constants) {
const target = ArrayUtil.take(output.constants, (elem) => elem.type === constant.type, () => MetadataConstant.create({
type: constant.type,
values: [],
}));
for (const value of constant.values)
ArrayUtil.add(target.values, value, (a, b) => a.value === b.value);
}
return output;
};
/** @internal */
Metadata.unalias = (w) => {
const visited = new Set();
while (w.size() === 1 &&
w.nullable === false &&
w.isRequired() === true &&
w.aliases.length === 1) {
if (visited.has(w))
break;
w = w.aliases[0].type.value;
visited.add(w);
}
return w;
};
})(Metadata || (Metadata = {}));
const getName = (metadata) => {
if (metadata.any === true)
return "any";
const elements = [];
// OPTIONAL
if (metadata.nullable === true)
elements.push("null");
if (metadata.isRequired() === false)
elements.push("undefined");
// ATOMIC
for (const atom of metadata.atomics) {
elements.push(atom.getName());
}
for (const constant of metadata.constants)
for (const value of constant.values)
elements.push(value.getName());
for (const template of metadata.templates)
elements.push(template.getName());
// NATIVES
for (const native of metadata.natives)
elements.push(native.getName());
for (const set of metadata.sets)
elements.push(set.getName());
for (const map of metadata.maps)
elements.push(map.getName());
// INSTANCES
if (metadata.rest !== null)
elements.push(`...${metadata.rest.getName()}`);
for (const tuple of metadata.tuples)
elements.push(tuple.type.name);
for (const array of metadata.arrays)
elements.push(array.getName());
for (const object of metadata.objects)
elements.push(object.getName());
for (const alias of metadata.aliases)
elements.push(alias.getName());
if (metadata.escaped !== null)
elements.push(metadata.escaped.getName());
// RETURNS
if (elements.length === 0)
return "unknown";
else if (elements.length === 1)
return elements[0];
elements.sort();
return `(${elements.join(" | ")})`;
};
const mergeTaggedTypes = (props) => (opposite) => {
const output = [...props.container];
for (const elem of opposite) {
const equal = props.container.find((x) => props.equals(x, elem));
if (equal === undefined) {
output.push(elem);
continue;
}
const matrix = props
.getter(equal)
.map((tags) => tags.map((t) => t.name))
.sort();
for (const tags of props.getter(elem)) {
const names = tags.map((t) => t.name).sort();
if (matrix.some((m) => m.length === names.length && m.every((s, i) => s === names[i])))
continue;
props.getter(equal).push(tags);
}
}
return output;
};
export { Metadata };
//# sourceMappingURL=Metadata.mjs.map