= {
+ [P in keyof T]?: T[P] extends HeaderValue | undefined
+ ? P extends string
+ ? Lowercase extends "set-cookie"
+ ? T[P] extends Array
+ ? T[P] | undefined
+ : never
+ : Lowercase extends
+ | "age"
+ | "authorization"
+ | "content-length"
+ | "content-type"
+ | "etag"
+ | "expires"
+ | "from"
+ | "host"
+ | "if-modified-since"
+ | "if-unmodified-since"
+ | "last-modified"
+ | "location"
+ | "max-forwards"
+ | "proxy-authorization"
+ | "referer"
+ | "retry-after"
+ | "server"
+ | "user-agent"
+ ? T[P] extends Array
+ ? never
+ : T[P] | undefined
+ : T[P] | undefined
+ : never
+ : never;
+ };
+}
diff --git a/src/structures/IHttpLlmApplication.ts b/src/structures/IHttpLlmApplication.ts
new file mode 100644
index 0000000..98ba33d
--- /dev/null
+++ b/src/structures/IHttpLlmApplication.ts
@@ -0,0 +1,210 @@
+import { OpenApi } from "../OpenApi";
+import { IHttpLlmFunction } from "./IHttpLlmFunction";
+import { IHttpMigrateRoute } from "./IHttpMigrateRoute";
+import { ILlmSchema } from "./ILlmSchema";
+
+/**
+ * Application of LLM function call from OpenAPI document.
+ *
+ * `IHttpLlmApplication` is a data structure representing collection of
+ * {@link IHttpLlmFunction LLM function calling schemas} composed from the
+ * {@link OpenApi.IDocument OpenAPI document} and its {@link OpenApi.IOperation operation}
+ * metadata. It also contains {@link IHttpLlmApplication.errors failed operations}, and
+ * adjusted {@link IHttpLlmApplication.options options} during the `IHttpLlmApplication`
+ * construction.
+ *
+ * About the {@link OpenApi.IOperation API operations}, they are converted to
+ * {@link IHttpLlmFunction} type which represents LLM function calling schema.
+ * By the way, if tehre're some recursive types which can't escape the
+ * {@link OpenApi.IJsonSchema.IReference} type, the operation would be failed and
+ * pushed into the {@link IHttpLlmApplication.errors}. Otherwise not, the operation
+ * would be successfully converted to {@link IHttpLlmFunction} and its type schemas
+ * are downgraded to {@link OpenApiV3.IJsonSchema} and converted to {@link ILlmSchema}.
+ *
+ * About the options, if you've configured {@link IHttpLlmApplication.options.keyword}
+ * (as `true`), number of {@link IHttpLlmFunction.parameters} are always 1 and the first
+ * parameter type is always {@link ILlmSchema.IObject}. Otherwise, the parameters would
+ * be multiple, and the sequence of the parameters are following below rules.
+ *
+ * - `pathParameters`: Path parameters of {@link IHttpMigrateRoute.parameters}
+ * - `query`: Query parameter of {@link IHttpMigrateRoute.query}
+ * - `body`: Body parameter of {@link IHttpMigrateRoute.body}
+ *
+ * ```typescript
+ * // KEYWORD TRUE
+ * {
+ * ...pathParameters,
+ * query,
+ * body,
+ * }
+ *
+ * // KEYWORD FALSE
+ * [
+ * ...pathParameters,
+ * ...(query ? [query] : []),
+ * ...(body ? [body] : []),
+ * ]
+ * ```
+ *
+ * By the way, there can be some parameters (or their nested properties) which must be
+ * composed by human, not by LLM. File uploading feature or some sensitive information
+ * like secrety key (password) are the examples. In that case, you can separate the
+ * function parameters to both LLM and human sides by configuring the
+ * {@link IHttpLlmApplication.IOptions.separate} property. The separated parameters are
+ * assigned to the {@link IHttpLlmFunction.separated} property.
+ *
+ * For reference, the actual function call execution is not by LLM, but by you.
+ * When the LLM selects the proper function and fills the arguments, you just call
+ * the function by {@link HttpLlm.execute} with the LLM prepared arguments. And then
+ * informs the return value to the LLM by system prompt. The LLM will continue the next
+ * conversation based on the return value.
+ *
+ * Additionally, if you've configured {@link IHttpLlmApplication.IOptions.separate},
+ * so that the parameters are separated to human and LLM sides, you can merge these
+ * humand and LLM sides' parameters into one through {@link HttpLlm.mergeParameters}
+ * before the actual LLM function call execution.
+ *
+ * @author Jeongho Nam - https://github.com/samchon
+ */
+export interface IHttpLlmApplication<
+ Schema extends ILlmSchema = ILlmSchema,
+ Operation extends OpenApi.IOperation = OpenApi.IOperation,
+ Route extends IHttpMigrateRoute = IHttpMigrateRoute,
+> {
+ /**
+ * Version of OpenAPI.
+ *
+ * LLM function call schema is based on OpenAPI 3.0.3 specification.
+ */
+ openapi: "3.0.3";
+
+ /**
+ * List of function metadata.
+ *
+ * List of function metadata that can be used for the LLM function call.
+ *
+ * When you want to execute the function with LLM constructed arguments,
+ * you can do it through {@link LlmFetcher.execute} function.
+ */
+ functions: IHttpLlmFunction[];
+
+ /**
+ * List of errors occurred during the composition.
+ */
+ errors: IHttpLlmApplication.IError[];
+
+ /**
+ * Options for the document.
+ *
+ * Adjusted options when composing the document through
+ * {@link HttpLlm.application} function.
+ */
+ options: IHttpLlmApplication.IOptions;
+}
+export namespace IHttpLlmApplication {
+ /**
+ * Error occurred in the composition.
+ */
+ export interface IError<
+ Operation extends OpenApi.IOperation = OpenApi.IOperation,
+ Route extends IHttpMigrateRoute = IHttpMigrateRoute,
+ > {
+ /**
+ * HTTP method of the endpoint.
+ */
+ method: "get" | "post" | "put" | "patch" | "delete" | "head";
+
+ /**
+ * Path of the endpoint.
+ */
+ path: string;
+
+ /**
+ * Error messsages.
+ */
+ messages: string[];
+
+ /**
+ * Get the Swagger operation metadata.
+ *
+ * Get the Swagger operation metadata, of the source.
+ */
+ operation: () => Operation;
+
+ /**
+ * Get the migration route metadata.
+ *
+ * Get the migration route metadata, of the source.
+ *
+ * If the property returns `undefined`, it means that the error has
+ * been occured in the migration level, not of LLM document composition.
+ *
+ * @returns Migration route metadata.
+ */
+ route: () => Route | undefined;
+ }
+
+ /**
+ * Options for composing the LLM document.
+ */
+ export interface IOptions {
+ /**
+ * Whether the parameters are keyworded or not.
+ *
+ * If this property value is `true`, length of the
+ * {@link IHttpLlmApplication.IFunction.parameters} is always 1, and type of
+ * the pararameter is always {@link ILlmSchema.IObject} type.
+ *
+ * Otherwise, the parameters would be multiple, and the sequence of the parameters
+ * are following below rules.
+ *
+ * ```typescript
+ * // KEYWORD TRUE
+ * {
+ * ...pathParameters,
+ * query,
+ * body,
+ * }
+ *
+ * // KEYWORD FALSE
+ * [
+ * ...pathParameters,
+ * ...(query ? [query] : []),
+ * ...(body ? [body] : []),
+ * ]
+ * ```
+ *
+ * @default false
+ */
+ keyword: boolean;
+
+ /**
+ * Separator function for the parameters.
+ *
+ * When composing parameter arguments through LLM function call,
+ * there can be a case that some parameters must be composed by human,
+ * or LLM cannot understand the parameter. For example, if the
+ * parameter type has configured
+ * {@link ILlmSchema.IString.contentMediaType} which indicates file
+ * uploading, it must be composed by human, not by LLM
+ * (Large Language Model).
+ *
+ * In that case, if you configure this property with a function that
+ * predicating whether the schema value must be composed by human or
+ * not, the parameters would be separated into two parts.
+ *
+ * - {@link IHttpLlmFunction.separated.llm}
+ * - {@link IHttpLlmFunction.separated.human}
+ *
+ * When writing the function, note that returning value `true` means
+ * to be a human composing the value, and `false` means to LLM
+ * composing the value. Also, when predicating the schema, it would
+ * better to utilize the {@link LlmTypeChecker} features.
+ *
+ * @param schema Schema to be separated.
+ * @returns Whether the schema value must be composed by human or not.
+ * @default null
+ */
+ separate: null | ((schema: Schema) => boolean);
+ }
+}
diff --git a/src/structures/IHttpLlmFunction.ts b/src/structures/IHttpLlmFunction.ts
new file mode 100644
index 0000000..a36e99d
--- /dev/null
+++ b/src/structures/IHttpLlmFunction.ts
@@ -0,0 +1,239 @@
+import { OpenApi } from "../OpenApi";
+import { IHttpMigrateRoute } from "./IHttpMigrateRoute";
+import { ILlmSchema } from "./ILlmSchema";
+
+/**
+ * LLM function calling schema from HTTP (OpenAPI) operation.
+ *
+ * `IHttpLlmFunction` is a data structure representing a function converted
+ * from the {@link OpenApi.IOperation OpenAPI operation}, used for the LLM
+ * (Large Language Model) function calling. It's a typical RPC (Remote Procedure Call)
+ * structure containing the function {@link name}, {@link parameters}, and
+ * {@link output return type}.
+ *
+ * If you provide this `IHttpLlmFunction` data to the LLM provider like "OpenAI",
+ * the "OpenAI" will compose a function arguments by analyzing conversations with
+ * the user. With the LLM composed arguments, you can execute the function through
+ * {@link LlmFetcher.execute} and get the result.
+ *
+ * For reference, different between `IHttpLlmFunction` and its origin source
+ * {@link OpenApi.IOperation} is, `IHttpLlmFunction` has converted every type schema
+ * informations from {@link OpenApi.IJsonSchema} to {@link ILlmSchema} to escape
+ * {@link OpenApi.IJsonSchema.IReference reference types}, and downgrade the version
+ * of the JSON schema to OpenAPI 3.0. It's because LLM function call feature cannot
+ * understand both reference types and OpenAPI 3.1 specification.
+ *
+ * Additionally, if you've composed `IHttpLlmFunction` with
+ * {@link IHttpLlmApplication.IOptions.keyword} configuration as `true`, number of
+ * {@link IHttpLlmFunction.parameters} are always 1 and the first parameter's
+ * type is always {@link ILlmSchema.IObject}. The properties' rule is:
+ *
+ * - `pathParameters`: Path parameters of {@link OpenApi.IOperation.parameters}
+ * - `query`: Query parameter of {@link IHttpMigrateRoute.query}
+ * - `body`: Body parameter of {@link IHttpMigrateRoute.body}
+ *
+ * ```typescript
+ * {
+ * ...pathParameters,
+ * query,
+ * body,
+ * }
+ * ```
+ *
+ * Otherwise, the parameters would be multiple, and the sequence of the parameters
+ * are following below rules:
+ *
+ * ```typescript
+ * [
+ * ...pathParameters,
+ * ...(query ? [query] : []),
+ * ...(body ? [body] : []),
+ * ]
+ * ```
+ *
+ * @reference https://platform.openai.com/docs/guides/function-calling
+ * @author Jeongho Nam - https://github.com/samchon
+ */
+export interface IHttpLlmFunction<
+ Schema extends ILlmSchema = ILlmSchema,
+ Operation extends OpenApi.IOperation = OpenApi.IOperation,
+ Route extends IHttpMigrateRoute = IHttpMigrateRoute,
+> {
+ /**
+ * HTTP method of the endpoint.
+ */
+ method: "get" | "post" | "patch" | "put" | "delete";
+
+ /**
+ * Path of the endpoint.
+ */
+ path: string;
+
+ /**
+ * Representative name of the function.
+ *
+ * The `name` is a repsentative name identifying the function in the
+ * {@link IHttpLlmApplication}. The `name` value is just composed by joining the
+ * {@link IHttpMigrateRoute.accessor} by underscore `_` character.
+ *
+ * Here is the composition rule of the {@link IHttpMigrateRoute.accessor}:
+ *
+ * > The `accessor` is composed with the following rules. At first,
+ * > namespaces are composed by static directory names in the {@link path}.
+ * > Parametric symbols represented by `:param` or `{param}` cannot be
+ * > a part of the namespace.
+ * >
+ * > Instead, they would be a part of the function name. The function
+ * > name is composed with the {@link method HTTP method} and parametric
+ * > symbols like `getByParam` or `postByParam`. If there are multiple
+ * > path parameters, they would be concatenated by `And` like
+ * > `getByParam1AndParam2`.
+ * >
+ * > For refefence, if the {@link operation}'s {@link method} is `delete`,
+ * > the function name would be replaced to `erase` instead of `delete`.
+ * > It is the reason why the `delete` is a reserved keyword in many
+ * > programming languages.
+ * >
+ * > - Example 1
+ * > - path: `POST /shopping/sellers/sales`
+ * > - accessor: `shopping.sellers.sales.post`
+ * > - Example 2
+ * > - endpoint: `GET /shoppings/sellers/sales/:saleId/reviews/:reviewId/comments/:id
+ * > - accessor: `shoppings.sellers.sales.reviews.getBySaleIdAndReviewIdAndCommentId`
+ */
+ name: string;
+
+ /**
+ * Whether the function schema types are strict or not.
+ *
+ * Newly added specification to "OpenAI" at 2024-08-07.
+ *
+ * @reference https://openai.com/index/introducing-structured-outputs-in-the-api/
+ */
+ strict: true;
+
+ /**
+ * List of parameter types.
+ *
+ * If you've configured {@link IHttpLlmApplication.IOptions.keyword} as `true`,
+ * number of {@link IHttpLlmFunction.parameters} are always 1 and the first
+ * parameter's type is always {@link ILlmSchema.IObject}. The
+ * properties' rule is:
+ *
+ * - `pathParameters`: Path parameters of {@link IHttpMigrateRoute.parameters}
+ * - `query`: Query parameter of {@link IHttpMigrateRoute.query}
+ * - `body`: Body parameter of {@link IHttpMigrateRoute.body}
+ *
+ * ```typescript
+ * {
+ * ...pathParameters,
+ * query,
+ * body,
+ * }
+ * ```
+ *
+ * Otherwise, the parameters would be multiple, and the sequence of the
+ * parameters are following below rules:
+ *
+ * ```typescript
+ * [
+ * ...pathParameters,
+ * ...(query ? [query] : []),
+ * ...(body ? [body] : []),
+ * ]
+ * ```
+ */
+ parameters: Schema[];
+
+ /**
+ * Collection of separated parameters.
+ *
+ * Filled only when {@link IHttpLlmApplication.IOptions.separate} is configured.
+ */
+ separated?: IHttpLlmFunction.ISeparated;
+
+ /**
+ * Expected return type.
+ *
+ * If the target operation returns nothing (`void`), the `output`
+ * would be `undefined`.
+ */
+ output?: Schema | undefined;
+
+ /**
+ * Description of the function.
+ *
+ * `IHttpLlmFunction.description` is composed by below rule:
+ *
+ * 1. Starts from the {@link OpenApi.IOperation.summary} paragraph.
+ * 2. The next paragraphs are filled with the
+ * {@link OpenApi.IOperation.description}. By the way, if the first
+ * paragraph of {@link OpenApi.IOperation.description} is same with the
+ * {@link OpenApi.IOperation.summary}, it would not be duplicated.
+ * 3. Parameters' descriptions are added with `@param` tag.
+ * 4. {@link OpenApi.IOperation.security Security requirements} are added
+ * with `@security` tag.
+ * 5. Tag names are added with `@tag` tag.
+ * 6. If {@link OpenApi.IOperation.deprecated}, `@deprecated` tag is added.
+ *
+ * For reference, the `description` is very important property to teach
+ * the purpose of the function to the LLM (Language Large Model), and
+ * LLM actually determines which function to call by the description.
+ *
+ * Also, when the LLM conversates with the user, the `description` is
+ * used to explain the function to the user. Therefore, the `description`
+ * property has the highest priroity, and you have to consider it.
+ */
+ description?: string;
+
+ /**
+ * Get the Swagger operation metadata.
+ *
+ * Get the Swagger operation metadata, of the source.
+ *
+ * @returns Swagger operation metadata.
+ */
+ operation: () => Operation;
+
+ /**
+ * Get the migration route metadata.
+ *
+ * Get the migration route metadata, of the source.
+ *
+ * @returns Migration route metadata.
+ */
+ route: () => Route;
+}
+export namespace IHttpLlmFunction {
+ /**
+ * Collection of separated parameters.
+ */
+ export interface ISeparated {
+ /**
+ * Parameters that would be composed by the LLM.
+ */
+ llm: ISeparatedParameter[];
+
+ /**
+ * Parameters that would be composed by the human.
+ */
+ human: ISeparatedParameter[];
+ }
+
+ /**
+ * Separated parameter.
+ */
+ export interface ISeparatedParameter {
+ /**
+ * Index of the parameter.
+ *
+ * @type uint
+ */
+ index: number;
+
+ /**
+ * Type schema info of the parameter.
+ */
+ schema: Schema;
+ }
+}
diff --git a/src/IMigrateDocument.ts b/src/structures/IHttpMigrateApplication.ts
similarity index 62%
rename from src/IMigrateDocument.ts
rename to src/structures/IHttpMigrateApplication.ts
index 89d7230..737d1ed 100644
--- a/src/IMigrateDocument.ts
+++ b/src/structures/IHttpMigrateApplication.ts
@@ -1,34 +1,40 @@
-import { IMigrateRoute } from "./IMigrateRoute";
-import { OpenApi } from "./OpenApi";
+import { OpenApi } from "../OpenApi";
+import { IHttpMigrateRoute } from "./IHttpMigrateRoute";
/**
* Document of migration.
*
- * The `IMigrateDocument` interface is a document of migration from
- * {@link OpenAPI.IDocument OpenAPI document} to RPC (Remote Procedure Call)
- * functions; {@link IMigrateRoute}.
+ * The `IHttpMigrateApplication` interface is an application migrated from
+ * {@link OpenAPI.IDocument OpenAPI document} for supporting the OpenAPI generator
+ * libraries which compose RPC (Remote Procedure Call) functions from the
+ * {@link OpenAPI.IOperation OpenAPI operations}.
*
- * As the `IMigrateDocument` and {@link IMigrateRoute} have a lot of special
+ * As the `IHttpMigrateApplication` and {@link IHttpMigrateRoute} have a lot of special
* stories, when you're developing OpenAPI generator library, please read
* their descriptions carefully including the description of properties.
*
* @author Jeongho Nam - https://github.com/samchon
*/
-export interface IMigrateDocument<
+export interface IHttpMigrateApplication<
Schema extends OpenApi.IJsonSchema = OpenApi.IJsonSchema,
Operation extends OpenApi.IOperation = OpenApi.IOperation,
> {
/**
* List of routes for migration.
*/
- routes: IMigrateRoute[];
+ routes: IHttpMigrateRoute[];
/**
* List of errors occurred during the migration.
*/
- errors: IMigrateDocument.IError[];
+ errors: IHttpMigrateApplication.IError[];
+
+ /**
+ * Source OpenAPI document.
+ */
+ document: () => OpenApi.IDocument;
}
-export namespace IMigrateDocument {
+export namespace IHttpMigrateApplication {
/**
* Error of migration in the operation level.
*/
diff --git a/src/IMigrateRoute.ts b/src/structures/IHttpMigrateRoute.ts
similarity index 87%
rename from src/IMigrateRoute.ts
rename to src/structures/IHttpMigrateRoute.ts
index bd297d7..5341273 100644
--- a/src/IMigrateRoute.ts
+++ b/src/structures/IHttpMigrateRoute.ts
@@ -1,19 +1,19 @@
-import { OpenApi } from "./OpenApi";
+import { OpenApi } from "../OpenApi";
/**
* Route information for migration.
*
- * The `IMigrateRoute` is a structure representing a route information for
- * OpenAPI generated RPC (Remote Procedure Call) function composed from the
- * {@link OpenApi.IOperation OpenAPI operation}.
+ * The `IHttpMigrateRoute` is a structure representing a route information for
+ * OpenAPI generator libraries, which composes an RPC (Remote Procedure Call) function
+ * from the {@link OpenApi.IOperation OpenAPI operation}.
*
- * As the `IMigrateRoute` has a lot of speical stories, when you're developing
+ * As the `IHttpMigrateRoute` has a lot of speical stories, when you're developing
* OpenAPI generator library, please read its description carefully including
* the description of its properties.
*
* @author Jeongho Nam - https://github.com/samchon
*/
-export interface IMigrateRoute<
+export interface IHttpMigrateRoute<
Schema extends OpenApi.IJsonSchema = OpenApi.IJsonSchema,
Operation extends OpenApi.IOperation = OpenApi.IOperation,
> {
@@ -78,7 +78,7 @@ export interface IMigrateRoute<
*
* Note that, not a list of every parameters, but only path parameters.
*/
- parameters: IMigrateRoute.IParameter[];
+ parameters: IHttpMigrateRoute.IParameter[];
/**
* Metadata of headers.
@@ -86,16 +86,16 @@ export interface IMigrateRoute<
* The `headers` property is a metadata of HTTP request headers for RPC function,
* including the parameter variable name and schema.
*
- * Also, its {@link IMigrateRoute.IHeaders.schema} is always object or reference
+ * Also, its {@link IHttpMigrateRoute.IHeaders.schema} is always object or reference
* to object. Even though the original {@link OpenApi.IOperation OpenAPI operation}'s
* headers are separated to atomic typed properties, the `headers` property forcibly
* combines them into a single object type.
*
* For reference, if the `headers` property has been converted to an object type
- * forcibly, its property {@link IMigrateRoute.IHeaders.name name} and
- * {@link IMigrateRoute.IHeaders.key key} are always "headers".
+ * forcibly, its property {@link IHttpMigrateRoute.IHeaders.name name} and
+ * {@link IHttpMigrateRoute.IHeaders.key key} are always "headers".
*/
- headers: IMigrateRoute.IHeaders | null;
+ headers: IHttpMigrateRoute.IHeaders | null;
/**
* Metadata of query values.
@@ -103,16 +103,16 @@ export interface IMigrateRoute<
* The `query` property is a metadata of HTTP request query values for RPC function,
* including the parameter variable name and schema.
*
- * Also, its {@link IMigrateRoute.IQuery.schema} is always object or reference
+ * Also, its {@link IHttpMigrateRoute.IQuery.schema} is always object or reference
* to object. Even though the original {@link OpenApi.IOperation OpenAPI operation}'s
* query parameters are separated to atomic typed properties, the `query` property
* forcibly combines them into a single object type.
*
* For reference, if the `query` property has been converted to an object type
- * forcibly, its property {@link IMigrateRoute.IQuery.name name} and
- * {@link IMigrateRoute.IQuery.key key} are always "headers".
+ * forcibly, its property {@link IHttpMigrateRoute.IQuery.name name} and
+ * {@link IHttpMigrateRoute.IQuery.key key} are always "headers".
*/
- query: IMigrateRoute.IQuery | null;
+ query: IHttpMigrateRoute.IQuery | null;
/**
* Metadata of request body.
@@ -123,7 +123,7 @@ export interface IMigrateRoute<
* If the `body` property is `null`, it means the operation does not require
* the request body data.
*/
- body: IMigrateRoute.IBody | null;
+ body: IHttpMigrateRoute.IBody | null;
/**
* Metadata of response body for success case.
@@ -134,7 +134,7 @@ export interface IMigrateRoute<
* If the `success` property is `null`, it means the operation does not have
* the response body data. In other words, the RPC function would return `void`.
*/
- success: IMigrateRoute.IBody | null;
+ success: IHttpMigrateRoute.IBody | null;
/**
* Metadata of response body for exceptional status cases.
@@ -147,7 +147,7 @@ export interface IMigrateRoute<
* stringified number, but sometimes it could be a string like "default",
* because the OpenAPI document allows the status code to be a string.
*/
- exceptions: Record>;
+ exceptions: Record>;
/**
* Description comment for the route function.
@@ -173,7 +173,7 @@ export interface IMigrateRoute<
*/
operation: () => Operation;
}
-export namespace IMigrateRoute {
+export namespace IHttpMigrateRoute {
/**
* Metadata of path parameter.
*/
diff --git a/src/structures/IHttpResponse.ts b/src/structures/IHttpResponse.ts
new file mode 100644
index 0000000..47684eb
--- /dev/null
+++ b/src/structures/IHttpResponse.ts
@@ -0,0 +1,25 @@
+/**
+ * Represents an HTTP response.
+ *
+ * The `IHttpResponse` interface represents an HTTP response.
+ *
+ * It contains the {@link status} code, {@link headers}, and {@link body} of the response.
+ *
+ * @author Jeongho Nam - https://github.com/samchon
+ */
+export interface IHttpResponse {
+ /**
+ * Status code of the response.
+ */
+ status: number;
+
+ /**
+ * Headers of the response.
+ */
+ headers: Record;
+
+ /**
+ * Body of the response.
+ */
+ body: unknown;
+}
diff --git a/src/structures/ILlmApplication.ts b/src/structures/ILlmApplication.ts
new file mode 100644
index 0000000..014b8c7
--- /dev/null
+++ b/src/structures/ILlmApplication.ts
@@ -0,0 +1,48 @@
+import { ILlmFunction } from "./ILlmFunction";
+import { ILlmSchema } from "./ILlmSchema";
+
+export interface ILlmApplication {
+ /**
+ * List of function metadata.
+ *
+ * List of function metadata that can be used for the LLM function call.
+ */
+ functions: ILlmFunction[];
+
+ /**
+ * Options for the document.
+ */
+ options: ILlmApplication.IOptions;
+}
+export namespace ILlmApplication {
+ export interface IOptions {
+ /**
+ * Separator function for the parameters.
+ *
+ * When composing parameter arguments through LLM function call,
+ * there can be a case that some parameters must be composed by human,
+ * or LLM cannot understand the parameter. For example, if the
+ * parameter type has configured
+ * {@link ILlmSchema.IString.contentMediaType} which indicates file
+ * uploading, it must be composed by human, not by LLM
+ * (Large Language Model).
+ *
+ * In that case, if you configure this property with a function that
+ * predicating whether the schema value must be composed by human or
+ * not, the parameters would be separated into two parts.
+ *
+ * - {@link ILlmFunction.separated.llm}
+ * - {@link ILlmFunction.separated.human}
+ *
+ * When writing the function, note that returning value `true` means
+ * to be a human composing the value, and `false` means to LLM
+ * composing the value. Also, when predicating the schema, it would
+ * better to utilize the {@link LlmTypeChecker} features.
+ *
+ * @param schema Schema to be separated.
+ * @returns Whether the schema value must be composed by human or not.
+ * @default null
+ */
+ separate: null | ((schema: Schema) => boolean);
+ }
+}
diff --git a/src/structures/ILlmFunction.ts b/src/structures/ILlmFunction.ts
new file mode 100644
index 0000000..e0cef71
--- /dev/null
+++ b/src/structures/ILlmFunction.ts
@@ -0,0 +1,94 @@
+import { ILlmSchema } from "./ILlmSchema";
+
+/**
+ * LLM function metadata.
+ *
+ * `ILlmFunction` is an interface representing a function metadata,
+ * which has been used for the LLM (Language Large Model) function
+ * calling. Also, it's a function structure containing the function
+ * {@link name}, {@link parameters} and {@link output return type}.
+ *
+ * If you provide this `ILlmFunction` data to the LLM provider like "OpenAI",
+ * the "OpenAI" will compose a function arguments by analyzing conversations
+ * with the user. With the LLM composed arguments, you can execute the function
+ * and get the result.
+ *
+ * By the way, do not ensure that LLM will always provide the correct
+ * arguments. The LLM of present age is not perfect, so that you would
+ * better to validate the arguments before executing the function.
+ * I recommend you to validate the arguments before execution by using
+ * [`typia`](https://github.com/samchon/typia) library.
+ *
+ * @reference https://platform.openai.com/docs/guides/function-calling
+ * @author Jeongho Nam - https://github.com/samchon
+ */
+export interface ILlmFunction {
+ /**
+ * Representative name of the function.
+ */
+ name: string;
+
+ /**
+ * List of parameter types.
+ */
+ parameters: Schema[];
+
+ /**
+ * Collection of separated parameters.
+ */
+ separated?: ILlmFunction.ISeparated;
+
+ /**
+ * Expected return type.
+ *
+ * If the function returns nothing (`void`), the `output` value would
+ * be `undefined`.
+ */
+ output?: Schema | undefined;
+
+ /**
+ * Description of the function.
+ *
+ * For reference, the `description` is very important property to teach
+ * the purpose of the function to the LLM (Language Large Model), and
+ * LLM actually determines which function to call by the description.
+ *
+ * Also, when the LLM conversates with the user, the `description` is
+ * used to explain the function to the user. Therefore, the `description`
+ * property has the highest priroity, and you have to consider it.
+ */
+ description?: string | undefined;
+}
+export namespace ILlmFunction {
+ /**
+ * Collection of separated parameters.
+ */
+ export interface ISeparated {
+ /**
+ * Parameters that would be composed by the LLM.
+ */
+ llm: ISeparatedParameter[];
+
+ /**
+ * Parameters that would be composed by the human.
+ */
+ human: ISeparatedParameter[];
+ }
+
+ /**
+ * Separated parameter.
+ */
+ export interface ISeparatedParameter {
+ /**
+ * Index of the parameter.
+ *
+ * @type uint
+ */
+ index: number;
+
+ /**
+ * Type schema info of the parameter.
+ */
+ schema: Schema;
+ }
+}
diff --git a/src/structures/ILlmSchema.ts b/src/structures/ILlmSchema.ts
new file mode 100644
index 0000000..ac388be
--- /dev/null
+++ b/src/structures/ILlmSchema.ts
@@ -0,0 +1,413 @@
+/**
+ * Type schema info of LLM function call.
+ *
+ * `ILlmSchema` is a type metadata of LLM (Large Language Model)
+ * function calling.
+ *
+ * `ILlmSchema` basically follows the JSON schema definition of OpenAPI
+ * v3.0 specification; {@link OpenApiV3.IJsonSchema}. However, `ILlmSchema`
+ * does not have the reference type; {@link OpenApiV3.IJsonSchema.IReference}.
+ * It's because the LLM cannot compose the reference typed arguments.
+ *
+ * For reference, the OpenAPI v3.0 based JSON schema definition can't
+ * express the tuple array type. It has been supported since OpenAPI v3.1;
+ * {@link OpenApi.IJsonSchema.ITuple}. Therefore, it would better to avoid
+ * using the tuple array type in the LLM function calling.
+ *
+ * @reference https://platform.openai.com/docs/guides/function-calling
+ * @author Jeongho Nam - https://github.com/samchon
+ */
+export type ILlmSchema =
+ | ILlmSchema.IBoolean
+ | ILlmSchema.IInteger
+ | ILlmSchema.INumber
+ | ILlmSchema.IString
+ | ILlmSchema.IArray
+ | ILlmSchema.IObject
+ | ILlmSchema.IUnknown
+ | ILlmSchema.INullOnly
+ | ILlmSchema.IOneOf;
+export namespace ILlmSchema {
+ /**
+ * Boolean type schema info.
+ */
+ export interface IBoolean extends __ISignificant<"boolean"> {
+ /**
+ * Default value.
+ */
+ default?: boolean | null;
+
+ /**
+ * Enumeration values.
+ */
+ enum?: Array;
+ }
+
+ /**
+ * Integer type schema info.
+ */
+ export interface IInteger extends __ISignificant<"integer"> {
+ /**
+ * Default value.
+ *
+ * @type int64
+ */
+ default?: number | null;
+
+ /**
+ * Enumeration values.
+ *
+ * @type int64
+ */
+ enum?: Array;
+
+ /**
+ * Minimum value restriction.
+ *
+ * @type int64
+ */
+ minimum?: number;
+
+ /**
+ * Maximum value restriction.
+ *
+ * @type int64
+ */
+ maximum?: number;
+
+ /**
+ * Exclusive minimum value restriction.
+ *
+ * For reference, even though your Swagger document has defined the
+ * `exclusiveMinimum` value as `number`, it has been forcibly converted
+ * to `boolean` type, and assigns the numeric value to the
+ * {@link minimum} property in the {@link OpenApi} conversion.
+ */
+ exclusiveMinimum?: boolean;
+
+ /**
+ * Exclusive maximum value restriction.
+ *
+ * For reference, even though your Swagger document has defined the
+ * `exclusiveMaximum` value as `number`, it has been forcibly converted
+ * to `boolean` type, and assigns the numeric value to the
+ * {@link maximum} property in the {@link OpenApi} conversion.
+ */
+ exclusiveMaximum?: boolean;
+
+ /**
+ * Multiple of value restriction.
+ *
+ * @type uint64
+ * @exclusiveMinimum 0
+ */
+ multipleOf?: number;
+ }
+
+ /**
+ * Number type schema info.
+ */
+ export interface INumber extends __ISignificant<"number"> {
+ /**
+ * Default value.
+ */
+ default?: number | null;
+
+ /**
+ * Enumeration values.
+ */
+ enum?: Array;
+
+ /**
+ * Minimum value restriction.
+ */
+ minimum?: number;
+
+ /**
+ * Maximum value restriction.
+ */
+ maximum?: number;
+
+ /**
+ * Exclusive minimum value restriction.
+ *
+ * For reference, even though your Swagger (or OpenAPI) document has
+ * defined the `exclusiveMinimum` value as `number`, {@link OpenAiComposer}
+ * forcibly converts it to `boolean` type, and assign the numeric value to
+ * the {@link minimum} property.
+ */
+ exclusiveMinimum?: boolean;
+
+ /**
+ * Exclusive maximum value restriction.
+ *
+ * For reference, even though your Swagger (or OpenAPI) document has
+ * defined the `exclusiveMaximum` value as `number`, {@link OpenAiComposer}
+ * forcibly converts it to `boolean` type, and assign the numeric value to
+ * the {@link maximum} property.
+ */
+ exclusiveMaximum?: boolean;
+
+ /**
+ * Multiple of value restriction.
+ *
+ * @exclusiveMinimum 0
+ */
+ multipleOf?: number;
+ }
+
+ /**
+ * String type schema info.
+ */
+ export interface IString extends __ISignificant<"string"> {
+ /**
+ * Default value.
+ */
+ default?: string | null;
+
+ /**
+ * Enumeration values.
+ */
+ enum?: Array;
+
+ /**
+ * Format restriction.
+ */
+ format?:
+ | "binary"
+ | "byte"
+ | "password"
+ | "regex"
+ | "uuid"
+ | "email"
+ | "hostname"
+ | "idn-email"
+ | "idn-hostname"
+ | "iri"
+ | "iri-reference"
+ | "ipv4"
+ | "ipv6"
+ | "uri"
+ | "uri-reference"
+ | "uri-template"
+ | "url"
+ | "date-time"
+ | "date"
+ | "time"
+ | "duration"
+ | "json-pointer"
+ | "relative-json-pointer"
+ | (string & {});
+
+ /**
+ * Pattern restriction.
+ */
+ pattern?: string;
+
+ /**
+ * Minimum length restriction.
+ *
+ * @type uint64
+ */
+ minLength?: number;
+
+ /**
+ * Maximum length restriction.
+ *
+ * @type uint64
+ */
+ maxLength?: number;
+
+ /**
+ * Content media type restriction.
+ */
+ contentMediaType?: string;
+ }
+
+ /**
+ * Array type schema info.
+ */
+ export interface IArray
+ extends __ISignificant<"array"> {
+ /**
+ * Items type schema info.
+ *
+ * The `items` means the type of the array elements. In other words, it is
+ * the type schema info of the `T` in the TypeScript array type `Array`.
+ */
+ items: Schema;
+
+ /**
+ * Unique items restriction.
+ *
+ * If this property value is `true`, target array must have unique items.
+ */
+ uniqueItems?: boolean;
+
+ /**
+ * Minimum items restriction.
+ *
+ * Restriction of minumum number of items in the array.
+ *
+ * @type uint64
+ */
+ minItems?: number;
+
+ /**
+ * Maximum items restriction.
+ *
+ * Restriction of maximum number of items in the array.
+ *
+ * @type uint64
+ */
+ maxItems?: number;
+ }
+
+ /**
+ * Object type schema info.
+ */
+ export interface IObject
+ extends __ISignificant<"object"> {
+ /**
+ * Properties of the object.
+ *
+ * The `properties` means a list of key-value pairs of the object's
+ * regular properties. The key is the name of the regular property,
+ * and the value is the type schema info.
+ *
+ * If you need additional properties that is represented by dynamic key,
+ * you can use the {@link additionalProperties} instead.
+ */
+ properties?: Record;
+
+ /**
+ * List of key values of the required properties.
+ *
+ * The `required` means a list of the key values of the required
+ * {@link properties}. If some property key is not listed in the `required`
+ * list, it means that property is optional. Otherwise some property key
+ * exists in the `required` list, it means that the property must be filled.
+ *
+ * Below is an example of the {@link properties} and `required`.
+ *
+ * ```typescript
+ * interface SomeObject {
+ * id: string;
+ * email: string;
+ * name?: string;
+ * }
+ * ```
+ *
+ * As you can see, `id` and `email` {@link properties} are {@link required},
+ * so that they are listed in the `required` list.
+ *
+ * ```json
+ * {
+ * "type": "object",
+ * "properties": {
+ * "id": { "type": "string" },
+ * "email": { "type": "string" },
+ * "name": { "type": "string" }
+ * },
+ * "required": ["id", "email"]
+ * }
+ * ```
+ */
+ required?: string[];
+
+ /**
+ * Additional properties' info.
+ *
+ * The `additionalProperties` means the type schema info of the additional
+ * properties that are not listed in the {@link properties}.
+ *
+ * If the value is `true`, it means that the additional properties are not
+ * restricted. They can be any type. Otherwise, if the value is
+ * {@link ILlmSchema} type, it means that the additional properties must
+ * follow the type schema info.
+ *
+ * - `true`: `Record`
+ * - `ILlmSchema`: `Record`
+ */
+ additionalProperties?: boolean | Schema;
+ }
+
+ /**
+ * Unknown type schema info.
+ *
+ * It means the type of the value is `any`.
+ */
+ export interface IUnknown extends __IAttribute {
+ /**
+ * Type is never be defined.
+ */
+ type?: undefined;
+ }
+
+ /**
+ * Null only type schema info.
+ */
+ export interface INullOnly extends __IAttribute {
+ /**
+ * Type is always `null`.
+ */
+ type: "null";
+
+ /**
+ * Default value.
+ */
+ default?: null;
+ }
+
+ /**
+ * One of type schema info.
+ *
+ * `IOneOf` represents an union type of the TypeScript (`A | B | C`).
+ *
+ * For reference, even though your Swagger (or OpenAPI) document has
+ * defined `anyOf` instead of the `oneOf`, it has been forcibly converted
+ * to `oneOf` type by {@link OpenApi.convert OpenAPI conversion}.
+ */
+ export interface IOneOf
+ extends __IAttribute {
+ /**
+ * List of the union types.
+ */
+ oneOf: Exclude>[];
+ }
+
+ /**
+ * Significant attributes that can be applied to the most types.
+ */
+ export interface __ISignificant extends __IAttribute {
+ /**
+ * Discriminator value of the type.
+ */
+ type: Type;
+
+ /**
+ * Whether to allow `null` value or not.
+ */
+ nullable?: boolean;
+ }
+
+ /**
+ * Common attributes that can be applied to all types.
+ */
+ export interface __IAttribute {
+ /**
+ * Title of the schema.
+ */
+ title?: string;
+
+ /**
+ * Detailed description of the schema.
+ */
+ description?: string;
+
+ /**
+ * Whether the type is deprecated or not.
+ */
+ deprecated?: boolean;
+ }
+}
diff --git a/src/utils/LlmDataMerger.ts b/src/utils/LlmDataMerger.ts
new file mode 100644
index 0000000..00729a8
--- /dev/null
+++ b/src/utils/LlmDataMerger.ts
@@ -0,0 +1,90 @@
+import { IHttpLlmFunction } from "../structures/IHttpLlmFunction";
+import { ILlmFunction } from "../structures/ILlmFunction";
+
+/**
+ * Data combiner for LLM function call.
+ *
+ * @author Samchon
+ */
+export namespace LlmDataMerger {
+ /**
+ * Properties of {@link parameters} function.
+ */
+ export interface IProps {
+ /**
+ * Target function to call.
+ */
+ function: ILlmFunction;
+
+ /**
+ * Arguments composed by LLM (Large Language Model).
+ */
+ llm: any[];
+
+ /**
+ * Arguments composed by human.
+ */
+ human: any[];
+ }
+
+ /**
+ * Combine LLM and human arguments into one.
+ *
+ * When you composes {@link IOpenAiDocument} with
+ * {@link IOpenAiDocument.IOptions.separate} option, then the arguments of the
+ * target function would be separated into two parts; LLM (Large Language Model)
+ * and human.
+ *
+ * In that case, you can combine both LLM and human composed arguments into one
+ * by utilizing this {@link LlmDataMerger.parameters} function, referencing
+ * the target function metadata {@link IOpenAiFunction.separated}.
+ *
+ * @param props Properties to combine LLM and human arguments with metadata.
+ * @returns Combined arguments
+ */
+ export const parameters = (props: IProps): unknown[] => {
+ const separated: IHttpLlmFunction.ISeparated | undefined =
+ props.function.separated;
+ if (separated === undefined)
+ throw new Error(
+ "Error on OpenAiDataComposer.parameters(): the function parameters are not separated.",
+ );
+ return new Array(props.function.parameters.length).fill(0).map((_, i) => {
+ const llm: number = separated.llm.findIndex((p) => p.index === i);
+ const human: number = separated.human.findIndex((p) => p.index === i);
+ if (llm === -1 && human === -1)
+ throw new Error(
+ "Error on OpenAiDataComposer.parameters(): failed to gather separated arguments, because both LLM and human sides are all empty.",
+ );
+ return value(props.llm[llm], props.human[human]);
+ });
+ };
+
+ /**
+ * Combine two values into one.
+ *
+ * If both values are objects, then combines them in the properties level.
+ *
+ * Otherwise, returns the latter value if it's not null, otherwise the former value
+ *
+ * - `return (y ?? x)`
+ *
+ * @param x Value X
+ * @param y Value Y
+ * @returns Combined value
+ */
+ export const value = (x: unknown, y: unknown): unknown =>
+ typeof x === "object" && typeof y === "object" && x !== null && y !== null
+ ? combineObject(x, y)
+ : Array.isArray(x) && Array.isArray(y)
+ ? new Array(Math.max(x.length, y.length))
+ .fill(0)
+ .map((_, i) => value(x[i], y[i]))
+ : y ?? x;
+
+ const combineObject = (x: any, y: any): any => {
+ const output: any = { ...x };
+ for (const [k, v] of Object.entries(y)) output[k] = value(x[k], v);
+ return output;
+ };
+}
diff --git a/src/utils/LlmSchemaSeparator.ts b/src/utils/LlmSchemaSeparator.ts
new file mode 100644
index 0000000..66bf824
--- /dev/null
+++ b/src/utils/LlmSchemaSeparator.ts
@@ -0,0 +1,92 @@
+import { IHttpLlmFunction } from "../structures/IHttpLlmFunction";
+import { ILlmSchema } from "../structures/ILlmSchema";
+import { LlmTypeChecker } from "./LlmTypeChecker";
+
+export namespace LlmSchemaSeparator {
+ export interface IProps {
+ parameters: ILlmSchema[];
+ predicator: (schema: ILlmSchema) => boolean;
+ }
+ export const parameters = (props: IProps): IHttpLlmFunction.ISeparated => {
+ const indexes: Array<[ILlmSchema | null, ILlmSchema | null]> =
+ props.parameters.map(schema(props.predicator));
+ return {
+ llm: indexes
+ .map(([llm], index) => ({
+ index,
+ schema: llm!,
+ }))
+ .filter(({ schema }) => schema !== null),
+ human: indexes
+ .map(([, human], index) => ({
+ index,
+ schema: human!,
+ }))
+ .filter(({ schema }) => schema !== null),
+ };
+ };
+
+ export const schema =
+ (predicator: (schema: ILlmSchema) => boolean) =>
+ (input: ILlmSchema): [ILlmSchema | null, ILlmSchema | null] => {
+ if (predicator(input) === true) return [null, input];
+ else if (LlmTypeChecker.isUnknown(input) || LlmTypeChecker.isOneOf(input))
+ return [input, null];
+ else if (LlmTypeChecker.isObject(input))
+ return separateObject(predicator)(input);
+ else if (LlmTypeChecker.isArray(input))
+ return separateArray(predicator)(input);
+ return [input, null];
+ };
+
+ const separateArray =
+ (predicator: (schema: ILlmSchema) => boolean) =>
+ (
+ input: ILlmSchema.IArray,
+ ): [ILlmSchema.IArray | null, ILlmSchema.IArray | null] => {
+ const [x, y] = schema(predicator)(input.items);
+ return [
+ x !== null ? { ...input, items: x } : null,
+ y !== null ? { ...input, items: y } : null,
+ ];
+ };
+
+ const separateObject =
+ (predicator: (schema: ILlmSchema) => boolean) =>
+ (
+ input: ILlmSchema.IObject,
+ ): [ILlmSchema.IObject | null, ILlmSchema.IObject | null] => {
+ if (
+ !!input.additionalProperties ||
+ Object.keys(input.properties ?? {}).length === 0
+ )
+ return [input, null];
+ const llm = {
+ ...input,
+ properties: {} as Record,
+ } satisfies ILlmSchema.IObject;
+ const human = {
+ ...input,
+ properties: {} as Record,
+ } satisfies ILlmSchema.IObject;
+ for (const [key, value] of Object.entries(input.properties ?? {})) {
+ const [x, y] = schema(predicator)(value);
+ if (x !== null) llm.properties[key] = x;
+ if (y !== null) human.properties[key] = y;
+ }
+ return [
+ Object.keys(llm.properties).length === 0 ? null : shrinkRequired(llm),
+ Object.keys(human.properties).length === 0
+ ? null
+ : shrinkRequired(human),
+ ];
+ };
+
+ const shrinkRequired = (input: ILlmSchema.IObject): ILlmSchema.IObject => {
+ if (input.required !== undefined)
+ input.required = input.required.filter(
+ (key) => input.properties?.[key] !== undefined,
+ );
+ return input;
+ };
+}
diff --git a/src/utils/LlmTypeChecker.ts b/src/utils/LlmTypeChecker.ts
new file mode 100644
index 0000000..633403e
--- /dev/null
+++ b/src/utils/LlmTypeChecker.ts
@@ -0,0 +1,132 @@
+import { ILlmSchema } from "../structures/ILlmSchema";
+
+/**
+ * Type checker for LLM type schema.
+ *
+ * `LlmSchemaTypeChecker` is a type checker of {@link ILlmSchema}.
+ *
+ * @author Samchon
+ */
+export namespace LlmTypeChecker {
+ /**
+ * Visit every nested schemas.
+ *
+ * Visit every nested schemas of the target, and apply the callback function
+ * to them.
+ *
+ * If the visitor meets an union type, it will visit every individual schemas
+ * in the union type. Otherwise meets an object type, it will visit every
+ * properties and additional properties. If the visitor meets an array type,
+ * it will visit the item type.
+ *
+ * @param schema Target schema to visit
+ * @param callback Callback function to apply
+ */
+ export const visit = (
+ schema: ILlmSchema,
+ callback: (schema: ILlmSchema) => void,
+ ): void => {
+ callback(schema);
+ if (isOneOf(schema)) schema.oneOf.forEach((s) => visit(s, callback));
+ else if (isObject(schema)) {
+ for (const [_, s] of Object.entries(schema.properties ?? {}))
+ visit(s, callback);
+ if (
+ typeof schema.additionalProperties === "object" &&
+ schema.additionalProperties !== null
+ )
+ visit(schema.additionalProperties, callback);
+ } else if (isArray(schema)) visit(schema.items, callback);
+ };
+
+ /**
+ * Test whether the schema is an union type.
+ *
+ * @param schema Target schema
+ * @returns Whether union type or not
+ */
+ export const isOneOf = (schema: ILlmSchema): schema is ILlmSchema.IOneOf =>
+ (schema as ILlmSchema.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: ILlmSchema): schema is ILlmSchema.IObject =>
+ (schema as ILlmSchema.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: ILlmSchema): schema is ILlmSchema.IArray =>
+ (schema as ILlmSchema.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: ILlmSchema,
+ ): schema is ILlmSchema.IBoolean =>
+ (schema as ILlmSchema.IBoolean).type === "boolean";
+
+ /**
+ * Test whether the schema is a number type.
+ *
+ * @param schema Target schema
+ * @returns Whether number type or not
+ */
+ export const isNumber = (schema: ILlmSchema): schema is ILlmSchema.INumber =>
+ (schema as ILlmSchema.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: ILlmSchema): schema is ILlmSchema.IString =>
+ (schema as ILlmSchema.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: ILlmSchema,
+ ): schema is ILlmSchema.INullOnly =>
+ (schema as ILlmSchema.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: ILlmSchema): 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: ILlmSchema,
+ ): schema is ILlmSchema.IUnknown =>
+ !isOneOf(schema) && (schema as ILlmSchema.IUnknown).type === undefined;
+}
diff --git a/src/OpenApiTypeChecker.ts b/src/utils/OpenApiTypeChecker.ts
similarity index 99%
rename from src/OpenApiTypeChecker.ts
rename to src/utils/OpenApiTypeChecker.ts
index 1d1d4ff..79e79af 100644
--- a/src/OpenApiTypeChecker.ts
+++ b/src/utils/OpenApiTypeChecker.ts
@@ -1,5 +1,5 @@
-import { OpenApi } from "./OpenApi";
-import { MapUtil } from "./utils/MapUtil";
+import { OpenApi } from "../OpenApi";
+import { MapUtil } from "./MapUtil";
export namespace OpenApiTypeChecker {
export const visit =
diff --git a/test/controllers/AppController.ts b/test/controllers/AppController.ts
new file mode 100644
index 0000000..5a8b718
--- /dev/null
+++ b/test/controllers/AppController.ts
@@ -0,0 +1,102 @@
+import {
+ TypedBody,
+ TypedFormData,
+ TypedParam,
+ TypedQuery,
+ TypedRoute,
+} from "@nestia/core";
+import { Controller, Query } from "@nestjs/common";
+import { tags } from "typia";
+
+@Controller()
+export class AppController {
+ @TypedRoute.Get(":index/:level/:optimal/parameters")
+ public parameters(
+ @TypedParam("index")
+ index: string & tags.Format<"uri"> & tags.ContentMediaType<"text/html">,
+ @TypedParam("level") level: number,
+ @TypedParam("optimal") optimal: boolean,
+ ) {
+ return { index, level, optimal };
+ }
+
+ @TypedRoute.Get(":index/:level/:optimal/query")
+ public query(
+ @TypedParam("index")
+ index: string & tags.Format<"uri"> & tags.ContentMediaType<"text/html">,
+ @TypedParam("level") level: number,
+ @TypedParam("optimal") optimal: boolean,
+ @TypedQuery() query: IQuery,
+ ) {
+ return { index, level, optimal, query };
+ }
+
+ @TypedRoute.Post(":index/:level/:optimal/body")
+ public body(
+ @TypedParam("index")
+ index: string & tags.Format<"uri"> & tags.ContentMediaType<"text/html">,
+ @TypedParam("level") level: number,
+ @TypedParam("optimal") optimal: boolean,
+ @TypedBody() body: IBody,
+ ) {
+ return { index, level, optimal, body };
+ }
+
+ @TypedRoute.Post(":index/:level/:optimal/query/body")
+ public query_body(
+ @TypedParam("index")
+ index: string & tags.Format<"uri"> & tags.ContentMediaType<"text/html">,
+ @TypedParam("level") level: number,
+ @TypedParam("optimal") optimal: boolean,
+ @Query("thumbnail")
+ thumbnail: string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">,
+ @TypedQuery() query: { summary: string },
+ @TypedBody() body: IBody,
+ ) {
+ return {
+ index,
+ level,
+ optimal,
+ query: {
+ ...query,
+ thumbnail,
+ },
+ body,
+ };
+ }
+
+ @TypedRoute.Post(":index/:level/:optimal/multipart")
+ public query_multipart(
+ @TypedParam("index")
+ index: string & tags.Format<"uri"> & tags.ContentMediaType<"text/html">,
+ @TypedParam("level") level: number,
+ @TypedParam("optimal") optimal: boolean,
+ @TypedQuery() query: IQuery,
+ @TypedFormData.Body()
+ body: IMultipart,
+ ) {
+ return {
+ index,
+ level,
+ optimal,
+ query,
+ body: {
+ ...body,
+ file: `http://localhost:3000/files/${Date.now()}.raw`,
+ },
+ };
+ }
+}
+
+interface IQuery {
+ summary: string;
+ thumbnail: string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">;
+}
+interface IBody {
+ title: string;
+ body: string;
+ draft: boolean;
+}
+interface IMultipart extends IBody {
+ file: File;
+}
diff --git a/test/controllers/AppFilter.ts b/test/controllers/AppFilter.ts
new file mode 100644
index 0000000..f4d7b93
--- /dev/null
+++ b/test/controllers/AppFilter.ts
@@ -0,0 +1,12 @@
+import { ArgumentsHost, Catch, HttpException } from "@nestjs/common";
+import { BaseExceptionFilter } from "@nestjs/core";
+
+@Catch()
+export class AppFilter extends BaseExceptionFilter {
+ public async catch(exception: HttpException | Error, host: ArgumentsHost) {
+ const status: number =
+ exception instanceof HttpException ? exception.getStatus() : 500;
+ if (status === 500) console.info(exception);
+ return super.catch(exception, host);
+ }
+}
diff --git a/test/controllers/AppModule.ts b/test/controllers/AppModule.ts
new file mode 100644
index 0000000..322f15a
--- /dev/null
+++ b/test/controllers/AppModule.ts
@@ -0,0 +1,8 @@
+import { Module } from "@nestjs/common";
+
+import { AppController } from "./AppController";
+
+@Module({
+ controllers: [AppController],
+})
+export class AppModule {}
diff --git a/test/examples/execute.ts b/test/examples/execute.ts
new file mode 100644
index 0000000..1c47e4f
--- /dev/null
+++ b/test/examples/execute.ts
@@ -0,0 +1,51 @@
+import {
+ HttpLlm,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ OpenApi,
+ OpenApiV3,
+ OpenApiV3_1,
+ SwaggerV2,
+} from "@samchon/openapi";
+import fs from "fs";
+import typia from "typia";
+
+const main = async (): Promise => {
+ // read swagger document and validate it
+ const swagger:
+ | SwaggerV2.IDocument
+ | OpenApiV3.IDocument
+ | OpenApiV3_1.IDocument = JSON.parse(
+ await fs.promises.readFile("swagger.json", "utf8"),
+ );
+ typia.assert(swagger);
+
+ // convert to emended OpenAPI document,
+ // and compose LLM function calling application
+ const document: OpenApi.IDocument = OpenApi.convert(swagger);
+ const application: IHttpLlmApplication = HttpLlm.application(document);
+
+ // Let's imagine that LLM has selected a function to call
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) => f.path === "/bbs/articles" && f.method === "post",
+ );
+ typia.assertGuard(func);
+
+ // actual execution is by yourself
+ const article = await HttpLlm.execute({
+ connection: {
+ host: "http://localhost:3000",
+ },
+ application,
+ function: func,
+ arguments: [
+ "general",
+ {
+ title: "Hello, world!",
+ body: "Let's imagine that this argument is composed by LLM.",
+ },
+ ],
+ });
+ console.log("article", article);
+};
+main().catch(console.error);
diff --git a/test/examples/keyword.ts b/test/examples/keyword.ts
new file mode 100644
index 0000000..e875f71
--- /dev/null
+++ b/test/examples/keyword.ts
@@ -0,0 +1,63 @@
+import {
+ HttpLlm,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ OpenApi,
+ OpenApiV3,
+ OpenApiV3_1,
+ SwaggerV2,
+} from "@samchon/openapi";
+import fs from "fs";
+import typia from "typia";
+import { v4 } from "uuid";
+
+const main = async (): Promise => {
+ // read swagger document and validate it
+ const swagger:
+ | SwaggerV2.IDocument
+ | OpenApiV3.IDocument
+ | OpenApiV3_1.IDocument = JSON.parse(
+ await fs.promises.readFile("swagger.json", "utf8"),
+ );
+ typia.assert(swagger); // recommended
+
+ // convert to emended OpenAPI document,
+ // and compose LLM function calling application
+ const document: OpenApi.IDocument = OpenApi.convert(swagger);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ });
+
+ // Let's imagine that LLM has selected a function to call
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ // (f) => f.name === "llm_selected_fuction_name"
+ (f) => f.path === "/bbs/articles/{id}" && f.method === "put",
+ );
+ if (func === undefined) throw new Error("No matched function exists.");
+
+ // actual execution is by yourself
+ const article = await HttpLlm.execute({
+ connection: {
+ host: "http://localhost:3000",
+ },
+ application,
+ function: func,
+ arguments: [
+ {
+ section: "general",
+ id: v4(),
+ query: {
+ language: "en-US",
+ format: "markdown",
+ },
+ body: {
+ title: "Hello, world!",
+ body: "Let's imagine that this argument is composed by LLM.",
+ thumbnail: null,
+ },
+ },
+ ],
+ });
+ console.log("article", article);
+};
+main().catch(console.error);
diff --git a/test/examples/separate.ts b/test/examples/separate.ts
new file mode 100644
index 0000000..feb6e3d
--- /dev/null
+++ b/test/examples/separate.ts
@@ -0,0 +1,71 @@
+import {
+ HttpLlm,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ LlmTypeChecker,
+ OpenApi,
+ OpenApiV3,
+ OpenApiV3_1,
+ SwaggerV2,
+} from "@samchon/openapi";
+import fs from "fs";
+import typia from "typia";
+import { v4 } from "uuid";
+
+const main = async (): Promise => {
+ // read swagger document and validate it
+ const swagger:
+ | SwaggerV2.IDocument
+ | OpenApiV3.IDocument
+ | OpenApiV3_1.IDocument = JSON.parse(
+ await fs.promises.readFile("swagger.json", "utf8"),
+ );
+ typia.assert(swagger); // recommended
+
+ // convert to emended OpenAPI document,
+ // and compose LLM function calling application
+ const document: OpenApi.IDocument = OpenApi.convert(swagger);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && schema.contentMediaType !== undefined,
+ });
+
+ // Let's imagine that LLM has selected a function to call
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ // (f) => f.name === "llm_selected_fuction_name"
+ (f) => f.path === "/bbs/articles/{id}" && f.method === "put",
+ );
+ if (func === undefined) throw new Error("No matched function exists.");
+
+ // actual execution is by yourself
+ const article = await HttpLlm.execute({
+ connection: {
+ host: "http://localhost:3000",
+ },
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ // LLM composed parameter values
+ "general",
+ v4(),
+ {
+ language: "en-US",
+ format: "markdown",
+ },
+ {
+ title: "Hello, world!",
+ content: "Let's imagine that this argument is composed by LLM.",
+ },
+ ],
+ human: [
+ // Human composed parameter values
+ { thumbnail: "https://example.com/thumbnail.jpg" },
+ ],
+ }),
+ });
+ console.log("article", article);
+};
+main().catch(console.error);
diff --git a/test/features/llm/test_http_llm_application_keyword.ts b/test/features/llm/test_http_llm_application_keyword.ts
new file mode 100644
index 0000000..9127744
--- /dev/null
+++ b/test/features/llm/test_http_llm_application_keyword.ts
@@ -0,0 +1,33 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpLlmApplication,
+ IHttpMigrateRoute,
+ ILlmSchema,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_application_keyword = (): void => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ });
+ for (const func of application.functions) {
+ const route: IHttpMigrateRoute = func.route();
+ TestValidator.equals("length")(1)(func.parameters.length);
+ TestValidator.equals("properties")([
+ ...route.parameters.map((p) => p.key),
+ ...(route.query ? ["query"] : []),
+ ...(route.body ? ["body"] : []),
+ ])(
+ (() => {
+ const schema: ILlmSchema = func.parameters[0];
+ if (!LlmTypeChecker.isObject(schema)) return [];
+ return Object.keys(schema.properties ?? {});
+ })(),
+ );
+ }
+};
diff --git a/test/features/llm/test_http_llm_application_positional.ts b/test/features/llm/test_http_llm_application_positional.ts
new file mode 100644
index 0000000..074ef13
--- /dev/null
+++ b/test/features/llm/test_http_llm_application_positional.ts
@@ -0,0 +1,22 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpLlmApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_application_positional = (): void => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ });
+ for (const func of application.functions) {
+ const route: IHttpMigrateRoute = func.route();
+ TestValidator.equals("length")(func.parameters.length)(
+ route.parameters.length + (route.query ? 1 : 0) + (route.body ? 1 : 0),
+ );
+ }
+};
diff --git a/test/features/llm/test_http_llm_fetcher_keyword_body.ts b/test/features/llm/test_http_llm_fetcher_keyword_body.ts
new file mode 100644
index 0000000..a6c03bd
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_keyword_body.ts
@@ -0,0 +1,53 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) => f.path === "/{index}/{level}/{optimal}/body" && f.method === "post",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ {
+ level: 123,
+ optimal: true,
+ body: {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ },
+ ],
+ human: [
+ {
+ index: "https://some.url/index.html",
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(201);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_keyword_parameters.ts b/test/features/llm/test_http_llm_fetcher_keyword_parameters.ts
new file mode 100644
index 0000000..50d1a26
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_keyword_parameters.ts
@@ -0,0 +1,49 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_parameters = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) =>
+ f.path === "/{index}/{level}/{optimal}/parameters" && f.method === "get",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ {
+ level: 123,
+ optimal: true,
+ },
+ ],
+ human: [
+ {
+ index: "https://some.url/index.html",
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(200);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_keyword_query.ts b/test/features/llm/test_http_llm_fetcher_keyword_query.ts
new file mode 100644
index 0000000..cd81620
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_keyword_query.ts
@@ -0,0 +1,54 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_query = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) => f.path === "/{index}/{level}/{optimal}/query" && f.method === "get",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ {
+ level: 123,
+ optimal: true,
+ query: {
+ summary: "some summary",
+ },
+ },
+ ],
+ human: [
+ {
+ index: "https://some.url/index.html",
+ query: {
+ thumbnail: "https://some.url/file.jpg",
+ },
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(200);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_keyword_query_and_body.ts b/test/features/llm/test_http_llm_fetcher_keyword_query_and_body.ts
new file mode 100644
index 0000000..2dcf74e
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_keyword_query_and_body.ts
@@ -0,0 +1,60 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_query_and_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: true,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) =>
+ f.path === "/{index}/{level}/{optimal}/query/body" && f.method === "post",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ {
+ level: 123,
+ optimal: true,
+ query: {
+ summary: "some summary",
+ },
+ body: {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ },
+ ],
+ human: [
+ {
+ index: "https://some.url/index.html",
+ query: {
+ thumbnail: "https://some.url/file.jpg",
+ },
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(201);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_positional_body.ts b/test/features/llm/test_http_llm_fetcher_positional_body.ts
new file mode 100644
index 0000000..7932fac
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_positional_body.ts
@@ -0,0 +1,47 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_positional_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) => f.path === "/{index}/{level}/{optimal}/body" && f.method === "post",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ 123,
+ true,
+ {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ ],
+ human: ["https://some.url/index.html"],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(201);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_positional_parameters.ts b/test/features/llm/test_http_llm_fetcher_positional_parameters.ts
new file mode 100644
index 0000000..03d19b6
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_positional_parameters.ts
@@ -0,0 +1,40 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_parameters = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) =>
+ f.path === "/{index}/{level}/{optimal}/parameters" && f.method === "get",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [123, true],
+ human: ["https://some.url/index.html"],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(200);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_positional_query.ts b/test/features/llm/test_http_llm_fetcher_positional_query.ts
new file mode 100644
index 0000000..2bbfb23
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_positional_query.ts
@@ -0,0 +1,50 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_query = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) => f.path === "/{index}/{level}/{optimal}/query" && f.method === "get",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ 123,
+ true,
+ {
+ summary: "some summary",
+ },
+ ],
+ human: [
+ "https://some.url/index.html",
+ {
+ thumbnail: "https://some.url/file.jpg",
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(200);
+};
diff --git a/test/features/llm/test_http_llm_fetcher_positional_query_and_body.ts b/test/features/llm/test_http_llm_fetcher_positional_query_and_body.ts
new file mode 100644
index 0000000..147d5ba
--- /dev/null
+++ b/test/features/llm/test_http_llm_fetcher_positional_query_and_body.ts
@@ -0,0 +1,56 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpLlm,
+ IHttpConnection,
+ IHttpLlmApplication,
+ IHttpLlmFunction,
+ IHttpResponse,
+ LlmTypeChecker,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_llm_fetcher_keyword_query_and_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const application: IHttpLlmApplication = HttpLlm.application(document, {
+ keyword: false,
+ separate: (schema) =>
+ LlmTypeChecker.isString(schema) && !!schema.contentMediaType,
+ });
+ const func: IHttpLlmFunction | undefined = application.functions.find(
+ (f) =>
+ f.path === "/{index}/{level}/{optimal}/query/body" && f.method === "post",
+ );
+ if (func === undefined) throw new Error("Function not found");
+
+ const response: IHttpResponse = await HttpLlm.propagate({
+ connection,
+ application,
+ function: func,
+ arguments: HttpLlm.mergeParameters({
+ function: func,
+ llm: [
+ 123,
+ true,
+ {
+ summary: "some summary",
+ },
+ {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ ],
+ human: [
+ "https://some.url/index.html",
+ {
+ thumbnail: "https://some.url/file.jpg",
+ },
+ ],
+ }),
+ });
+ TestValidator.equals("response.status")(response.status)(201);
+};
diff --git a/test/features/llm/test_llm_merge_parameters.ts b/test/features/llm/test_llm_merge_parameters.ts
new file mode 100644
index 0000000..759a6a6
--- /dev/null
+++ b/test/features/llm/test_llm_merge_parameters.ts
@@ -0,0 +1,42 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm } from "@samchon/openapi";
+
+export const test_llm_merge_parameters = (): void => {
+ TestValidator.equals("atomics")(
+ HttpLlm.mergeParameters({
+ function: {
+ name: "test",
+ parameters: [
+ { type: "boolean" },
+ { type: "number" },
+ { type: "string" },
+ { type: "string" },
+ ],
+ separated: {
+ human: [
+ {
+ schema: { type: "boolean" },
+ index: 0,
+ },
+ {
+ schema: { type: "number" },
+ index: 1,
+ },
+ ],
+ llm: [
+ {
+ schema: { type: "string" },
+ index: 2,
+ },
+ {
+ schema: { type: "string" },
+ index: 3,
+ },
+ ],
+ },
+ },
+ human: [false, 1],
+ llm: ["two", "three"],
+ }),
+ )([false, 1, "two", "three"]);
+};
diff --git a/test/features/llm/test_llm_merge_value.ts b/test/features/llm/test_llm_merge_value.ts
new file mode 100644
index 0000000..e7239ea
--- /dev/null
+++ b/test/features/llm/test_llm_merge_value.ts
@@ -0,0 +1,62 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm } from "@samchon/openapi";
+
+export const test_llm_merge_parameters = (): void => {
+ TestValidator.equals("number")(HttpLlm.mergeValue(1, 2))(2);
+ TestValidator.equals("nullable")(HttpLlm.mergeValue(0, null))(0);
+ TestValidator.equals("optional")(HttpLlm.mergeValue(0, undefined))(0);
+ TestValidator.equals("object")(
+ HttpLlm.mergeValue(
+ {
+ a: "A",
+ array: [1, 2, 3],
+ nestedArray: [{ alpha: "alpha" }, { alpha: "alpha" }],
+ object: { x: "X" },
+ },
+ {
+ b: "B",
+ array: [3, 4, 5],
+ nestedArray: [{ beta: "beta" }, { beta: "beta" }],
+ object: { y: "Y" },
+ },
+ ),
+ )({
+ a: "A",
+ b: "B",
+ array: [3, 4, 5],
+ nestedArray: [
+ {
+ alpha: "alpha",
+ beta: "beta",
+ },
+ {
+ alpha: "alpha",
+ beta: "beta",
+ },
+ ],
+ object: { x: "X", y: "Y" },
+ });
+ TestValidator.equals("membership")(
+ HttpLlm.mergeValue(
+ {
+ name: "Samchon",
+ email: "samchon.github@gmail.com",
+ password: "1234",
+ age: 30,
+ gender: 1,
+ },
+ {
+ homepage: "https://github.com/samchon",
+ secret: "something",
+ },
+ ),
+ )({
+ name: "Samchon",
+ email: "samchon.github@gmail.com",
+ password: "1234",
+ age: 30,
+ gender: 1,
+ homepage: "https://github.com/samchon",
+ secret: "something",
+ });
+};
diff --git a/test/features/llm/test_llm_schema_object.ts b/test/features/llm/test_llm_schema_object.ts
new file mode 100644
index 0000000..0847f2e
--- /dev/null
+++ b/test/features/llm/test_llm_schema_object.ts
@@ -0,0 +1,55 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm, ILlmSchema } from "@samchon/openapi";
+import typia, { IJsonApplication, tags } from "typia";
+
+export const test_llm_schema_object = (): void => {
+ const app: IJsonApplication = typia.json.application<[First]>();
+ const schema: ILlmSchema | null = HttpLlm.schema({
+ components: app.components,
+ schema: app.schemas[0],
+ });
+ TestValidator.equals("schema")(schema)({
+ type: "object",
+ required: ["second"],
+ properties: {
+ second: {
+ type: "object",
+ required: ["third"],
+ description: "The second property",
+ properties: {
+ third: {
+ type: "object",
+ required: ["id"],
+ description: "The third property",
+ properties: {
+ id: {
+ type: "string",
+ format: "uuid",
+ description: "Hello word",
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+};
+
+interface First {
+ /**
+ * The second property
+ */
+ second: Second;
+}
+interface Second {
+ /**
+ * The third property
+ */
+ third: Third;
+}
+interface Third {
+ /**
+ * Hello word
+ */
+ id: string & tags.Format<"uuid">;
+}
diff --git a/test/features/llm/test_llm_schema_oneof.ts b/test/features/llm/test_llm_schema_oneof.ts
new file mode 100644
index 0000000..d03153c
--- /dev/null
+++ b/test/features/llm/test_llm_schema_oneof.ts
@@ -0,0 +1,77 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm, ILlmSchema } from "@samchon/openapi";
+import typia, { IJsonApplication } from "typia";
+
+export const test_llm_schema_oneof = (): void => {
+ const app: IJsonApplication =
+ typia.json.application<[Circle | Triangle | Rectangle]>();
+ const casted: ILlmSchema | null = HttpLlm.schema({
+ components: app.components,
+ schema: app.schemas[0],
+ });
+ TestValidator.equals("oneOf")(casted)({
+ oneOf: [
+ {
+ type: "object",
+ properties: {
+ type: {
+ type: "string",
+ enum: ["circle"],
+ },
+ radius: {
+ type: "number",
+ },
+ },
+ required: ["type", "radius"],
+ },
+ {
+ type: "object",
+ properties: {
+ type: {
+ type: "string",
+ enum: ["triangle"],
+ },
+ base: {
+ type: "number",
+ },
+ height: {
+ type: "number",
+ },
+ },
+ required: ["type", "base", "height"],
+ },
+ {
+ type: "object",
+ properties: {
+ type: {
+ type: "string",
+ enum: ["square"],
+ },
+ width: {
+ type: "number",
+ },
+ height: {
+ type: "number",
+ },
+ },
+ required: ["type", "width", "height"],
+ },
+ ],
+ ...{ discriminator: undefined },
+ });
+};
+
+interface Circle {
+ type: "circle";
+ radius: number;
+}
+interface Triangle {
+ type: "triangle";
+ base: number;
+ height: number;
+}
+interface Rectangle {
+ type: "square";
+ width: number;
+ height: number;
+}
diff --git a/test/features/llm/test_llm_schema_separate_array.ts b/test/features/llm/test_llm_schema_separate_array.ts
new file mode 100644
index 0000000..eb7fc1f
--- /dev/null
+++ b/test/features/llm/test_llm_schema_separate_array.ts
@@ -0,0 +1,38 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm, ILlmSchema, LlmTypeChecker, OpenApi } from "@samchon/openapi";
+import { LlmSchemaSeparator } from "@samchon/openapi/lib/utils/LlmSchemaSeparator";
+import typia, { tags } from "typia";
+
+export const test_llm_schema_separate_array = (): void => {
+ const separator = LlmSchemaSeparator.schema(
+ (s) => LlmTypeChecker.isString(s) && s.contentMediaType !== undefined,
+ );
+ const member: ILlmSchema = schema(typia.json.application<[IMember[]]>());
+ const upload: ILlmSchema = schema(typia.json.application<[IFileUpload[]]>());
+ const combined: ILlmSchema = schema(typia.json.application<[ICombined[]]>());
+
+ TestValidator.equals("member")(separator(member))([member, null]);
+ TestValidator.equals("upload")(separator(upload))([null, upload]);
+ TestValidator.equals("combined")(separator(combined))([member, upload]);
+};
+
+interface IMember {
+ id: number;
+ name: string;
+}
+interface IFileUpload {
+ file: string & tags.ContentMediaType<"image/png">;
+}
+interface ICombined extends IMember, IFileUpload {}
+
+const schema = (props: {
+ components: OpenApi.IComponents;
+ schemas: OpenApi.IJsonSchema[];
+}): ILlmSchema => {
+ const schema: ILlmSchema | null = HttpLlm.schema({
+ components: props.components,
+ schema: props.schemas[0],
+ });
+ if (schema === null) throw new Error("Invalid schema");
+ return schema;
+};
diff --git a/test/features/llm/test_llm_schema_separate_nested.ts b/test/features/llm/test_llm_schema_separate_nested.ts
new file mode 100644
index 0000000..816d8a2
--- /dev/null
+++ b/test/features/llm/test_llm_schema_separate_nested.ts
@@ -0,0 +1,54 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm, ILlmSchema, LlmTypeChecker, OpenApi } from "@samchon/openapi";
+import { LlmSchemaSeparator } from "@samchon/openapi/lib/utils/LlmSchemaSeparator";
+import typia, { tags } from "typia";
+
+export const test_llm_schema_separate_nested = (): void => {
+ const separator = LlmSchemaSeparator.schema(
+ (s) => LlmTypeChecker.isString(s) && s.contentMediaType !== undefined,
+ );
+ const member: ILlmSchema = schema(
+ typia.json.application<[INested]>(),
+ );
+ const upload: ILlmSchema = schema(
+ typia.json.application<[INested]>(),
+ );
+ const combined: ILlmSchema = schema(
+ typia.json.application<[INested]>(),
+ );
+
+ TestValidator.equals("member")(separator(member))([member, null]);
+ TestValidator.equals("upload")(separator(upload))([null, upload]);
+ TestValidator.equals("combined")(separator(combined))([member, upload]);
+};
+
+interface INested {
+ first: {
+ second: {
+ third: {
+ fourth: T;
+ };
+ array: T[];
+ };
+ };
+}
+interface IMember {
+ id: number;
+ name: string;
+}
+interface IFileUpload {
+ file: string & tags.Format<"uri"> & tags.ContentMediaType<"image/png">;
+}
+interface ICombined extends IMember, IFileUpload {}
+
+const schema = (props: {
+ components: OpenApi.IComponents;
+ schemas: OpenApi.IJsonSchema[];
+}): ILlmSchema => {
+ const schema: ILlmSchema | null = HttpLlm.schema({
+ components: props.components,
+ schema: props.schemas[0],
+ });
+ if (schema === null) throw new Error("Invalid schema");
+ return schema;
+};
diff --git a/test/features/llm/test_llm_schema_separate_object.ts b/test/features/llm/test_llm_schema_separate_object.ts
new file mode 100644
index 0000000..abe7802
--- /dev/null
+++ b/test/features/llm/test_llm_schema_separate_object.ts
@@ -0,0 +1,38 @@
+import { TestValidator } from "@nestia/e2e";
+import { HttpLlm, ILlmSchema, LlmTypeChecker, OpenApi } from "@samchon/openapi";
+import { LlmSchemaSeparator } from "@samchon/openapi/lib/utils/LlmSchemaSeparator";
+import typia, { tags } from "typia";
+
+export const test_llm_schema_separate_object = (): void => {
+ const separator = LlmSchemaSeparator.schema(
+ (s) => LlmTypeChecker.isString(s) && s.contentMediaType !== undefined,
+ );
+ const member: ILlmSchema = schema(typia.json.application<[IMember]>());
+ const upload: ILlmSchema = schema(typia.json.application<[IFileUpload]>());
+ const combined: ILlmSchema = schema(typia.json.application<[ICombined]>());
+
+ TestValidator.equals("member")(separator(member))([member, null]);
+ TestValidator.equals("upload")(separator(upload))([null, upload]);
+ TestValidator.equals("combined")(separator(combined))([member, upload]);
+};
+
+interface IMember {
+ id: number;
+ name: string;
+}
+interface IFileUpload {
+ file: string & tags.Format<"uri"> & tags.ContentMediaType<"image/png">;
+}
+interface ICombined extends IMember, IFileUpload {}
+
+const schema = (props: {
+ components: OpenApi.IComponents;
+ schemas: OpenApi.IJsonSchema[];
+}): ILlmSchema => {
+ const schema: ILlmSchema | null = HttpLlm.schema({
+ components: props.components,
+ schema: props.schemas[0],
+ });
+ if (schema === null) throw new Error("Invalid schema");
+ return schema;
+};
diff --git a/test/features/llm/test_llm_schema_separate_string.ts b/test/features/llm/test_llm_schema_separate_string.ts
new file mode 100644
index 0000000..ba4c6e1
--- /dev/null
+++ b/test/features/llm/test_llm_schema_separate_string.ts
@@ -0,0 +1,17 @@
+import { TestValidator } from "@nestia/e2e";
+import { ILlmSchema, LlmTypeChecker } from "@samchon/openapi";
+import { LlmSchemaSeparator } from "@samchon/openapi/lib/utils/LlmSchemaSeparator";
+
+export const test_schema_separate_string = (): void => {
+ const separator = LlmSchemaSeparator.schema(
+ (s) => LlmTypeChecker.isString(s) && s.contentMediaType !== undefined,
+ );
+ const plain: ILlmSchema = { type: "string" };
+ const upload: ILlmSchema = {
+ type: "string",
+ format: "uri",
+ contentMediaType: "image/png",
+ };
+ TestValidator.equals("plain")(separator(plain))([plain, null]);
+ TestValidator.equals("upload")(separator(upload))([null, upload]);
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_body.ts b/test/features/migrate/test_http_migrate_fetch_body.ts
new file mode 100644
index 0000000..57fbecf
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_body.ts
@@ -0,0 +1,38 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpMigration,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+} from "@samchon/openapi";
+import { OpenApi } from "@samchon/openapi/lib/OpenApi";
+import { IHttpConnection } from "@samchon/openapi/lib/structures/IHttpConnection";
+import { IHttpResponse } from "@samchon/openapi/lib/structures/IHttpResponse";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) => r.path === "/{index}/{level}/{optimal}/body" && r.method === "post",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ const response: IHttpResponse = await HttpMigration.propagate({
+ connection,
+ route,
+ parameters: {
+ index: "https://some.url/index.html",
+ level: 123,
+ optimal: true,
+ },
+ body: {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ });
+ TestValidator.equals("response.status")(response.status)(201);
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_keyword_parameters.ts b/test/features/migrate/test_http_migrate_fetch_keyword_parameters.ts
new file mode 100644
index 0000000..6551139
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_keyword_parameters.ts
@@ -0,0 +1,31 @@
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_keyword_parameters = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) =>
+ r.path === "/{index}/{level}/{optimal}/parameters" && r.method === "get",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ await HttpMigration.execute({
+ connection,
+ route,
+ parameters: {
+ index: "https://some.url/index.html",
+ level: 2,
+ optimal: true,
+ },
+ });
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_multipart.ts b/test/features/migrate/test_http_migrate_fetch_multipart.ts
new file mode 100644
index 0000000..63f268e
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_multipart.ts
@@ -0,0 +1,41 @@
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_multipart = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) =>
+ r.path === "/{index}/{level}/{optimal}/multipart" && r.method === "post",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ await HttpMigration.execute({
+ connection,
+ route,
+ parameters: {
+ index: "https://some.url/index.html",
+ level: 2,
+ optimal: true,
+ },
+ query: {
+ summary: "some summary",
+ thumbnail: "https://some.url",
+ },
+ body: {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ file: new File([new Uint8Array(999).fill(1)], "file.txt"),
+ },
+ });
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_positional_parameters.ts b/test/features/migrate/test_http_migrate_fetch_positional_parameters.ts
new file mode 100644
index 0000000..f20d839
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_positional_parameters.ts
@@ -0,0 +1,27 @@
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_positional_parameters = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) =>
+ r.path === "/{index}/{level}/{optimal}/parameters" && r.method === "get",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ await HttpMigration.execute({
+ connection,
+ route,
+ parameters: ["https://some.url/index.html", 2, true],
+ });
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_propagate.ts b/test/features/migrate/test_http_migrate_fetch_propagate.ts
new file mode 100644
index 0000000..06755a5
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_propagate.ts
@@ -0,0 +1,30 @@
+import { TestValidator } from "@nestia/e2e";
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ IHttpResponse,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_propagate = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) =>
+ r.path === "/{index}/{level}/{optimal}/parameters" && r.method === "get",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ const response: IHttpResponse = await HttpMigration.propagate({
+ connection,
+ route,
+ parameters: ["https://some.url/index.html", "two", "one"],
+ });
+ TestValidator.equals("status")(response.status)(400);
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_query.ts b/test/features/migrate/test_http_migrate_fetch_query.ts
new file mode 100644
index 0000000..5df5243
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_query.ts
@@ -0,0 +1,34 @@
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_query = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) => r.path === "/{index}/{level}/{optimal}/query" && r.method === "get",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ await HttpMigration.execute({
+ connection,
+ route,
+ parameters: {
+ index: "https://some.url/index.html",
+ level: 123,
+ optimal: true,
+ },
+ query: {
+ summary: "some summary",
+ thumbnail: "https://some.url",
+ },
+ });
+};
diff --git a/test/features/migrate/test_http_migrate_fetch_query_and_body.ts b/test/features/migrate/test_http_migrate_fetch_query_and_body.ts
new file mode 100644
index 0000000..8c1c1dd
--- /dev/null
+++ b/test/features/migrate/test_http_migrate_fetch_query_and_body.ts
@@ -0,0 +1,40 @@
+import {
+ HttpMigration,
+ IHttpConnection,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
+
+import swagger from "../../swagger.json";
+
+export const test_http_migrate_fetch_query_and_body = async (
+ connection: IHttpConnection,
+): Promise => {
+ const document: OpenApi.IDocument = OpenApi.convert(swagger as any);
+ const app: IHttpMigrateApplication = HttpMigration.application(document);
+ const route: IHttpMigrateRoute | undefined = app.routes.find(
+ (r) =>
+ r.path === "/{index}/{level}/{optimal}/query/body" && r.method === "post",
+ );
+ if (route === undefined) throw new Error("Route not found");
+
+ await HttpMigration.execute({
+ connection,
+ route,
+ parameters: {
+ index: "https://some.url/index.html",
+ level: 123,
+ optimal: true,
+ },
+ query: {
+ summary: "some summary",
+ thumbnail: "https://some.url",
+ },
+ body: {
+ title: "some title",
+ body: "some body",
+ draft: false,
+ },
+ });
+};
diff --git a/test/features/test_document_migrate_route_comment.ts b/test/features/migrate/test_http_migrate_route_comment.ts
similarity index 76%
rename from test/features/test_document_migrate_route_comment.ts
rename to test/features/migrate/test_http_migrate_route_comment.ts
index bb15eaf..9750a09 100644
--- a/test/features/test_document_migrate_route_comment.ts
+++ b/test/features/migrate/test_http_migrate_route_comment.ts
@@ -1,18 +1,23 @@
import { TestValidator } from "@nestia/e2e";
-import { IMigrateDocument, IMigrateRoute, OpenApi } from "@samchon/openapi";
+import {
+ HttpMigration,
+ IHttpMigrateApplication,
+ IHttpMigrateRoute,
+ OpenApi,
+} from "@samchon/openapi";
import fs from "fs";
-export const test_document_migrate_route_comment = async (): Promise => {
+export const test_http_migrate_route_comment = async (): Promise => {
const swagger: OpenApi.IDocument = OpenApi.convert(
JSON.parse(
await fs.promises.readFile(
- `${__dirname}/../../../examples/v3.1/shopping.json`,
+ `${__dirname}/../../../../examples/v3.1/shopping.json`,
"utf8",
),
),
);
- const migrate: IMigrateDocument = OpenApi.migrate(swagger);
- const route: IMigrateRoute | undefined = migrate.routes.find(
+ const migrate: IHttpMigrateApplication = HttpMigration.application(swagger);
+ const route: IHttpMigrateRoute | undefined = migrate.routes.find(
(r) => r.path === "/shoppings/sellers/sales/{id}" && r.method === "put",
);
TestValidator.equals("comment")(route?.comment())(EXPECTED);
diff --git a/test/features/test_document_migrate_v20.ts b/test/features/migrate/test_http_migrate_v20.ts
similarity index 56%
rename from test/features/test_document_migrate_v20.ts
rename to test/features/migrate/test_http_migrate_v20.ts
index 8a6b083..d95263d 100644
--- a/test/features/test_document_migrate_v20.ts
+++ b/test/features/migrate/test_http_migrate_v20.ts
@@ -1,16 +1,21 @@
-import { IMigrateDocument, OpenApi, SwaggerV2 } from "@samchon/openapi";
+import {
+ HttpMigration,
+ IHttpMigrateApplication,
+ OpenApi,
+ SwaggerV2,
+} from "@samchon/openapi";
import fs from "fs";
import typia from "typia";
-export const test_document_migrate_v20 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v2.0`;
+export const test_http_migrate_v20 = async (): Promise => {
+ const path: string = `${__dirname}/../../../../examples/v2.0`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: SwaggerV2.IDocument = typia.assert(
JSON.parse(await fs.promises.readFile(`${path}/${file}`, "utf8")),
);
const openapi: OpenApi.IDocument = OpenApi.convert(swagger);
- const migrate: IMigrateDocument = OpenApi.migrate(openapi);
+ const migrate: IHttpMigrateApplication = HttpMigration.application(openapi);
typia.assert(migrate);
}
};
diff --git a/test/features/test_document_migrate_v30.ts b/test/features/migrate/test_http_migrate_v30.ts
similarity index 56%
rename from test/features/test_document_migrate_v30.ts
rename to test/features/migrate/test_http_migrate_v30.ts
index de3a2e6..ecdf998 100644
--- a/test/features/test_document_migrate_v30.ts
+++ b/test/features/migrate/test_http_migrate_v30.ts
@@ -1,16 +1,21 @@
-import { IMigrateDocument, OpenApi, OpenApiV3 } from "@samchon/openapi";
+import {
+ HttpMigration,
+ IHttpMigrateApplication,
+ OpenApi,
+ OpenApiV3,
+} from "@samchon/openapi";
import fs from "fs";
import typia from "typia";
-export const test_document_migrate_v30 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.0`;
+export const test_http_migrate_v30 = async (): Promise => {
+ const path: string = `${__dirname}/../../../../examples/v3.0`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: OpenApiV3.IDocument = typia.assert(
JSON.parse(await fs.promises.readFile(`${path}/${file}`, "utf8")),
);
const openapi: OpenApi.IDocument = OpenApi.convert(swagger);
- const migrate: IMigrateDocument = OpenApi.migrate(openapi);
+ const migrate: IHttpMigrateApplication = HttpMigration.application(openapi);
typia.assert(migrate);
}
};
diff --git a/test/features/test_document_migrate_v31.ts b/test/features/migrate/test_http_migrate_v31.ts
similarity index 56%
rename from test/features/test_document_migrate_v31.ts
rename to test/features/migrate/test_http_migrate_v31.ts
index fdd9d5c..0a9f3d8 100644
--- a/test/features/test_document_migrate_v31.ts
+++ b/test/features/migrate/test_http_migrate_v31.ts
@@ -1,16 +1,21 @@
-import { IMigrateDocument, OpenApi, OpenApiV3_1 } from "@samchon/openapi";
+import {
+ HttpMigration,
+ IHttpMigrateApplication,
+ OpenApi,
+ OpenApiV3_1,
+} from "@samchon/openapi";
import fs from "fs";
import typia from "typia";
-export const test_document_migrate_v31 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.1`;
+export const test_http_migrate_v31 = async (): Promise => {
+ const path: string = `${__dirname}/../../../../examples/v3.1`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: OpenApiV3_1.IDocument = typia.assert(
JSON.parse(await fs.promises.readFile(`${path}/${file}`, "utf8")),
);
const openapi: OpenApi.IDocument = OpenApi.convert(swagger);
- const migrate: IMigrateDocument = OpenApi.migrate(openapi);
+ const migrate: IHttpMigrateApplication = HttpMigration.application(openapi);
typia.assert(migrate);
}
};
diff --git a/test/features/test_document_convert_v20.ts b/test/features/openapi/test_document_convert_v20.ts
similarity index 89%
rename from test/features/test_document_convert_v20.ts
rename to test/features/openapi/test_document_convert_v20.ts
index 1002ad7..526bb3a 100644
--- a/test/features/test_document_convert_v20.ts
+++ b/test/features/openapi/test_document_convert_v20.ts
@@ -3,7 +3,7 @@ import fs from "fs";
import typia from "typia";
export const test_document_convert_v20 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v2.0`;
+ const path: string = `${__dirname}/../../../../examples/v2.0`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: SwaggerV2.IDocument = typia.assert(
diff --git a/test/features/test_document_convert_v30.ts b/test/features/openapi/test_document_convert_v30.ts
similarity index 89%
rename from test/features/test_document_convert_v30.ts
rename to test/features/openapi/test_document_convert_v30.ts
index a6fd498..ec91471 100644
--- a/test/features/test_document_convert_v30.ts
+++ b/test/features/openapi/test_document_convert_v30.ts
@@ -3,7 +3,7 @@ import fs from "fs";
import typia from "typia";
export const test_document_convert_v30 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.0`;
+ const path: string = `${__dirname}/../../../../examples/v3.0`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: OpenApiV3.IDocument = typia.assert(
diff --git a/test/features/test_document_convert_v31.ts b/test/features/openapi/test_document_convert_v31.ts
similarity index 89%
rename from test/features/test_document_convert_v31.ts
rename to test/features/openapi/test_document_convert_v31.ts
index a22419a..4a42a2d 100644
--- a/test/features/test_document_convert_v31.ts
+++ b/test/features/openapi/test_document_convert_v31.ts
@@ -3,7 +3,7 @@ import fs from "fs";
import typia from "typia";
export const test_document_convert_v31 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.1`;
+ const path: string = `${__dirname}/../../../../examples/v3.1`;
for (const file of await fs.promises.readdir(path)) {
if (file.endsWith(".json") === false) continue;
const swagger: OpenApiV3_1.IDocument = typia.assert(
diff --git a/test/features/test_document_downgrade_v20.ts b/test/features/openapi/test_document_downgrade_v20.ts
similarity index 94%
rename from test/features/test_document_downgrade_v20.ts
rename to test/features/openapi/test_document_downgrade_v20.ts
index beef3a4..701bb0c 100644
--- a/test/features/test_document_downgrade_v20.ts
+++ b/test/features/openapi/test_document_downgrade_v20.ts
@@ -3,7 +3,7 @@ import fs from "fs";
import typia from "typia";
export const test_document_downgrade_v20 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.1`;
+ const path: string = `${__dirname}/../../../../examples/v3.1`;
for (const directory of await fs.promises.readdir(path)) {
const stats: fs.Stats = await fs.promises.lstat(`${path}/${directory}`);
if (stats.isDirectory() === false) continue;
diff --git a/test/features/test_document_downgrade_v30.ts b/test/features/openapi/test_document_downgrade_v30.ts
similarity index 94%
rename from test/features/test_document_downgrade_v30.ts
rename to test/features/openapi/test_document_downgrade_v30.ts
index b5d078e..90dd404 100644
--- a/test/features/test_document_downgrade_v30.ts
+++ b/test/features/openapi/test_document_downgrade_v30.ts
@@ -3,7 +3,7 @@ import fs from "fs";
import typia from "typia";
export const test_document_downgrade_v30 = async (): Promise => {
- const path: string = `${__dirname}/../../../examples/v3.1`;
+ const path: string = `${__dirname}/../../../../examples/v3.1`;
for (const directory of await fs.promises.readdir(path)) {
const stats: fs.Stats = await fs.promises.lstat(`${path}/${directory}`);
if (stats.isDirectory() === false) continue;
diff --git a/test/features/test_json_schema_downgrade_v20.ts b/test/features/openapi/test_json_schema_downgrade_v20.ts
similarity index 87%
rename from test/features/test_json_schema_downgrade_v20.ts
rename to test/features/openapi/test_json_schema_downgrade_v20.ts
index da70757..4002f4a 100644
--- a/test/features/test_json_schema_downgrade_v20.ts
+++ b/test/features/openapi/test_json_schema_downgrade_v20.ts
@@ -1,6 +1,6 @@
import { TestValidator } from "@nestia/e2e";
import { OpenApi, SwaggerV2 } from "@samchon/openapi";
-import { SwaggerV2Downgrader } from "@samchon/openapi/lib/internal/SwaggerV2Downgrader";
+import { SwaggerV2Downgrader } from "@samchon/openapi/lib/converters/SwaggerV2Downgrader";
export const test_json_schema_downgrade_v20 = () => {
const schema: OpenApi.IJsonSchema = {
diff --git a/test/features/test_json_schema_downgrade_v30.ts b/test/features/openapi/test_json_schema_downgrade_v30.ts
similarity index 87%
rename from test/features/test_json_schema_downgrade_v30.ts
rename to test/features/openapi/test_json_schema_downgrade_v30.ts
index 51bf90c..c93af16 100644
--- a/test/features/test_json_schema_downgrade_v30.ts
+++ b/test/features/openapi/test_json_schema_downgrade_v30.ts
@@ -1,6 +1,6 @@
import { TestValidator } from "@nestia/e2e";
import { OpenApi, OpenApiV3 } from "@samchon/openapi";
-import { OpenApiV3Downgrader } from "@samchon/openapi/lib/internal/OpenApiV3Downgrader";
+import { OpenApiV3Downgrader } from "@samchon/openapi/lib/converters/OpenApiV3Downgrader";
export const test_json_schema_downgrade_v30 = () => {
const schema: OpenApi.IJsonSchema = {
diff --git a/test/features/test_json_schema_type_checker_cover_any.ts b/test/features/openapi/test_json_schema_type_checker_cover_any.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_any.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_any.ts
diff --git a/test/features/test_json_schema_type_checker_cover_array.ts b/test/features/openapi/test_json_schema_type_checker_cover_array.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_array.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_array.ts
diff --git a/test/features/test_json_schema_type_checker_cover_nullable.ts b/test/features/openapi/test_json_schema_type_checker_cover_nullable.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_nullable.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_nullable.ts
diff --git a/test/features/test_json_schema_type_checker_cover_number.ts b/test/features/openapi/test_json_schema_type_checker_cover_number.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_number.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_number.ts
diff --git a/test/features/test_json_schema_type_checker_cover_object.ts b/test/features/openapi/test_json_schema_type_checker_cover_object.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_object.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_object.ts
diff --git a/test/features/test_json_schema_type_checker_cover_string.ts b/test/features/openapi/test_json_schema_type_checker_cover_string.ts
similarity index 100%
rename from test/features/test_json_schema_type_checker_cover_string.ts
rename to test/features/openapi/test_json_schema_type_checker_cover_string.ts
diff --git a/test/index.ts b/test/index.ts
index 0416acb..3f8ae72 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -1,12 +1,28 @@
import { DynamicExecutor } from "@nestia/e2e";
+import { NestFactory } from "@nestjs/core";
import chalk from "chalk";
+import { AppFilter } from "./controllers/AppFilter";
+import { AppModule } from "./controllers/AppModule";
+
+const EXTENSION = __filename.substr(-2);
+if (EXTENSION === "js") require("source-map-support").install();
+
const main = async (): Promise => {
+ // PREPARE SERVER
+ const app = await NestFactory.create(AppModule, { logger: false });
+ app.useGlobalFilters(new AppFilter(app.getHttpAdapter()));
+ await app.listen(3_000);
+
// DO TEST
const report: DynamicExecutor.IReport = await DynamicExecutor.validate({
prefix: "test_",
location: __dirname + "/features",
- parameters: () => [],
+ parameters: () => [
+ {
+ host: `http://localhost:3000`,
+ },
+ ],
onComplete: (exec) => {
const trace = (str: string) =>
console.log(` - ${chalk.green(exec.name)}: ${str}`);
@@ -31,9 +47,7 @@ const main = async (): Promise => {
console.log("Failed");
console.log("Elapsed time", report.time.toLocaleString(), `ms`);
}
+ await app.close();
if (exceptions.length) process.exit(-1);
};
-main().catch((exp) => {
- console.error(exp);
- process.exit(-1);
-});
+main().catch(console.error);
diff --git a/test/nestia.config.ts b/test/nestia.config.ts
new file mode 100644
index 0000000..7d06443
--- /dev/null
+++ b/test/nestia.config.ts
@@ -0,0 +1,13 @@
+import { INestiaConfig } from "@nestia/sdk";
+import { NestFactory } from "@nestjs/core";
+
+import { AppModule } from "./controllers/AppModule";
+
+export const NESTIA_CONFIG: INestiaConfig = {
+ input: () => NestFactory.create(AppModule),
+ swagger: {
+ output: "./swagger.json",
+ beautify: true,
+ },
+};
+export default NESTIA_CONFIG;
diff --git a/test/swagger.json b/test/swagger.json
new file mode 100644
index 0000000..efdd7be
--- /dev/null
+++ b/test/swagger.json
@@ -0,0 +1,436 @@
+{
+ "openapi": "3.1.0",
+ "servers": [
+ {
+ "url": "https://github.com/samchon/nestia",
+ "description": "insert your server url"
+ }
+ ],
+ "info": {
+ "version": "0.5.0-dev.20240824",
+ "title": "@samchon/openapi",
+ "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
+ "license": {
+ "name": "MIT"
+ }
+ },
+ "paths": {
+ "/{index}/{level}/{optimal}/parameters": {
+ "get": {
+ "tags": [],
+ "parameters": [
+ {
+ "name": "index",
+ "in": "path",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "text/html"
+ },
+ "required": true
+ },
+ {
+ "name": "level",
+ "in": "path",
+ "schema": {
+ "type": "number"
+ },
+ "required": true
+ },
+ {
+ "name": "optimal",
+ "in": "path",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/{index}/{level}/{optimal}/query": {
+ "get": {
+ "tags": [],
+ "parameters": [
+ {
+ "name": "index",
+ "in": "path",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "text/html"
+ },
+ "required": true
+ },
+ {
+ "name": "level",
+ "in": "path",
+ "schema": {
+ "type": "number"
+ },
+ "required": true
+ },
+ {
+ "name": "optimal",
+ "in": "path",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true
+ },
+ {
+ "name": "summary",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ },
+ {
+ "name": "thumbnail",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "image/*"
+ },
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/{index}/{level}/{optimal}/body": {
+ "post": {
+ "tags": [],
+ "parameters": [
+ {
+ "name": "index",
+ "in": "path",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "text/html"
+ },
+ "required": true
+ },
+ {
+ "name": "level",
+ "in": "path",
+ "schema": {
+ "type": "number"
+ },
+ "required": true
+ },
+ {
+ "name": "optimal",
+ "in": "path",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/IBody"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/{index}/{level}/{optimal}/query/body": {
+ "post": {
+ "tags": [],
+ "parameters": [
+ {
+ "name": "index",
+ "in": "path",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "text/html"
+ },
+ "required": true
+ },
+ {
+ "name": "level",
+ "in": "path",
+ "schema": {
+ "type": "number"
+ },
+ "required": true
+ },
+ {
+ "name": "optimal",
+ "in": "path",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true
+ },
+ {
+ "name": "thumbnail",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "image/*"
+ },
+ "required": true
+ },
+ {
+ "name": "summary",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/IBody"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "object",
+ "properties": {
+ "summary": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "summary"
+ ]
+ }
+ },
+ "required": [
+ "query"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/{index}/{level}/{optimal}/multipart": {
+ "post": {
+ "tags": [],
+ "parameters": [
+ {
+ "name": "index",
+ "in": "path",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "text/html"
+ },
+ "required": true
+ },
+ {
+ "name": "level",
+ "in": "path",
+ "schema": {
+ "type": "number"
+ },
+ "required": true
+ },
+ {
+ "name": "optimal",
+ "in": "path",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true
+ },
+ {
+ "name": "summary",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ },
+ {
+ "name": "thumbnail",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "image/*"
+ },
+ "required": true
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/IMultipart"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "object",
+ "properties": {
+ "file": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "draft": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "file",
+ "title",
+ "body",
+ "draft"
+ ]
+ }
+ },
+ "required": [
+ "body"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "IQuery": {
+ "type": "object",
+ "properties": {
+ "summary": {
+ "type": "string"
+ },
+ "thumbnail": {
+ "type": "string",
+ "format": "uri",
+ "contentMediaType": "image/*"
+ }
+ },
+ "required": [
+ "summary",
+ "thumbnail"
+ ]
+ },
+ "IBody": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "draft": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "title",
+ "body",
+ "draft"
+ ]
+ },
+ "IMultipart": {
+ "type": "object",
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary"
+ },
+ "title": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "draft": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "file",
+ "title",
+ "body",
+ "draft"
+ ]
+ }
+ }
+ },
+ "tags": [],
+ "x-samchon-emended": true
+}
\ No newline at end of file
diff --git a/test/tsconfig.json b/test/tsconfig.json
index 753ac46..1bfade9 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -1,14 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
+ "target": "ESNext",
"outDir": "../bin",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "resolveJsonModule": true,
"paths": {
"@samchon/openapi": ["../src/index.ts"],
"@samchon/openapi/lib/*": ["../src/*"],
},
"plugins": [
- { "transform": "typia/lib/transform" },
{ "transform": "typescript-transform-paths" },
+ { "transform": "typia/lib/transform" },
+ { "transform": "@nestia/core/lib/transform" },
]
},
"include": ["../src", "../test"]
diff --git a/tsconfig.json b/tsconfig.json
index 5711531..c21c8ed 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": [
"DOM",
"ES2020",
@@ -63,7 +63,7 @@
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
@@ -94,8 +94,8 @@
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */