Skip to content

Commit f8d1453

Browse files
authoredMar 1, 2025··
New embedded function IHttpLlmFunction.validate()for validation feedback (#147)
* Developing internal validator * Prepared testing program * Only object discriminator is left * Fiix HTTP application accessors * Validator implementation completed
1 parent 3db6bc4 commit f8d1453

File tree

278 files changed

+8972
-136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

278 files changed

+8972
-136
lines changed
 

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@samchon/openapi",
3-
"version": "3.0.0-dev.20250226",
3+
"version": "3.0.0-dev.20250301",
44
"description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
55
"main": "./lib/index.js",
66
"module": "./lib/index.mjs",
@@ -69,6 +69,7 @@
6969
"nestia": "^6.0.1",
7070
"openai": "^4.72.0",
7171
"prettier": "^3.2.5",
72+
"randexp": "^0.5.3",
7273
"rimraf": "^5.0.5",
7374
"rollup": "^4.18.1",
7475
"rollup-plugin-auto-external": "^2.0.0",

‎src/composers/HttpLlmApplicationComposer.ts

+111-108
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { OpenApi } from "../OpenApi";
2-
import { IChatGptSchema } from "../structures/IChatGptSchema";
32
import { IHttpLlmApplication } from "../structures/IHttpLlmApplication";
43
import { IHttpLlmFunction } from "../structures/IHttpLlmFunction";
54
import { IHttpMigrateApplication } from "../structures/IHttpMigrateApplication";
65
import { IHttpMigrateRoute } from "../structures/IHttpMigrateRoute";
76
import { ILlmFunction } from "../structures/ILlmFunction";
87
import { ILlmSchema } from "../structures/ILlmSchema";
8+
import { IOpenApiSchemaError } from "../structures/IOpenApiSchemaError";
9+
import { IResult } from "../structures/IResult";
10+
import { OpenApiValidator } from "../utils/OpenApiValidator";
911
import { LlmSchemaComposer } from "./LlmSchemaComposer";
1012

1113
export namespace HttpLlmComposer {
@@ -54,7 +56,7 @@ export namespace HttpLlmComposer {
5456
const localErrors: string[] = [];
5557
const func: IHttpLlmFunction<Model> | null = composeFunction<Model>({
5658
model: props.model,
57-
options: props.options,
59+
config: props.options,
5860
components: props.migrate.document().components,
5961
route: route,
6062
errors: localErrors,
@@ -86,95 +88,12 @@ export namespace HttpLlmComposer {
8688
model: Model;
8789
components: OpenApi.IComponents;
8890
route: IHttpMigrateRoute;
89-
options: IHttpLlmApplication.IOptions<Model>;
91+
config: IHttpLlmApplication.IOptions<Model>;
9092
errors: string[];
9193
index: number;
9294
}): IHttpLlmFunction<Model> | null => {
93-
const $defs: Record<string, IChatGptSchema> = {};
94-
const cast = (
95-
s: OpenApi.IJsonSchema,
96-
accessor: string,
97-
): ILlmSchema.ModelSchema[Model] | null => {
98-
const result = LlmSchemaComposer.schema(props.model)({
99-
config: props.options as any,
100-
schema: s,
101-
components: props.components,
102-
$defs,
103-
accessor,
104-
refAccessor: `$input.components.schemas`,
105-
});
106-
if (result.success === false) {
107-
props.errors.push(
108-
...result.error.reasons.map((r) => `${r.accessor}: ${r.message}`),
109-
);
110-
return null;
111-
}
112-
return result.value as ILlmSchema.ModelSchema[Model];
113-
};
114-
11595
// METADATA
11696
const endpoint: string = `$input.paths[${JSON.stringify(props.route.path)}][${JSON.stringify(props.route.method)}]`;
117-
const output: ILlmSchema.ModelSchema[Model] | null | undefined = props.route
118-
.success
119-
? cast(
120-
props.route.success.schema,
121-
`${endpoint}.responses[${JSON.stringify(props.route.success.status)}][${JSON.stringify(props.route.success.type)}].schema`,
122-
)
123-
: undefined;
124-
const properties: Array<
125-
readonly [string, ILlmSchema.ModelSchema[Model] | null]
126-
> = [
127-
...props.route.parameters.map(
128-
(s) =>
129-
[
130-
s.key,
131-
cast(
132-
{
133-
...s.schema,
134-
title: s.parameter().title ?? s.schema.title,
135-
description: s.parameter().description ?? s.schema.description,
136-
},
137-
`${endpoint}.parameters[${JSON.stringify(s.key)}].schema`,
138-
),
139-
] as const,
140-
),
141-
...(props.route.query
142-
? [
143-
[
144-
props.route.query.key,
145-
cast(
146-
{
147-
...props.route.query.schema,
148-
title:
149-
props.route.query.title() ?? props.route.query.schema.title,
150-
description:
151-
props.route.query.description() ??
152-
props.route.query.schema.description,
153-
},
154-
`${endpoint}.parameters[${JSON.stringify(props.route.query.key)}].schema`,
155-
),
156-
] as const,
157-
]
158-
: []),
159-
...(props.route.body
160-
? [
161-
[
162-
props.route.body.key,
163-
cast(
164-
{
165-
...props.route.body.schema,
166-
description:
167-
props.route.body.description() ??
168-
props.route.body.schema.description,
169-
},
170-
`${endpoint}.requestBody.content[${JSON.stringify(props.route.body.type)}].schema`,
171-
),
172-
] as const,
173-
]
174-
: []),
175-
];
176-
177-
// DESCRIPTION
17897
const operation: OpenApi.IOperation = props.route.operation();
17998
const description: [string | undefined, number] = (() => {
18099
if (!operation.summary?.length || !operation.description?.length)
@@ -204,44 +123,128 @@ export namespace HttpLlmComposer {
204123
props.errors.push(
205124
`Elements of path (separated by '/') must be composed with alphabets, numbers, underscores, and hyphens`,
206125
);
126+
127+
//----
128+
// CONSTRUCT SCHEMAS
129+
//----
130+
// PARAMETERS
131+
const parameters: OpenApi.IJsonSchema.IObject = {
132+
type: "object",
133+
properties: Object.fromEntries([
134+
...props.route.parameters.map(
135+
(s) =>
136+
[
137+
s.key,
138+
{
139+
...s.schema,
140+
title: s.parameter().title ?? s.schema.title,
141+
description: s.parameter().description ?? s.schema.description,
142+
},
143+
] as const,
144+
),
145+
...(props.route.query
146+
? [
147+
[
148+
props.route.query.key,
149+
{
150+
...props.route.query.schema,
151+
title:
152+
props.route.query.title() ?? props.route.query.schema.title,
153+
description:
154+
props.route.query.description() ??
155+
props.route.query.schema.description,
156+
},
157+
] as const,
158+
]
159+
: []),
160+
...(props.route.body
161+
? [
162+
[
163+
props.route.body.key,
164+
{
165+
...props.route.body.schema,
166+
description:
167+
props.route.body.description() ??
168+
props.route.body.schema.description,
169+
},
170+
] as const,
171+
]
172+
: []),
173+
]),
174+
};
175+
parameters.required = Object.keys(parameters.properties ?? {});
176+
177+
const llmParameters: IResult<
178+
ILlmSchema.IParameters<Model>,
179+
IOpenApiSchemaError
180+
> = LlmSchemaComposer.parameters(props.model)({
181+
config: props.config as any,
182+
components: props.components,
183+
schema: parameters,
184+
accessor: `${endpoint}.parameters`,
185+
}) as IResult<ILlmSchema.IParameters<Model>, IOpenApiSchemaError>;
186+
187+
// RETURN VALUE
188+
const output: IResult<ILlmSchema<Model>, IOpenApiSchemaError> | undefined =
189+
props.route.success
190+
? (LlmSchemaComposer.schema(props.model)({
191+
config: props.config as any,
192+
components: props.components,
193+
schema: props.route.success.schema,
194+
accessor: `${endpoint}.responses[${JSON.stringify(props.route.success.status)}][${JSON.stringify(props.route.success.type)}].schema`,
195+
$defs: llmParameters.success
196+
? (llmParameters.value as any).$defs!
197+
: {},
198+
}) as IResult<ILlmSchema<Model>, IOpenApiSchemaError>)
199+
: undefined;
200+
201+
//----
202+
// CONVERSION
203+
//----
207204
if (
208-
output === null ||
209-
properties.some(([_k, v]) => v === null) ||
205+
output?.success === false ||
206+
llmParameters.success === false ||
210207
isNameVariable === false ||
211208
isNameStartsWithNumber === true ||
212209
description[1] > 1_024
213-
)
210+
) {
211+
if (output?.success === false)
212+
props.errors.push(
213+
...output.error.reasons.map((r) => `${r.accessor}: ${r.message}`),
214+
);
215+
if (llmParameters.success === false)
216+
props.errors.push(
217+
...llmParameters.error.reasons.map((r) => {
218+
const accessor: string = r.accessor.replace(
219+
`parameters.properties["body"]`,
220+
`requestBody.content[${JSON.stringify(props.route.body?.type ?? "application/json")}].schema`,
221+
);
222+
return `${accessor}: ${r.message}`;
223+
}),
224+
);
214225
return null;
215-
216-
// COMPOSE PARAMETERS
217-
const parameters: ILlmSchema.ModelParameters[Model] = {
218-
type: "object",
219-
properties: Object.fromEntries(
220-
properties as [string, ILlmSchema.ModelSchema[Model]][],
221-
),
222-
additionalProperties: false,
223-
required: properties.map(([k]) => k),
224-
} as any as ILlmSchema.ModelParameters[Model];
225-
if (LlmSchemaComposer.isDefs(props.model))
226-
(parameters as any as IChatGptSchema.IParameters).$defs = $defs;
227-
228-
// FINALIZATION
226+
}
229227
return {
230228
method: props.route.method as "get",
231229
path: props.route.path,
232230
name,
233-
parameters,
234-
separated: props.options.separate
231+
parameters: llmParameters.value,
232+
separated: props.config.separate
235233
? (LlmSchemaComposer.separateParameters(props.model)({
236-
predicate: props.options.separate as any,
234+
predicate: props.config.separate as any,
237235
parameters:
238-
parameters satisfies ILlmSchema.ModelParameters[Model] as any,
236+
llmParameters.value satisfies ILlmSchema.ModelParameters[Model] as any,
239237
}) as ILlmFunction.ISeparated<Model>)
240238
: undefined,
241-
output: output as any,
239+
output: output?.value,
242240
description: description[0],
243241
deprecated: operation.deprecated,
244242
tags: operation.tags,
243+
validate: OpenApiValidator.create({
244+
components: props.components,
245+
schema: parameters,
246+
required: true,
247+
}),
245248
route: () => props.route as any,
246249
operation: () => props.route.operation(),
247250
};

0 commit comments

Comments
 (0)
Please sign in to comment.