UNPKG

@samchon/openapi

Version:

OpenAPI definitions and converters for 'typia' and 'nestia'.

349 lines (328 loc) 10.9 kB
import { ILlmSchemaV3 } from "../structures/ILlmSchemaV3"; /** * Type checker for LLM type schema. * * `LlmSchemaTypeChecker` is a type checker of {@link ILlmSchemaV3}. * * @author Samchon */ export namespace LlmTypeCheckerV3 { /* ----------------------------------------------------------- OPERATORS ----------------------------------------------------------- */ /** * Visit every nested schemas. * * Visit every nested schemas of the target, and apply the `props.closure` function. * * Here is the list of occurring nested visitings: * * - {@link ILlmSchemaV3.IOneOf.oneOf} * - {@link ILlmSchemaV3.IObject.additionalProperties} * - {@link ILlmSchemaV3.IArray.items} * * @param props Properties for visiting */ export const visit = (props: { closure: (schema: ILlmSchemaV3, accessor: string) => void; schema: ILlmSchemaV3; accessor?: string; }): void => { const accessor: string = props.accessor ?? "$input.schema"; props.closure(props.schema, accessor); if (isOneOf(props.schema)) props.schema.oneOf.forEach((s, i) => visit({ closure: props.closure, schema: s, accessor: `${accessor}.oneOf[${i}]`, }), ); else if (isObject(props.schema)) { for (const [k, s] of Object.entries(props.schema.properties)) visit({ closure: props.closure, schema: s, accessor: `${accessor}.properties[${JSON.stringify(k)}]`, }); if ( typeof props.schema.additionalProperties === "object" && props.schema.additionalProperties !== null ) visit({ closure: props.closure, schema: props.schema.additionalProperties, accessor: `${accessor}.additionalProperties`, }); } else if (isArray(props.schema)) visit({ closure: props.closure, schema: props.schema.items, accessor: `${accessor}.items`, }); }; export const covers = (x: ILlmSchemaV3, y: ILlmSchemaV3): boolean => { const alpha: ILlmSchemaV3[] = flatSchema(x); const beta: ILlmSchemaV3[] = flatSchema(y); if (alpha.some((x) => isUnknown(x))) return true; else if (beta.some((x) => isUnknown(x))) return false; return beta.every((b) => alpha.some((a) => { // CHECK EQUALITY if (a === b) return true; else if (isUnknown(a)) return true; else if (isUnknown(b)) return false; else if (isNullOnly(a)) return isNullOnly(b); else if (isNullOnly(b)) return isNullable(a); else if (isNullable(a) && !isNullable(b)) return false; // ATOMIC CASE else if (isBoolean(a)) return isBoolean(b) && coverBoolean(a, b); else if (isInteger(a)) return isInteger(b) && coverInteger(a, b); else if (isNumber(a)) return (isNumber(b) || isInteger(b)) && coverNumber(a, b); else if (isString(a)) return isString(b) && covertString(a, b); // INSTANCE CASE else if (isArray(a)) return isArray(b) && coverArray(a, b); else if (isObject(a)) return isObject(b) && coverObject(a, b); else if (isOneOf(a)) return false; }), ); }; /** * @internal */ const coverBoolean = ( x: ILlmSchemaV3.IBoolean, y: ILlmSchemaV3.IBoolean, ): boolean => x.enum === undefined || (y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v))); /** * @internal */ const coverInteger = ( x: ILlmSchemaV3.IInteger, y: ILlmSchemaV3.IInteger, ): boolean => { if (x.enum !== undefined) return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v)); return [ x.type === y.type, x.minimum === undefined || (y.minimum !== undefined && x.minimum <= y.minimum), x.maximum === undefined || (y.maximum !== undefined && x.maximum >= y.maximum), x.exclusiveMinimum !== true || x.minimum === undefined || (y.minimum !== undefined && (y.exclusiveMinimum === true || x.minimum < y.minimum)), x.exclusiveMaximum !== true || x.maximum === undefined || (y.maximum !== undefined && (y.exclusiveMaximum === true || x.maximum > y.maximum)), x.multipleOf === undefined || (y.multipleOf !== undefined && y.multipleOf / x.multipleOf === Math.floor(y.multipleOf / x.multipleOf)), ].every((v) => v); }; /** * @internal */ const coverNumber = ( x: ILlmSchemaV3.INumber, y: ILlmSchemaV3.INumber | ILlmSchemaV3.IInteger, ): boolean => { if (x.enum !== undefined) return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v)); return [ x.type === y.type || (x.type === "number" && y.type === "integer"), x.minimum === undefined || (y.minimum !== undefined && x.minimum <= y.minimum), x.maximum === undefined || (y.maximum !== undefined && x.maximum >= y.maximum), x.exclusiveMinimum !== true || x.minimum === undefined || (y.minimum !== undefined && (y.exclusiveMinimum === true || x.minimum < y.minimum)), x.exclusiveMaximum !== true || x.maximum === undefined || (y.maximum !== undefined && (y.exclusiveMaximum === true || x.maximum > y.maximum)), x.multipleOf === undefined || (y.multipleOf !== undefined && y.multipleOf / x.multipleOf === Math.floor(y.multipleOf / x.multipleOf)), ].every((v) => v); }; /** * @internal */ const covertString = ( x: ILlmSchemaV3.IString, y: ILlmSchemaV3.IString, ): boolean => { if (x.enum !== undefined) return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v)); return [ x.type === y.type, x.format === undefined || (y.format !== undefined && coverFormat(x.format, y.format)), x.pattern === undefined || x.pattern === y.pattern, x.minLength === undefined || (y.minLength !== undefined && x.minLength <= y.minLength), x.maxLength === undefined || (y.maxLength !== undefined && x.maxLength >= y.maxLength), ].every((v) => v); }; const coverFormat = ( x: Required<ILlmSchemaV3.IString>["format"], y: Required<ILlmSchemaV3.IString>["format"], ): boolean => x === y || (x === "idn-email" && y === "email") || (x === "idn-hostname" && y === "hostname") || (["uri", "iri"].includes(x) && y === "url") || (x === "iri" && y === "uri") || (x === "iri-reference" && y === "uri-reference"); /** * @internal */ const coverArray = ( x: ILlmSchemaV3.IArray, y: ILlmSchemaV3.IArray, ): boolean => covers(x.items, y.items); const coverObject = ( x: ILlmSchemaV3.IObject, y: ILlmSchemaV3.IObject, ): boolean => { if (!x.additionalProperties && !!y.additionalProperties) return false; else if ( (!!x.additionalProperties && !!y.additionalProperties && typeof x.additionalProperties === "object" && y.additionalProperties === true) || (typeof x.additionalProperties === "object" && typeof y.additionalProperties === "object" && !covers(x.additionalProperties, y.additionalProperties)) ) return false; return Object.entries(y.properties ?? {}).every(([key, b]) => { const a: ILlmSchemaV3 | undefined = x.properties?.[key]; if (a === undefined) return false; else if ( (x.required?.includes(key) ?? false) === true && (y.required?.includes(key) ?? false) === false ) return false; return covers(a, b); }); }; const flatSchema = (schema: ILlmSchemaV3): ILlmSchemaV3[] => isOneOf(schema) ? schema.oneOf.flatMap(flatSchema) : [schema]; /* ----------------------------------------------------------- TYPE CHECKERS ----------------------------------------------------------- */ /** * Test whether the schema is an union type. * * @param schema Target schema * @returns Whether union type or not */ export const isOneOf = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IOneOf => (schema as ILlmSchemaV3.IOneOf).oneOf !== undefined; /** * Test whether the schema is an object type. * * @param schema Target schema * @returns Whether object type or not */ export const isObject = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IObject => (schema as ILlmSchemaV3.IObject).type === "object"; /** * Test whether the schema is an array type. * * @param schema Target schema * @returns Whether array type or not */ export const isArray = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IArray => (schema as ILlmSchemaV3.IArray).type === "array"; /** * Test whether the schema is a boolean type. * * @param schema Target schema * @returns Whether boolean type or not */ export const isBoolean = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IBoolean => (schema as ILlmSchemaV3.IBoolean).type === "boolean"; /** * Test whether the schema is an integer type. * * @param schema Target schema * @returns Whether integer type or not */ export const isInteger = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IInteger => (schema as ILlmSchemaV3.IInteger).type === "integer"; /** * Test whether the schema is a number type. * * @param schema Target schema * @returns Whether number type or not */ export const isNumber = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.INumber => (schema as ILlmSchemaV3.INumber).type === "number"; /** * Test whether the schema is a string type. * * @param schema Target schema * @returns Whether string type or not */ export const isString = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IString => (schema as ILlmSchemaV3.IString).type === "string"; /** * Test whether the schema is a null type. * * @param schema Target schema * @returns Whether null type or not */ export const isNullOnly = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.INullOnly => (schema as ILlmSchemaV3.INullOnly).type === "null"; /** * Test whether the schema is a nullable type. * * @param schema Target schema * @returns Whether nullable type or not */ export const isNullable = (schema: ILlmSchemaV3): boolean => !isUnknown(schema) && (isNullOnly(schema) || (isOneOf(schema) ? schema.oneOf.some(isNullable) : schema.nullable === true)); /** * Test whether the schema is an unknown type. * * @param schema Target schema * @returns Whether unknown type or not */ export const isUnknown = ( schema: ILlmSchemaV3, ): schema is ILlmSchemaV3.IUnknown => !isOneOf(schema) && (schema as ILlmSchemaV3.IUnknown).type === undefined; }