mock-fried 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/runtime/server/handlers/openapi/client-mode.d.ts +23 -0
- package/dist/runtime/server/handlers/openapi/client-mode.js +217 -0
- package/dist/runtime/server/handlers/{openapi.d.ts → openapi/index.d.ts} +3 -0
- package/dist/runtime/server/handlers/openapi/index.js +79 -0
- package/dist/runtime/server/handlers/openapi/spec-mode.d.ts +14 -0
- package/dist/runtime/server/handlers/openapi/spec-mode.js +198 -0
- package/dist/runtime/server/handlers/reset.js +1 -1
- package/dist/runtime/server/handlers/rpc.js +29 -93
- package/dist/runtime/server/handlers/schema.js +8 -53
- package/dist/runtime/server/utils/cache-manager.d.ts +99 -0
- package/dist/runtime/server/utils/cache-manager.js +84 -0
- package/dist/runtime/server/utils/mock/pagination/cursor-manager.js +16 -8
- package/dist/runtime/server/utils/mock/pagination/types.d.ts +4 -0
- package/dist/runtime/server/utils/mock/pagination/types.js +2 -1
- package/dist/runtime/server/utils/pagination-factory.d.ts +26 -0
- package/dist/runtime/server/utils/pagination-factory.js +29 -0
- package/dist/runtime/server/utils/proto-utils.d.ts +14 -0
- package/dist/runtime/server/utils/proto-utils.js +68 -0
- package/package.json +1 -1
- package/dist/runtime/server/handlers/openapi.js +0 -493
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI Client Package Mode Handler
|
|
3
|
+
* Handles mock responses based on generated OpenAPI client packages
|
|
4
|
+
*/
|
|
5
|
+
import { SchemaMockGenerator, CursorPaginationManager, PagePaginationManager } from '../../utils/mock/index.js';
|
|
6
|
+
import type { ParsedClientPackage, OpenApiClientConfig, MockPaginationConfig, MockCursorConfig } from '../../../../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* 클라이언트 패키지에서 파싱된 정보 가져오기 (캐싱 포함)
|
|
9
|
+
*/
|
|
10
|
+
export declare function getClientPackageData(packagePath: string, config?: OpenApiClientConfig, paginationConfig?: MockPaginationConfig, cursorConfig?: MockCursorConfig): {
|
|
11
|
+
package: ParsedClientPackage;
|
|
12
|
+
generator: SchemaMockGenerator;
|
|
13
|
+
cursorManager: CursorPaginationManager;
|
|
14
|
+
pageManager: PagePaginationManager;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 클라이언트 패키지 모드에서 Mock 응답 생성
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleClientPackageRequest(pkg: ParsedClientPackage, generator: SchemaMockGenerator, cursorManager: CursorPaginationManager, pageManager: PagePaginationManager, path: string, method: string, query: Record<string, string | number>, backwardParam?: string): {
|
|
20
|
+
statusCode: number;
|
|
21
|
+
body: unknown;
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SchemaMockGenerator,
|
|
3
|
+
extractDataModelName,
|
|
4
|
+
CursorPaginationManager,
|
|
5
|
+
PagePaginationManager
|
|
6
|
+
} from "../../utils/mock/index.js";
|
|
7
|
+
import { getClientPackage } from "../../utils/client-parser.js";
|
|
8
|
+
import { cacheManager } from "../../utils/cache-manager.js";
|
|
9
|
+
function getPathSpecificity(path) {
|
|
10
|
+
const segments = path.split("/").filter(Boolean);
|
|
11
|
+
const paramCount = (path.match(/\{(\w+)\}/g) || []).length;
|
|
12
|
+
return segments.length * 100 - paramCount * 10;
|
|
13
|
+
}
|
|
14
|
+
function findMatchingEndpoint(endpoints, path, method) {
|
|
15
|
+
const normalizedMethod = method.toUpperCase();
|
|
16
|
+
const sortedEndpoints = [...endpoints].filter((e) => e.method === normalizedMethod).sort((a, b) => getPathSpecificity(b.path) - getPathSpecificity(a.path));
|
|
17
|
+
for (const endpoint of sortedEndpoints) {
|
|
18
|
+
const pattern = endpoint.path.replace(/\{(\w+)\}/g, "([^/]+)");
|
|
19
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
20
|
+
const match = path.match(regex);
|
|
21
|
+
if (match) {
|
|
22
|
+
const pathParams = {};
|
|
23
|
+
const paramNames = endpoint.path.match(/\{(\w+)\}/g) || [];
|
|
24
|
+
paramNames.forEach((param, index) => {
|
|
25
|
+
const paramName = param.slice(1, -1);
|
|
26
|
+
pathParams[paramName] = match[index + 1] || "";
|
|
27
|
+
});
|
|
28
|
+
return { endpoint, pathParams };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function getClientPackageData(packagePath, config, paginationConfig, cursorConfig) {
|
|
34
|
+
const cache = cacheManager.clientMode;
|
|
35
|
+
if (cache.package && cache.path === packagePath && cache.generator && cache.cursorManager && cache.pageManager) {
|
|
36
|
+
return {
|
|
37
|
+
package: cache.package,
|
|
38
|
+
generator: cache.generator,
|
|
39
|
+
cursorManager: cache.cursorManager,
|
|
40
|
+
pageManager: cache.pageManager
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
cache.package = getClientPackage(packagePath, config);
|
|
44
|
+
cache.path = packagePath;
|
|
45
|
+
cache.generator = new SchemaMockGenerator(cache.package.models);
|
|
46
|
+
cache.cursorManager = new CursorPaginationManager(cache.generator, {
|
|
47
|
+
cursorConfig
|
|
48
|
+
});
|
|
49
|
+
cache.pageManager = new PagePaginationManager(cache.generator, {
|
|
50
|
+
config: paginationConfig
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
package: cache.package,
|
|
54
|
+
generator: cache.generator,
|
|
55
|
+
cursorManager: cache.cursorManager,
|
|
56
|
+
pageManager: cache.pageManager
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function handleClientPackageRequest(pkg, generator, cursorManager, pageManager, path, method, query, backwardParam = "isBackward") {
|
|
60
|
+
const match = findMatchingEndpoint(pkg.endpoints, path, method);
|
|
61
|
+
if (!match) {
|
|
62
|
+
return {
|
|
63
|
+
statusCode: 404,
|
|
64
|
+
body: { error: "Not found", message: `No matching endpoint for ${method} ${path}` }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const { endpoint, pathParams } = match;
|
|
68
|
+
if (endpoint.responseType.toLowerCase() === "void") {
|
|
69
|
+
return {
|
|
70
|
+
statusCode: 204,
|
|
71
|
+
body: null,
|
|
72
|
+
meta: {
|
|
73
|
+
operationId: endpoint.operationId,
|
|
74
|
+
apiClass: endpoint.apiClassName,
|
|
75
|
+
responseType: endpoint.responseType
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const primitiveTypes = ["object", "string", "number", "boolean", "any", "unknown"];
|
|
80
|
+
if (primitiveTypes.includes(endpoint.responseType.toLowerCase())) {
|
|
81
|
+
return handlePrimitiveResponse(path, endpoint);
|
|
82
|
+
}
|
|
83
|
+
const typeInfo = extractDataModelName(endpoint.responseType, pkg.models);
|
|
84
|
+
const { modelName, isList, listFieldName, wrapperType } = typeInfo;
|
|
85
|
+
const page = Number(query.page) || 1;
|
|
86
|
+
const limit = Number(query.limit) || Number(query.size) || 20;
|
|
87
|
+
const cursor = query.cursor;
|
|
88
|
+
const isBackward = query[backwardParam] === "true" || query[backwardParam] === "1";
|
|
89
|
+
const wrapperSchema = wrapperType ? pkg.models.get(wrapperType) : null;
|
|
90
|
+
const hasItemsField = listFieldName === "items";
|
|
91
|
+
const hasPaginationFields = wrapperSchema?.fields.some(
|
|
92
|
+
(f) => ["page", "totalPages", "total", "totalItems", "pagination"].includes(f.name)
|
|
93
|
+
);
|
|
94
|
+
let responseData;
|
|
95
|
+
if (isList) {
|
|
96
|
+
const seed = `${endpoint.path}-${JSON.stringify(pathParams)}`;
|
|
97
|
+
if (hasItemsField && hasPaginationFields) {
|
|
98
|
+
responseData = handlePaginatedListResponse(
|
|
99
|
+
cursorManager,
|
|
100
|
+
pageManager,
|
|
101
|
+
modelName,
|
|
102
|
+
seed,
|
|
103
|
+
page,
|
|
104
|
+
limit,
|
|
105
|
+
cursor,
|
|
106
|
+
isBackward
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
responseData = handleSimpleListResponse(
|
|
110
|
+
cursorManager,
|
|
111
|
+
modelName,
|
|
112
|
+
seed,
|
|
113
|
+
limit,
|
|
114
|
+
cursor,
|
|
115
|
+
isBackward,
|
|
116
|
+
listFieldName,
|
|
117
|
+
wrapperSchema
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
const seed = `${endpoint.operationId}-${JSON.stringify(pathParams)}`;
|
|
122
|
+
responseData = generator.generateOne(endpoint.responseType, seed);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
statusCode: 200,
|
|
126
|
+
body: responseData,
|
|
127
|
+
meta: {
|
|
128
|
+
operationId: endpoint.operationId,
|
|
129
|
+
apiClass: endpoint.apiClassName,
|
|
130
|
+
responseType: endpoint.responseType
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function handlePrimitiveResponse(path, endpoint) {
|
|
135
|
+
const pathLower = path.toLowerCase();
|
|
136
|
+
let primitiveResponse = {};
|
|
137
|
+
if (pathLower.includes("health")) {
|
|
138
|
+
primitiveResponse = { status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
139
|
+
} else if (pathLower.includes("ping")) {
|
|
140
|
+
primitiveResponse = { pong: true };
|
|
141
|
+
} else if (endpoint.responseType === "string") {
|
|
142
|
+
primitiveResponse = "success";
|
|
143
|
+
} else if (endpoint.responseType === "number") {
|
|
144
|
+
primitiveResponse = 0;
|
|
145
|
+
} else if (endpoint.responseType === "boolean") {
|
|
146
|
+
primitiveResponse = true;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
statusCode: 200,
|
|
150
|
+
body: primitiveResponse,
|
|
151
|
+
meta: {
|
|
152
|
+
operationId: endpoint.operationId,
|
|
153
|
+
apiClass: endpoint.apiClassName,
|
|
154
|
+
responseType: endpoint.responseType
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function handlePaginatedListResponse(cursorManager, pageManager, modelName, seed, page, limit, cursor, isBackward) {
|
|
159
|
+
if (cursor || isBackward) {
|
|
160
|
+
const result = cursorManager.getCursorPage(modelName, {
|
|
161
|
+
cursor,
|
|
162
|
+
limit,
|
|
163
|
+
total: 100,
|
|
164
|
+
seed,
|
|
165
|
+
isBackward
|
|
166
|
+
});
|
|
167
|
+
const { _snapshotId: _, ...responseWithoutSnapshotId } = result;
|
|
168
|
+
return responseWithoutSnapshotId;
|
|
169
|
+
} else {
|
|
170
|
+
const result = pageManager.getPagedResponse(modelName, {
|
|
171
|
+
page,
|
|
172
|
+
limit,
|
|
173
|
+
total: 100,
|
|
174
|
+
seed
|
|
175
|
+
});
|
|
176
|
+
const { _snapshotId: _, ...responseWithoutSnapshotId } = result;
|
|
177
|
+
return responseWithoutSnapshotId;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function handleSimpleListResponse(cursorManager, modelName, seed, limit, cursor, isBackward, listFieldName, wrapperSchema) {
|
|
181
|
+
const result = cursorManager.getCursorPage(modelName, {
|
|
182
|
+
cursor,
|
|
183
|
+
limit,
|
|
184
|
+
total: 100,
|
|
185
|
+
seed,
|
|
186
|
+
isBackward
|
|
187
|
+
});
|
|
188
|
+
if (listFieldName) {
|
|
189
|
+
const listField = wrapperSchema?.fields.find((f) => f.name === listFieldName);
|
|
190
|
+
const listJsonKey = listField?.jsonKey || listFieldName;
|
|
191
|
+
const otherFields = {};
|
|
192
|
+
if (wrapperSchema) {
|
|
193
|
+
for (const field of wrapperSchema.fields) {
|
|
194
|
+
if (field.name !== listFieldName) {
|
|
195
|
+
const outputKey = field.jsonKey || field.name;
|
|
196
|
+
if (field.name === "nextCursor" || field.name === "cursor") {
|
|
197
|
+
otherFields[outputKey] = result.nextCursor ?? null;
|
|
198
|
+
} else if (field.name === "prevCursor") {
|
|
199
|
+
otherFields[outputKey] = result.prevCursor ?? null;
|
|
200
|
+
} else if (field.name === "hasMore") {
|
|
201
|
+
otherFields[outputKey] = result.hasMore;
|
|
202
|
+
} else if (field.name === "hasPrev") {
|
|
203
|
+
otherFields[outputKey] = result.hasPrev ?? false;
|
|
204
|
+
} else if (field.name === "total" || field.name === "totalItems") {
|
|
205
|
+
otherFields[outputKey] = 100;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
[listJsonKey]: result.items,
|
|
212
|
+
...otherFields
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
return result.items;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError } from "h3";
|
|
2
|
+
import { parseQuery } from "ufo";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
import { cacheManager } from "../../utils/cache-manager.js";
|
|
5
|
+
import { getOpenAPIBackend } from "./spec-mode.js";
|
|
6
|
+
import { getClientPackageData, handleClientPackageRequest } from "./client-mode.js";
|
|
7
|
+
export function clearOpenApiCache() {
|
|
8
|
+
cacheManager.clearOpenApi();
|
|
9
|
+
}
|
|
10
|
+
export default defineEventHandler(async (event) => {
|
|
11
|
+
const config = useRuntimeConfig(event);
|
|
12
|
+
const mockConfig = config.mock;
|
|
13
|
+
const prefix = mockConfig?.prefix || "/mock";
|
|
14
|
+
const fullPath = event.path;
|
|
15
|
+
const queryIndex = fullPath.indexOf("?");
|
|
16
|
+
let path = queryIndex >= 0 ? fullPath.substring(0, queryIndex) : fullPath;
|
|
17
|
+
const queryString = queryIndex >= 0 ? fullPath.substring(queryIndex + 1) : "";
|
|
18
|
+
if (path.startsWith(prefix)) {
|
|
19
|
+
path = path.substring(prefix.length) || "/";
|
|
20
|
+
}
|
|
21
|
+
const query = parseQuery(queryString);
|
|
22
|
+
let body;
|
|
23
|
+
if (event.method !== "GET" && event.method !== "HEAD") {
|
|
24
|
+
try {
|
|
25
|
+
body = await readBody(event);
|
|
26
|
+
} catch {
|
|
27
|
+
body = void 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (mockConfig?.clientPackagePath) {
|
|
31
|
+
const { package: pkg, generator, cursorManager, pageManager } = getClientPackageData(
|
|
32
|
+
mockConfig.clientPackagePath,
|
|
33
|
+
mockConfig.clientPackageConfig,
|
|
34
|
+
mockConfig.pagination,
|
|
35
|
+
mockConfig.cursor
|
|
36
|
+
);
|
|
37
|
+
const backwardParam = mockConfig?.cursor?.backwardParam || "isBackward";
|
|
38
|
+
const result = handleClientPackageRequest(
|
|
39
|
+
pkg,
|
|
40
|
+
generator,
|
|
41
|
+
cursorManager,
|
|
42
|
+
pageManager,
|
|
43
|
+
path,
|
|
44
|
+
event.method,
|
|
45
|
+
query,
|
|
46
|
+
backwardParam
|
|
47
|
+
);
|
|
48
|
+
if (result.statusCode) {
|
|
49
|
+
event.node.res.statusCode = result.statusCode;
|
|
50
|
+
}
|
|
51
|
+
return result.body;
|
|
52
|
+
}
|
|
53
|
+
if (mockConfig?.openapiPath) {
|
|
54
|
+
cacheManager.specMode.backwardParam = mockConfig?.cursor?.backwardParam || "isBackward";
|
|
55
|
+
const backend = await getOpenAPIBackend(mockConfig.openapiPath);
|
|
56
|
+
const headers = {};
|
|
57
|
+
const rawHeaders = event.headers;
|
|
58
|
+
if (rawHeaders) {
|
|
59
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
60
|
+
if (value) headers[key] = String(value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const result = await backend.handleRequest({
|
|
64
|
+
method: event.method,
|
|
65
|
+
path,
|
|
66
|
+
query,
|
|
67
|
+
body,
|
|
68
|
+
headers
|
|
69
|
+
});
|
|
70
|
+
if (result?.statusCode) {
|
|
71
|
+
event.node.res.statusCode = result.statusCode;
|
|
72
|
+
}
|
|
73
|
+
return result?.body ?? result;
|
|
74
|
+
}
|
|
75
|
+
throw createError({
|
|
76
|
+
statusCode: 500,
|
|
77
|
+
message: "OpenAPI configuration not found. Set openapi path or client package."
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type OpenAPISchema } from '../../utils/mock/providers/index.js';
|
|
2
|
+
import { type OpenAPISpec } from '../../utils/cache-manager.js';
|
|
3
|
+
/**
|
|
4
|
+
* OpenAPI 스펙 파일 로드
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadOpenAPISpec(specPath: string): OpenAPISpec;
|
|
7
|
+
/**
|
|
8
|
+
* 스키마 참조 해결
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveSchemaRef(schema: OpenAPISchema, schemas?: Record<string, Record<string, unknown>>): OpenAPISchema;
|
|
11
|
+
/**
|
|
12
|
+
* OpenAPI Backend 인스턴스 가져오기 (캐싱 포함)
|
|
13
|
+
*/
|
|
14
|
+
export declare function getOpenAPIBackend(specPath: string): Promise<any>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import {
|
|
4
|
+
generateMockFromSchema,
|
|
5
|
+
hashString
|
|
6
|
+
} from "../../utils/mock/index.js";
|
|
7
|
+
import {
|
|
8
|
+
OpenAPIItemProvider,
|
|
9
|
+
analyzePaginationSchema
|
|
10
|
+
} from "../../utils/mock/providers/index.js";
|
|
11
|
+
import { cacheManager } from "../../utils/cache-manager.js";
|
|
12
|
+
import { getOrCreateCursorManager, getOrCreatePageManager } from "../../utils/pagination-factory.js";
|
|
13
|
+
export function loadOpenAPISpec(specPath) {
|
|
14
|
+
const content = readFileSync(specPath, "utf-8");
|
|
15
|
+
if (specPath.endsWith(".yaml") || specPath.endsWith(".yml")) {
|
|
16
|
+
return yaml.load(content);
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
}
|
|
20
|
+
export function resolveSchemaRef(schema, schemas) {
|
|
21
|
+
if (!schema.$ref || !schemas) return schema;
|
|
22
|
+
const schemaName = schema.$ref.split("/").pop();
|
|
23
|
+
const resolved = schemaName ? schemas[schemaName] : void 0;
|
|
24
|
+
return resolved ?? schema;
|
|
25
|
+
}
|
|
26
|
+
export async function getOpenAPIBackend(specPath) {
|
|
27
|
+
const cache = cacheManager.specMode;
|
|
28
|
+
if (cache.apiInstance && cache.specPath === specPath) {
|
|
29
|
+
return cache.apiInstance;
|
|
30
|
+
}
|
|
31
|
+
const { OpenAPIBackend } = await import("openapi-backend");
|
|
32
|
+
const definition = loadOpenAPISpec(specPath);
|
|
33
|
+
cache.spec = definition;
|
|
34
|
+
const apiInstance = new OpenAPIBackend({
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
definition,
|
|
37
|
+
quick: false,
|
|
38
|
+
// $ref 역참조를 위해 false로 설정
|
|
39
|
+
validate: false
|
|
40
|
+
// Mock 서버에서는 request validation 비활성화
|
|
41
|
+
});
|
|
42
|
+
apiInstance.register({
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
notFound: async (_c) => {
|
|
45
|
+
return {
|
|
46
|
+
statusCode: 404,
|
|
47
|
+
body: { error: "Not found", message: "No matching operation found" }
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
notImplemented: async (c) => {
|
|
52
|
+
return handleNotImplemented(c, cache);
|
|
53
|
+
},
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
validationFail: async (c) => {
|
|
56
|
+
return {
|
|
57
|
+
statusCode: 400,
|
|
58
|
+
body: {
|
|
59
|
+
error: "Validation failed",
|
|
60
|
+
details: c.validation?.errors || []
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
await apiInstance.init();
|
|
66
|
+
cache.apiInstance = apiInstance;
|
|
67
|
+
cache.specPath = specPath;
|
|
68
|
+
return apiInstance;
|
|
69
|
+
}
|
|
70
|
+
async function handleNotImplemented(c, cache) {
|
|
71
|
+
const operationId = c.operation?.operationId || "unknown";
|
|
72
|
+
const responses = c.operation?.responses;
|
|
73
|
+
if (responses?.["204"]) {
|
|
74
|
+
return {
|
|
75
|
+
statusCode: 204,
|
|
76
|
+
body: null,
|
|
77
|
+
meta: { operationId }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const successResponse = responses?.["200"] || responses?.["201"] || Object.values(responses || {})[0];
|
|
81
|
+
const content = successResponse?.content;
|
|
82
|
+
const jsonContent = content?.["application/json"];
|
|
83
|
+
let mockData = null;
|
|
84
|
+
if (jsonContent?.example) {
|
|
85
|
+
mockData = jsonContent.example;
|
|
86
|
+
} else if (jsonContent?.schema) {
|
|
87
|
+
const schema = jsonContent.schema;
|
|
88
|
+
const seed = `${operationId}-${JSON.stringify(c.request?.params || {})}`;
|
|
89
|
+
const apiSchemas = c.api?.document?.components?.schemas;
|
|
90
|
+
const schemaContext = {
|
|
91
|
+
schemas: apiSchemas || cache.spec?.components?.schemas,
|
|
92
|
+
maxDepth: 10
|
|
93
|
+
};
|
|
94
|
+
const resolvedSchema = resolveSchemaRef(schema, schemaContext.schemas);
|
|
95
|
+
const paginationInfo = analyzePaginationSchema(resolvedSchema);
|
|
96
|
+
if (paginationInfo) {
|
|
97
|
+
mockData = handlePaginationResponse(c, cache, paginationInfo, seed, schemaContext, operationId);
|
|
98
|
+
} else {
|
|
99
|
+
const numericSeed = hashString(seed);
|
|
100
|
+
mockData = generateMockFromSchema(
|
|
101
|
+
schema,
|
|
102
|
+
numericSeed,
|
|
103
|
+
schemaContext
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
statusCode: 200,
|
|
109
|
+
body: mockData,
|
|
110
|
+
meta: { operationId }
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function handlePaginationResponse(c, cache, paginationInfo, seed, schemaContext, operationId) {
|
|
114
|
+
if (!paginationInfo) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
const query = c.request?.query || {};
|
|
118
|
+
const page = Number(query.page) || 1;
|
|
119
|
+
const limit = Number(query.limit) || Number(query.size) || 20;
|
|
120
|
+
const cursor = query.cursor;
|
|
121
|
+
const isBackward = query[cache.backwardParam] === "true" || query[cache.backwardParam] === "1";
|
|
122
|
+
const total = 100;
|
|
123
|
+
const itemProvider = new OpenAPIItemProvider(paginationInfo.itemSchema, {
|
|
124
|
+
modelName: operationId,
|
|
125
|
+
schemaContext
|
|
126
|
+
});
|
|
127
|
+
const cursorManager = getOrCreateCursorManager(itemProvider, cache);
|
|
128
|
+
const pageManager = getOrCreatePageManager(itemProvider, cache);
|
|
129
|
+
if (cursor || isBackward || paginationInfo.isCursorBased) {
|
|
130
|
+
const result = cursorManager.getCursorPageWithProvider(itemProvider, {
|
|
131
|
+
cursor,
|
|
132
|
+
limit,
|
|
133
|
+
total,
|
|
134
|
+
seed,
|
|
135
|
+
isBackward
|
|
136
|
+
});
|
|
137
|
+
return buildCursorResponse(result, paginationInfo, total);
|
|
138
|
+
} else {
|
|
139
|
+
const result = pageManager.getPagedResponseWithProvider(itemProvider, {
|
|
140
|
+
page,
|
|
141
|
+
limit,
|
|
142
|
+
total,
|
|
143
|
+
seed
|
|
144
|
+
});
|
|
145
|
+
return buildPageResponse(result, paginationInfo);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function buildCursorResponse(result, paginationInfo, total) {
|
|
149
|
+
const responseData = {
|
|
150
|
+
[paginationInfo.itemsFieldName]: result.items
|
|
151
|
+
};
|
|
152
|
+
if (paginationInfo.metaFields.includes("nextCursor")) {
|
|
153
|
+
responseData.nextCursor = result.nextCursor ?? null;
|
|
154
|
+
}
|
|
155
|
+
if (paginationInfo.metaFields.includes("prevCursor")) {
|
|
156
|
+
responseData.prevCursor = result.prevCursor ?? null;
|
|
157
|
+
}
|
|
158
|
+
if (paginationInfo.metaFields.includes("hasMore")) {
|
|
159
|
+
responseData.hasMore = result.hasMore;
|
|
160
|
+
}
|
|
161
|
+
if (paginationInfo.metaFields.includes("hasPrev")) {
|
|
162
|
+
responseData.hasPrev = result.hasPrev ?? false;
|
|
163
|
+
}
|
|
164
|
+
if (paginationInfo.metaFields.includes("total")) {
|
|
165
|
+
responseData.total = total;
|
|
166
|
+
}
|
|
167
|
+
if (paginationInfo.metaFields.includes("cursor")) {
|
|
168
|
+
responseData.cursor = result.nextCursor ?? null;
|
|
169
|
+
}
|
|
170
|
+
return responseData;
|
|
171
|
+
}
|
|
172
|
+
function buildPageResponse(result, paginationInfo) {
|
|
173
|
+
const responseData = {
|
|
174
|
+
[paginationInfo.itemsFieldName]: result.items
|
|
175
|
+
};
|
|
176
|
+
if (paginationInfo.metaFields.includes("page")) {
|
|
177
|
+
responseData.page = result.page;
|
|
178
|
+
}
|
|
179
|
+
if (paginationInfo.metaFields.includes("totalPages")) {
|
|
180
|
+
responseData.totalPages = result.totalPages;
|
|
181
|
+
}
|
|
182
|
+
if (paginationInfo.metaFields.includes("total")) {
|
|
183
|
+
responseData.total = result.total;
|
|
184
|
+
}
|
|
185
|
+
if (paginationInfo.metaFields.includes("totalItems")) {
|
|
186
|
+
responseData.totalItems = result.total;
|
|
187
|
+
}
|
|
188
|
+
if (paginationInfo.metaFields.includes("limit")) {
|
|
189
|
+
responseData.limit = result.limit;
|
|
190
|
+
}
|
|
191
|
+
if (paginationInfo.metaFields.includes("size")) {
|
|
192
|
+
responseData.size = result.limit;
|
|
193
|
+
}
|
|
194
|
+
if (paginationInfo.metaFields.includes("offset")) {
|
|
195
|
+
responseData.offset = (result.page - 1) * result.limit;
|
|
196
|
+
}
|
|
197
|
+
return responseData;
|
|
198
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
|
-
import { clearOpenApiCache } from "./openapi.js";
|
|
2
|
+
import { clearOpenApiCache } from "./openapi/index.js";
|
|
3
3
|
import { clearProtoCache } from "./rpc.js";
|
|
4
4
|
import { clearSchemaCache } from "./schema.js";
|
|
5
5
|
import { clearClientPackageCache } from "../utils/client-parser.js";
|