UNPKG

typia

Version:

Superfast runtime validators with only one line

213 lines (202 loc) 6.6 kB
// import ts from "typescript"; import { IMetadataTypeTag } from "../../../schemas/metadata/IMetadataTypeTag"; import { Metadata } from "../../../schemas/metadata/Metadata"; import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; import { MetadataCollection } from "../../MetadataCollection"; import { MetadataFactory } from "../../MetadataFactory"; import { MetadataTypeTagFactory } from "../../MetadataTypeTagFactory"; import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; import { explore_metadata } from "./explore_metadata"; import { iterate_metadata } from "./iterate_metadata"; export const iterate_metadata_intersection = ( props: IMetadataIteratorProps, ): boolean => { if (props.intersected === true) return false; else if (props.type.isIntersection() === false) return false; // CONSTRUCT FAKE METADATA LIST const commit: MetadataCollection = props.collection.clone(); props.collection["options"] = undefined; const fakeErrors: MetadataFactory.IError[] = []; const children: Metadata[] = props.type.types.map((t) => explore_metadata({ ...props, options: { ...props.options, absorb: true, functional: false, }, collection: props.collection, errors: fakeErrors, type: t, explore: { ...props.explore, aliased: false, }, intersected: false, }), ); // ERROR OR ANY TYPE CASE const escape = (out: boolean) => { Object.assign(props.collection, commit); return out; }; if (fakeErrors.length) { props.errors.push(...fakeErrors); return escape(true); } else if (children.length === 0) return escape(false); else if (children.some((m) => m.any === true || m.size() === 0)) return escape(false); // PREPARE MEATDATAS AND TAGS const indexes: number[] = []; const metadatas: Metadata[] = []; const tagObjects: MetadataObjectType[] = []; children.forEach((child, i) => { if ( child.size() === 1 && child.objects.length === 1 && MetadataTypeTagFactory.is(child.objects[0]!.type) ) tagObjects.push(child.objects[0]!.type); else { indexes.push(i); metadatas.push(child); } }); const nonsensible = () => { props.errors.push({ name: children.map((c) => c.getName()).join(" & "), explore: { ...props.explore }, messages: ["nonsensible intersection"], }); return escape(true); }; // NO METADATA CASE if (metadatas.length === 0) if (tagObjects.length !== 0) { props.errors.push({ name: children.map((c) => c.getName()).join(" & "), explore: { ...props.explore }, messages: ["type tag cannot be standalone"], }); return escape(true); } else return escape(false); // ONLY OBJECTS CASE else if ( metadatas.every((m) => m.objects.length === 1) && tagObjects.length === 0 ) return escape(false); else if (metadatas.length !== 1) { const indexes: number[] = metadatas .map((m, i) => m.size() === 1 && m.objects.length === 1 && (m.objects[0]!.type.properties.length === 0 || m.objects[0]!.type.properties.every((p) => p.value.optional === true)) ? i : null, ) .filter((i) => i !== null); if (indexes.length && metadatas.length !== indexes.length) for (const i of indexes.reverse()) metadatas.splice(i, 1); else return nonsensible(); } else if (metadatas.some((m) => m.size() !== 1)) return nonsensible(); const candidates: Map<string, string> = new Map(); const assigners: Array<(tags: IMetadataTypeTag[]) => void> = []; for (const meta of metadatas) { for (const a of meta.atomics) { candidates.set(a.type, a.type); assigners.push((tags) => props.metadata.atomics .find((atom) => atom.type === a.type) ?.tags.push(tags), ); } for (const c of meta.constants) for (const v of c.values) { candidates.set(c.type, c.type); assigners.push((tags) => props.metadata.constants .find((constant) => constant.type === c.type) ?.values.find((value) => value === v) ?.tags?.push(tags), ); } for (const t of meta.templates) { candidates.set("string", "string"); assigners.push((tags) => props.metadata.templates .find((tpl) => tpl.getBaseName() === t.getBaseName()) ?.tags.push(tags), ); } if (meta.objects.length) { candidates.set("object", "object"); assigners.push((tags) => props.metadata.objects.at(-1)?.tags.push(tags)); } if (meta.arrays.length) { candidates.set("array", "array"); assigners.push((tags) => props.metadata.arrays.at(-1)?.tags.push(tags)); } if (meta.tuples.length) candidates.set("invalid", "tuple"); for (const n of meta.natives) { candidates.set(`native::${n.name}`, "object"); assigners.push((tags) => props.metadata.natives .find((native) => native.name === n.name) ?.tags.push(tags), ); } for (const s of meta.sets) { candidates.set(`set::${s.value.getName()}`, "object"); assigners.push((tags) => props.metadata.sets .find((set) => set.value.getName() === s.value.getName()) ?.tags.push(tags), ); } for (const e of meta.maps) { candidates.set(`map::${e.key.getName()}::${e.value.getName()}`, "object"); assigners.push((tags) => props.metadata.maps .find( (map) => map.key.getName() === e.key.getName() && map.value.getName() === e.value.getName(), ) ?.tags.push(tags), ); } } if ( candidates.size !== 1 || candidates.has("nonsensible") // || // (candidates.size !== 1 && // Array.from(candidates.keys()).some((v) => v !== "object")) ) return nonsensible(); const tags: IMetadataTypeTag[] = MetadataTypeTagFactory.analyze({ errors: props.errors, type: candidates.values().next().value as "string", objects: tagObjects, explore: props.explore, }); Object.assign(props.collection, commit); iterate_metadata({ ...props, type: props.type.types[indexes[0]!]!, options: { ...props.options, functional: false, }, explore: { ...props.explore, aliased: false, escaped: false, }, intersected: true, }); if (tags.length) assigners.forEach((fn) => fn(tags)); return true; };