Skip to content

Commit

Permalink
Merge pull request #115 from samchon/feat/name
Browse files Browse the repository at this point in the history
New plugin property `OpenApi.IOperation["x-samchon-accessor"]`.
  • Loading branch information
samchon authored Dec 18, 2024
2 parents 9d60fdc + f4093d7 commit 5548c64
Show file tree
Hide file tree
Showing 10 changed files with 7,733 additions and 3,770 deletions.
11,361 changes: 7,615 additions & 3,746 deletions examples/v3.1/shopping.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@samchon/openapi",
"version": "2.2.1",
"version": "2.3.0",
"description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
Expand Down
5 changes: 3 additions & 2 deletions src/HttpMigration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OpenApi } from "./OpenApi";
import { MigrateConverter } from "./composers/migrate/MigrateConverter";
import { HttpMigrateApplicationComposer } from "./composers/migrate/HttpMigrateApplicationComposer";
import { HttpMigrateRouteFetcher } from "./http/HttpMigrateRouteFetcher";
import { IHttpConnection } from "./structures/IHttpConnection";
import { IHttpMigrateApplication } from "./structures/IHttpMigrateApplication";
Expand Down Expand Up @@ -74,7 +74,8 @@ export namespace HttpMigration {
*/
export const application = (
document: OpenApi.IDocument,
): IHttpMigrateApplication => MigrateConverter.convert(document);
): IHttpMigrateApplication =>
HttpMigrateApplicationComposer.compose(document);

/**
* Properties for the request to the HTTP server.
Expand Down
31 changes: 31 additions & 0 deletions src/OpenApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,37 @@ export namespace OpenApi {
* interface.
*/
"x-samchon-human"?: boolean;

/**
* Accessor of the operation.
*
* If you configure this property, the assigned value would be used as
* {@link IHttpMigrateRoute.accessor}. Also, it also can be used as the
* {@link IHttpLlmFunction.name} by joininig with `.` character in the
* LLM function calling application.
*
* Note that, the `x-samchon-accessor` value must be unique in the entire
* OpenAPI document operations. If there're duplicated `x-samchon-accessor`
* values, {@link IHttpMigrateRoute.accessor} will ignore every duplicated
* `x-samchon-accessor` values and generate the
* {@link IHttpMigrateRoute.accessor} by itself.
*/
"x-samchon-accessor"?: string[];

