UNPKG

typia

Version:

Superfast runtime validators with only one line

268 lines (252 loc) • 8.17 kB
import ts from "typescript"; import { Metadata } from "../schemas/metadata/Metadata"; import { MetadataObject } from "../schemas/metadata/MetadataObject"; import { ProtobufUtil } from "../programmers/helpers/ProtobufUtil"; import { TransformerError } from "../transformers/TransformerError"; import { ValidationPipe } from "../typings/ValidationPipe"; import { Escaper } from "../utils/Escaper"; import { MetadataCollection } from "./MetadataCollection"; import { MetadataFactory } from "./MetadataFactory"; export namespace ProtobufFactory { export const metadata = (method: string) => (checker: ts.TypeChecker, context?: ts.TransformationContext) => (collection: MetadataCollection) => (type: ts.Type): Metadata => { // COMPOSE METADATA WITH INDIVIDUAL VALIDATIONS const result: ValidationPipe<Metadata, MetadataFactory.IError> = MetadataFactory.analyze( checker, context, )({ escape: false, constant: true, absorb: true, validate, })(collection)(type); if (result.success === false) throw TransformerError.from(`typia.protobuf.${method}`)(result.errors); return result.data; }; const validate = ( meta: Metadata, explore: MetadataFactory.IExplore, ): string[] => { const errors: string[] = []; const insert = (msg: string) => errors.push(msg); if (explore.top === true) { const onlyObject: boolean = meta.size() === 1 && meta.objects.length === 1 && meta.objects[0]!.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"); } //---- // NOT SUPPORTED TYPES //---- const noSupport = (msg: string) => 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 === "Uint8Array") continue; const instead = BANNED_NATIVE_TYPES.get(native); if (instead === undefined) noSupport(`${native} type`); else noSupport(`${native} type. Use ${instead} type instead.`); } //---- // ATOMIC CASES //---- if (meta.atomics.length) { const numbers = ProtobufUtil.getNumbers(meta); const bigints = ProtobufUtil.getBigints(meta); for (const type of ["int64", "uint64"]) if (numbers.some((n) => n === type) && bigints.some((b) => b === type)) insert( `tags.Type<"${type}"> cannot be used in both number and bigint types. Recommend to remove from number type`, ); } //---- // ARRRAY CASES //---- // DO NOT ALLOW MULTI-DIMENTIONAL ARRAY if ( meta.arrays.length && meta.arrays.some((array) => !!array.type.value.arrays.length) ) noSupport("over two dimenstional 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) => a.type.value.size() > 1 && a.type.value.constants.length !== 1 && a.type.value.constants[0]?.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.isStaticObject(o) === 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.properties.length === 0) ) noSupport("empty object type"); // MULTIPLE DYNAMIC KEY TYPED PROPERTIES if ( meta.objects.length && meta.objects.some( (obj) => obj.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.properties.some((p) => p.key.isSoleLiteral()) && obj.properties.some((p) => !p.key.isSoleLiteral()), ) ) noSupport( "object type with mixed static and dynamic key typed properties. Keep statics or dynamic only.", ); // STATIC PROPERTY, BUT INVALID KEY NAME if ( meta.objects.length && meta.objects.some((obj) => obj.properties.some( (p) => p.key.isSoleLiteral() === true && Escaper.variable(p.key.getSoleLiteral()!) === false, ), ) ) noSupport(`object type with invalid static key name.`); // DYNAMIC OBJECT, BUT PROPERTY VALUE TYPE IS ARRAY if ( meta.objects.length && isDynamicObject(meta.objects[0]!) && meta.objects[0]!.properties.some((p) => !!p.value.arrays.length) ) noSupport("dynamic object with array value type"); // UNION WITH DYNAMIC OBJECT if ( meta.size() > 1 && meta.objects.length && isDynamicObject(meta.objects[0]!) ) noSupport("union type with dynamic object type"); // UNION IN DYNAMIC PROPERTY VALUE if ( meta.objects.length && meta.objects.some( (obj) => isDynamicObject(obj) && obj.properties.some((p) => 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.isUnion(m.key))) noSupport("union key typed map"); // KEY TYPE IS NOT ATOMIC if ( meta.maps.length && meta.maps.some((m) => ProtobufUtil.getAtomics(m.key).length !== 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.isUnion(m.value)) ) noSupport("union type in map value type"); return errors; }; } const isDynamicObject = (obj: MetadataObject): boolean => obj.properties[0]!.key.isSoleLiteral() === false; const BANNED_NATIVE_TYPES: Map<string, string | null> = 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"] as const), ["WeakSet", "Array"], ["WeakMap", "Map"], ]);