@samchon/openapi
Version: 
OpenAPI definitions and converters for 'typia' and 'nestia'.
349 lines (328 loc) • 10.9 kB
text/typescript
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;
}