/**
* Controller of the operation.
*
* If you configure this property, the assigned value would be utilized
* as the controller name in the OpenAPI generator library like
* [`@nestia/editor`](https://nestia.io/docs/editor/) and
* [`@nestia/migrate`](https://nestia.io/docs/migrate/).
*
* Also, if {@link x-samchon-accessor} has been configured, its last
* element would be used as the controller method (function) name.
* Of course, the OpenAPI document generator `@nestia/sdk` fills both of
* them.
*/
"x-samchon-controller"?: string;
}
export namespace IOperation {
/**
Expand Down
19 changes: 17 additions & 2 deletions src/composers/HttpLlmApplicationComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export namespace HttpLlmComposer {
return result.value as ILlmSchema.ModelSchema[Model];
};

// METADATA
const endpoint: string = `$input.paths[${JSON.stringify(props.route.path)}][${JSON.stringify(props.route.method)}]`;
const output: ILlmSchema.ModelSchema[Model] | null | undefined = props.route
.success
Expand Down Expand Up @@ -169,7 +170,21 @@ export namespace HttpLlmComposer {
]
: []),
];
if (output === null || properties.some(([_k, v]) => v === null))

// FUNTION NAME
const name: string = props.route.accessor.join("_");
const isNameVariable: boolean = /^[a-zA-Z0-9_-]+$/.test(name);
const isNameStartsWithNumber: boolean = /^[0-9]/.test(name[0] ?? "");
if (isNameVariable === false)
props.errors.push(
`Elements of path (separated by '/') must be composed with alphabets, numbers, underscores, and hyphens`,
);
if (
output === null ||
properties.some(([_k, v]) => v === null) ||
isNameVariable === false ||
isNameStartsWithNumber === true
)
return null;

// COMPOSE PARAMETERS
Expand All @@ -189,7 +204,7 @@ export namespace HttpLlmComposer {
return {
method: props.route.method as "get",
path: props.route.path,
name: props.route.accessor.join("_"),
name,
parameters,
separated: props.options.separate
? (LlmSchemaComposer.separateParameters(props.model)({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { OpenApi } from "../../OpenApi";
import { IHttpMigrateApplication } from "../../structures/IHttpMigrateApplication";
import { IHttpMigrateRoute } from "../../structures/IHttpMigrateRoute";
import { EndpointUtil } from "../../utils/EndpointUtil";
import { HttpMigrateApplicationComposer } from "../HttpMigrateApplicationComposer";
import { MigrateRouteAccessor } from "./MigrateRouteAccessor";
import { HttpMigrateRouteAccessor } from "./HttpMigrateRouteAccessor";
import { HttpMigrateRouteComposer } from "./HttpMigrateRouteComposer";

export namespace MigrateConverter {
export const convert = (
export namespace HttpMigrateApplicationComposer {
export const compose = (
document: OpenApi.IDocument,
): IHttpMigrateApplication => {
const errors: IHttpMigrateApplication.IError[] = [];
Expand All @@ -20,7 +20,7 @@ export namespace MigrateConverter {
.map((method) => {
const operation: OpenApi.IOperation = collection[method]!;
const migrated: IHttpMigrateRoute | string[] =
HttpMigrateApplicationComposer.application({
HttpMigrateRouteComposer.compose({
document,
method,
path,
Expand All @@ -43,7 +43,7 @@ export namespace MigrateConverter {
const operations: IHttpMigrateRoute[] = entire.filter(
(o): o is IHttpMigrateRoute => !!o,
);
MigrateRouteAccessor.overwrite(operations);
HttpMigrateRouteAccessor.overwrite(operations);
return {
document: () => document,
routes: operations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { EndpointUtil } from "../../utils/EndpointUtil";
import { Escaper } from "../../utils/Escaper";
import { MapUtil } from "../../utils/MapUtil";

export namespace MigrateRouteAccessor {
export namespace HttpMigrateRouteAccessor {
export const overwrite = (routes: IHttpMigrateRoute[]): void => {
const predefined: Map<string, number> = getPredefinedAccessors(routes);
const dict: Map<string, IElement> = collect((op) =>
op.emendedPath
.split("/")
.filter((str) => !!str.length && str[0] !== ":")
.map(EndpointUtil.normalize)
.map((str) => (Escaper.variable(str) ? str : `_${str}`)),
)(routes) as Map<string, IElement>;

for (const props of dict.values())
props.entries.forEach((entry, i) => {
entry.alias = EndpointUtil.escapeDuplicate(
Expand All @@ -20,7 +22,6 @@ export namespace MigrateRouteAccessor {
...props.entries.filter((_, j) => i !== j).map((e) => e.alias),
].map(EndpointUtil.normalize),
)(EndpointUtil.normalize(entry.alias));
entry.route.accessor = [...props.namespace, entry.alias];

const parameters: { name: string; key: string }[] = [
...entry.route.parameters,
Expand All @@ -36,6 +37,12 @@ export namespace MigrateRouteAccessor {
...parameters.filter((_, j) => i !== j).map((y) => y.key),
])(p.key)),
);

const accessor: string[] | undefined =
entry.route.operation()["x-samchon-accessor"];
if (accessor !== undefined && predefined.get(accessor.join(".")) === 1)
entry.route.accessor = accessor;
else entry.route.accessor = [...props.namespace, entry.alias];
});
};

Expand Down Expand Up @@ -85,6 +92,19 @@ export namespace MigrateRouteAccessor {
);
};

const getPredefinedAccessors = (
routes: IHttpMigrateRoute[],
): Map<string, number> => {
const dict: Map<string, number> = new Map();
for (const r of routes) {
const accessor = r.operation()["x-samchon-accessor"]?.join(".");
if (accessor === undefined) continue;
else if (dict.has(accessor)) dict.set(accessor, dict.get(accessor)! + 1);
else dict.set(accessor, 1);
}
return dict;
};

interface IElement {
namespace: string[];
entries: IEntry[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { OpenApi } from "../OpenApi";
import { IHttpMigrateRoute } from "../structures/IHttpMigrateRoute";
import { EndpointUtil } from "../utils/EndpointUtil";
import { Escaper } from "../utils/Escaper";
import { OpenApiTypeChecker } from "../utils/OpenApiTypeChecker";
import { OpenApi } from "../../OpenApi";
import { IHttpMigrateRoute } from "../../structures/IHttpMigrateRoute";
import { EndpointUtil } from "../../utils/EndpointUtil";
import { Escaper } from "../../utils/Escaper";
import { OpenApiTypeChecker } from "../../utils/OpenApiTypeChecker";

export namespace HttpMigrateApplicationComposer {
export namespace HttpMigrateRouteComposer {
export interface IProps {
document: OpenApi.IDocument;
method: "head" | "get" | "post" | "put" | "patch" | "delete";
path: string;
emendedPath: string;
operation: OpenApi.IOperation;
}
export const application = (props: IProps): IHttpMigrateRoute | string[] => {
export const compose = (props: IProps): IHttpMigrateRoute | string[] => {
//----
// REQUEST AND RESPONSE BODY
//----
Expand Down
29 changes: 29 additions & 0 deletions test/features/migrate/test_http_migrate_route_accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { TestValidator } from "@nestia/e2e";
import {
HttpMigration,
IHttpMigrateApplication,
IHttpMigrateRoute,
OpenApi,
} from "@samchon/openapi";
import fs from "fs";

import { TestGlobal } from "../../TestGlobal";

export const test_http_migrate_route_accessor = async (): Promise<void> => {
const document: OpenApi.IDocument = OpenApi.convert(
JSON.parse(
await fs.promises.readFile(
`${TestGlobal.ROOT}/examples/v3.1/shopping.json`,
"utf8",
),
),
);
const application: IHttpMigrateApplication =
HttpMigration.application(document);
const route: IHttpMigrateRoute | undefined = application.routes.find(
(r) => r.path === "/shoppings/sellers/sales" && r.method === "post",
);
TestValidator.equals("accessor")(route?.accessor.join("."))(
"shoppings.sellers.sales.create",
);
};
6 changes: 2 additions & 4 deletions test/features/migrate/test_http_migrate_route_comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ The 2nd purpose is for the A/B tests. {@link IShoppingSeller Seller} needs
to demonstrate operating performance by chaning price, content, and
composition of the product. This snapshot concept would be helpful for it.
@param id Target sale's ID to update.
Note that, you have to specify only the sale ID of your own.
@param body New information of the sale.
Note that, your input data would entirely modify the sale, so that have to be careful if you only want the partial updating.
@param id Target sale's {@link IShoppingSale.id }
@param body New information of the sale\n@security bearer
@tag Sale`
.split("\r\n")
.join("\n");

0 comments on commit 5548c64

Please sign in to comment.