mock-fried 1.1.0 → 1.1.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.json +1 -1
- package/dist/runtime/components/ApiExplorer.vue +2 -0
- package/dist/runtime/components/EndpointCard.vue +1 -0
- package/dist/runtime/components/ResponseViewer.vue +1 -0
- package/dist/runtime/components/RpcMethodCard.vue +1 -0
- package/dist/runtime/server/handlers/rpc.js +44 -13
- package/dist/runtime/server/utils/client-parser.js +41 -25
- package/dist/runtime/server/utils/mock/providers/proto-item-provider.js +3 -0
- package/package.json +2 -2
package/dist/module.json
CHANGED
|
@@ -88,6 +88,8 @@
|
|
|
88
88
|
</template>
|
|
89
89
|
|
|
90
90
|
<script setup>
|
|
91
|
+
import { ref, onMounted } from "vue";
|
|
92
|
+
import { useNuxtApp } from "#imports";
|
|
91
93
|
import EndpointCard from "./EndpointCard.vue";
|
|
92
94
|
import RpcMethodCard from "./RpcMethodCard.vue";
|
|
93
95
|
import ResponseViewer from "./ResponseViewer.vue";
|
|
@@ -86,6 +86,7 @@ export function clearProtoCache() {
|
|
|
86
86
|
protoPageManager = null;
|
|
87
87
|
}
|
|
88
88
|
const PROTO_TYPE_MAP = {
|
|
89
|
+
// 숫자 키 (legacy/fallback)
|
|
89
90
|
1: "double",
|
|
90
91
|
2: "float",
|
|
91
92
|
3: "int64",
|
|
@@ -97,25 +98,57 @@ const PROTO_TYPE_MAP = {
|
|
|
97
98
|
9: "string",
|
|
98
99
|
10: "group",
|
|
99
100
|
11: "message",
|
|
100
|
-
// 중첩 메시지
|
|
101
101
|
12: "bytes",
|
|
102
102
|
13: "uint32",
|
|
103
103
|
14: "enum",
|
|
104
104
|
15: "sfixed32",
|
|
105
105
|
16: "sfixed64",
|
|
106
106
|
17: "sint32",
|
|
107
|
-
18: "sint64"
|
|
107
|
+
18: "sint64",
|
|
108
|
+
// 문자열 키 (proto-loader 실제 반환값)
|
|
109
|
+
TYPE_DOUBLE: "double",
|
|
110
|
+
TYPE_FLOAT: "float",
|
|
111
|
+
TYPE_INT64: "int64",
|
|
112
|
+
TYPE_UINT64: "uint64",
|
|
113
|
+
TYPE_INT32: "int32",
|
|
114
|
+
TYPE_FIXED64: "fixed64",
|
|
115
|
+
TYPE_FIXED32: "fixed32",
|
|
116
|
+
TYPE_BOOL: "bool",
|
|
117
|
+
TYPE_STRING: "string",
|
|
118
|
+
TYPE_GROUP: "group",
|
|
119
|
+
TYPE_MESSAGE: "message",
|
|
120
|
+
TYPE_BYTES: "bytes",
|
|
121
|
+
TYPE_UINT32: "uint32",
|
|
122
|
+
TYPE_ENUM: "enum",
|
|
123
|
+
TYPE_SFIXED32: "sfixed32",
|
|
124
|
+
TYPE_SFIXED64: "sfixed64",
|
|
125
|
+
TYPE_SINT32: "sint32",
|
|
126
|
+
TYPE_SINT64: "sint64"
|
|
108
127
|
};
|
|
109
128
|
function findTypeInPackageDefinition(typeName, packageDefinition) {
|
|
110
129
|
const cleanName = typeName.startsWith(".") ? typeName.slice(1) : typeName;
|
|
111
|
-
const
|
|
112
|
-
|
|
130
|
+
const directMatch = packageDefinition[cleanName];
|
|
131
|
+
if (directMatch?.type) {
|
|
132
|
+
return directMatch.type;
|
|
133
|
+
}
|
|
134
|
+
if (!cleanName.includes(".")) {
|
|
135
|
+
for (const key of Object.keys(packageDefinition)) {
|
|
136
|
+
if (key.endsWith(`.${cleanName}`)) {
|
|
137
|
+
const matchedType = packageDefinition[key];
|
|
138
|
+
if (matchedType?.type) {
|
|
139
|
+
return matchedType.type;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
113
145
|
}
|
|
114
146
|
function convertFieldDescriptor(fieldDesc, protoType, context) {
|
|
115
147
|
const typeName = PROTO_TYPE_MAP[fieldDesc.type] || "string";
|
|
148
|
+
const isRepeated = fieldDesc.label === 3 || fieldDesc.label === "LABEL_REPEATED";
|
|
116
149
|
const result = {
|
|
117
150
|
type: typeName,
|
|
118
|
-
rule:
|
|
151
|
+
rule: isRepeated ? "repeated" : void 0
|
|
119
152
|
};
|
|
120
153
|
if (fieldDesc.typeName) {
|
|
121
154
|
const shortName = fieldDesc.typeName.split(".").pop() || fieldDesc.typeName;
|
|
@@ -123,10 +156,6 @@ function convertFieldDescriptor(fieldDesc, protoType, context) {
|
|
|
123
156
|
if (context.depth >= context.maxDepth) {
|
|
124
157
|
return result;
|
|
125
158
|
}
|
|
126
|
-
if (context.visitedTypes.has(fieldDesc.typeName)) {
|
|
127
|
-
result.resolvedType = { fields: {}, name: shortName };
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
159
|
const enumType = protoType.enumType?.find(
|
|
131
160
|
(e) => e.name === shortName
|
|
132
161
|
);
|
|
@@ -185,9 +214,10 @@ function resolveProtoType(protoType, context) {
|
|
|
185
214
|
};
|
|
186
215
|
}
|
|
187
216
|
function getResponseTypeInfo(methodDef, packageDefinition) {
|
|
188
|
-
const
|
|
217
|
+
const methodDefWithResponseType = methodDef;
|
|
218
|
+
const responseType = methodDefWithResponseType.responseType;
|
|
189
219
|
if (!responseType?.type?.field) {
|
|
190
|
-
return {};
|
|
220
|
+
return { name: "unknown" };
|
|
191
221
|
}
|
|
192
222
|
const context = {
|
|
193
223
|
packageDefinition,
|
|
@@ -254,7 +284,8 @@ export default defineEventHandler(async (event) => {
|
|
|
254
284
|
requestBody = {};
|
|
255
285
|
}
|
|
256
286
|
const responseTypeInfo = getResponseTypeInfo(methodDef, cache.packageDefinition);
|
|
257
|
-
const
|
|
287
|
+
const seedNum = deriveSeedFromRequest(requestBody);
|
|
288
|
+
const seed = String(seedNum);
|
|
258
289
|
const paginationInfo = analyzeProtoPagination(responseTypeInfo);
|
|
259
290
|
let mockResponse;
|
|
260
291
|
if (paginationInfo) {
|
|
@@ -323,7 +354,7 @@ export default defineEventHandler(async (event) => {
|
|
|
323
354
|
mockResponse = responseData;
|
|
324
355
|
}
|
|
325
356
|
} else {
|
|
326
|
-
mockResponse = generateMockMessage(responseTypeInfo,
|
|
357
|
+
mockResponse = generateMockMessage(responseTypeInfo, seedNum);
|
|
327
358
|
}
|
|
328
359
|
return {
|
|
329
360
|
success: true,
|
|
@@ -12,19 +12,22 @@ function parseApiFile(filePath, fileName) {
|
|
|
12
12
|
while ((match = methodBodyRegex.exec(content)) !== null) {
|
|
13
13
|
const methodName = match[1];
|
|
14
14
|
const body = match[2];
|
|
15
|
-
|
|
15
|
+
if (methodName && body) {
|
|
16
|
+
methodBodies.set(methodName, body);
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
rawMethodRegex.lastIndex = 0;
|
|
18
20
|
while ((match = rawMethodRegex.exec(content)) !== null) {
|
|
19
21
|
const jsdocContent = match[1];
|
|
20
22
|
const operationId = match[2];
|
|
21
23
|
const responseType = match[3];
|
|
24
|
+
if (!jsdocContent || !operationId || !responseType) continue;
|
|
22
25
|
const jsdocLines = jsdocContent.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("@"));
|
|
23
26
|
const summary = jsdocLines[jsdocLines.length - 1] || jsdocLines[0] || operationId;
|
|
24
27
|
const body = methodBodies.get(operationId);
|
|
25
28
|
if (!body) continue;
|
|
26
29
|
const pathMatch = body.match(/let\s+urlPath\s*=\s*`([^`]+)`/);
|
|
27
|
-
if (!pathMatch) continue;
|
|
30
|
+
if (!pathMatch?.[1]) continue;
|
|
28
31
|
let path = pathMatch[1];
|
|
29
32
|
path = path.replace(/\$\{[^}]+\}/g, (match2) => {
|
|
30
33
|
const paramName = match2.match(/requestParameters(?:\.(\w+)|\["?(\w+)"?\])/)?.[1] || match2.match(/requestParameters(?:\.(\w+)|\["?(\w+)"?\])/)?.[2];
|
|
@@ -38,25 +41,31 @@ function parseApiFile(filePath, fileName) {
|
|
|
38
41
|
const pathParams = [];
|
|
39
42
|
const pathParamMatches = path.matchAll(/\{(\w+)\}/g);
|
|
40
43
|
for (const pm of pathParamMatches) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const paramName = pm[1];
|
|
45
|
+
if (paramName) {
|
|
46
|
+
pathParams.push({
|
|
47
|
+
name: paramName,
|
|
48
|
+
type: "string",
|
|
49
|
+
required: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
const queryParams = [];
|
|
48
54
|
const queryParamRegex = /if\s*\(requestParameters\[?['"]?(\w+)['"]?\]?\s*!==?\s*(?:undefined|null)\)\s*\{\s*queryParameters\[['"](\w+)['"]\]/g;
|
|
49
55
|
let qpm;
|
|
50
56
|
while ((qpm = queryParamRegex.exec(body)) !== null) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const paramName = qpm[2];
|
|
58
|
+
if (paramName) {
|
|
59
|
+
queryParams.push({
|
|
60
|
+
name: paramName,
|
|
61
|
+
type: "string",
|
|
62
|
+
required: false
|
|
63
|
+
});
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
let requestBodyType;
|
|
58
67
|
const bodyMatch = body.match(/body:\s*(\w+)ToJSON\(requestParameters\.(\w+)\)/);
|
|
59
|
-
if (bodyMatch) {
|
|
68
|
+
if (bodyMatch?.[1]) {
|
|
60
69
|
requestBodyType = bodyMatch[1];
|
|
61
70
|
}
|
|
62
71
|
endpoints.push({
|
|
@@ -79,7 +88,7 @@ function analyzeType(rawType) {
|
|
|
79
88
|
let type = rawType;
|
|
80
89
|
if (isArray) {
|
|
81
90
|
const arrayTypeMatch = rawType.match(/Array<(.+)>/) || rawType.match(/(.+)\[\]/);
|
|
82
|
-
if (arrayTypeMatch) {
|
|
91
|
+
if (arrayTypeMatch?.[1]) {
|
|
83
92
|
type = arrayTypeMatch[1].trim();
|
|
84
93
|
}
|
|
85
94
|
}
|
|
@@ -96,11 +105,13 @@ function parseModelFile(filePath, fileName) {
|
|
|
96
105
|
if (!hasInterface) {
|
|
97
106
|
const enumRegex = new RegExp(`export\\s+const\\s+(${modelName})\\s*=\\s*\\{([^}]+)\\}\\s*as\\s*const`);
|
|
98
107
|
const enumMatch = content.match(enumRegex);
|
|
99
|
-
if (enumMatch) {
|
|
108
|
+
if (enumMatch?.[2]) {
|
|
100
109
|
const enumValues = [];
|
|
101
110
|
const valueMatches = enumMatch[2].matchAll(/(\w+):\s*['"]([^'"]+)['"]/g);
|
|
102
111
|
for (const vm of valueMatches) {
|
|
103
|
-
|
|
112
|
+
if (vm[2]) {
|
|
113
|
+
enumValues.push(vm[2]);
|
|
114
|
+
}
|
|
104
115
|
}
|
|
105
116
|
if (enumValues.length > 0) {
|
|
106
117
|
return {
|
|
@@ -114,11 +125,12 @@ function parseModelFile(filePath, fileName) {
|
|
|
114
125
|
const fields = [];
|
|
115
126
|
const jsonKeyMap = /* @__PURE__ */ new Map();
|
|
116
127
|
const toJsonMatch = content.match(/export\s+function\s+\w+ToJSON[^{]*\{[\s\S]*?return\s*\{([\s\S]*?)\};?\s*\}/);
|
|
117
|
-
if (toJsonMatch) {
|
|
128
|
+
if (toJsonMatch?.[1]) {
|
|
118
129
|
const returnBody = toJsonMatch[1];
|
|
119
130
|
const fieldMatches = returnBody.matchAll(/['"](\w+)['"]\s*:\s*(?:value\.(\w+)|[^,\n]*?value\.(\w+))/g);
|
|
120
131
|
for (const fm of fieldMatches) {
|
|
121
132
|
const jsonKey = fm[1];
|
|
133
|
+
if (!jsonKey) continue;
|
|
122
134
|
const propertyName = fm[2] || fm[3];
|
|
123
135
|
if (!propertyName) continue;
|
|
124
136
|
jsonKeyMap.set(propertyName, jsonKey);
|
|
@@ -134,29 +146,31 @@ function parseModelFile(filePath, fileName) {
|
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
const fromJsonMatch = content.match(/export\s+function\s+\w+FromJSONTyped[^{]*\{[\s\S]*?return\s*\{([\s\S]*?)\};?\s*\}/);
|
|
137
|
-
if (fromJsonMatch) {
|
|
149
|
+
if (fromJsonMatch?.[1]) {
|
|
138
150
|
const returnBody = fromJsonMatch[1];
|
|
139
151
|
const dateFields = /* @__PURE__ */ new Set();
|
|
140
152
|
const dateMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?new\s+Date\(/g);
|
|
141
153
|
for (const dm of dateMatches) {
|
|
142
|
-
dateFields.add(dm[1]);
|
|
154
|
+
if (dm[1]) dateFields.add(dm[1]);
|
|
143
155
|
}
|
|
144
156
|
const arrayFields = /* @__PURE__ */ new Set();
|
|
145
157
|
const arrayMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?\(json\[['"](\w+)['"]\]\s*as\s*Array/g);
|
|
146
158
|
for (const am of arrayMatches) {
|
|
147
|
-
arrayFields.add(am[1]);
|
|
159
|
+
if (am[1]) arrayFields.add(am[1]);
|
|
148
160
|
}
|
|
149
161
|
const refTypeMap = /* @__PURE__ */ new Map();
|
|
150
162
|
const refMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?(\w+)FromJSON\(json\[/g);
|
|
151
163
|
for (const rm of refMatches) {
|
|
152
|
-
if (!rm[2].includes("Array")) {
|
|
164
|
+
if (rm[1] && rm[2] && !rm[2].includes("Array")) {
|
|
153
165
|
refTypeMap.set(rm[1], rm[2]);
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
const arrayRefMatches = returnBody.matchAll(/['"](\w+)['"]\s*:.*?\.map\((\w+)FromJSON\)/g);
|
|
157
169
|
for (const arm of arrayRefMatches) {
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
if (arm[1] && arm[2]) {
|
|
171
|
+
refTypeMap.set(arm[1], arm[2]);
|
|
172
|
+
arrayFields.add(arm[1]);
|
|
173
|
+
}
|
|
160
174
|
}
|
|
161
175
|
for (const field of fields) {
|
|
162
176
|
if (dateFields.has(field.name)) {
|
|
@@ -173,14 +187,16 @@ function parseModelFile(filePath, fileName) {
|
|
|
173
187
|
}
|
|
174
188
|
}
|
|
175
189
|
const interfaceMatch = content.match(/export\s+interface\s+(\w+)(?:\s+extends\s+[\w,\s]+)?\s*\{([\s\S]*?)\n\}/);
|
|
176
|
-
if (interfaceMatch) {
|
|
190
|
+
if (interfaceMatch?.[2]) {
|
|
177
191
|
const interfaceBody = interfaceMatch[2];
|
|
178
192
|
const simpleFieldRegex = /^\s+(\w+)(\?)?:\s*([^;]+);/gm;
|
|
179
193
|
let sfm;
|
|
180
194
|
while ((sfm = simpleFieldRegex.exec(interfaceBody)) !== null) {
|
|
181
195
|
const name = sfm[1];
|
|
196
|
+
const rawTypeStr = sfm[3];
|
|
197
|
+
if (!name || !rawTypeStr) continue;
|
|
182
198
|
const optional = sfm[2] === "?";
|
|
183
|
-
const rawType =
|
|
199
|
+
const rawType = rawTypeStr.trim();
|
|
184
200
|
const { type, isArray, refType } = analyzeType(rawType);
|
|
185
201
|
const existingField = fields.find((f) => f.name === name);
|
|
186
202
|
if (existingField) {
|
|
@@ -90,6 +90,9 @@ export function analyzeProtoPagination(messageType) {
|
|
|
90
90
|
const foundCursorFields = cursorFieldPatterns.filter((p) => fieldNames.includes(p.toLowerCase()));
|
|
91
91
|
const isPageBased = foundPageFields.length >= 2;
|
|
92
92
|
const isCursorBased = foundCursorFields.length >= 1;
|
|
93
|
+
if (!isPageBased && !isCursorBased) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
93
96
|
return {
|
|
94
97
|
itemsFieldName,
|
|
95
98
|
itemMessageType,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mock-fried",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Nuxt3 Mock API Module - OpenAPI & Protobuf RPC Mock Server",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"test:e2e:openapi-client": "vitest run test/e2e/playground-openapi-client.e2e.test.ts",
|
|
60
60
|
"test:e2e:proto": "vitest run test/e2e/playground-proto.e2e.test.ts test/e2e/playground-proto-advanced.e2e.test.ts",
|
|
61
61
|
"test:e2e:proto-advanced": "vitest run test/e2e/playground-proto-advanced.e2e.test.ts",
|
|
62
|
-
"test:types": "vue-tsc --noEmit
|
|
62
|
+
"test:types": "vue-tsc --noEmit"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@grpc/grpc-js": "^1.12.0",
|