swaggie 1.9.0-dev.1 → 2.0.0
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/README.md +117 -31
- package/dist/browser.js +24 -5
- package/dist/cli.js +54 -7
- package/dist/gen/genMocks.js +453 -0
- package/dist/gen/genOperations.js +112 -20
- package/dist/gen/genTypes.js +6 -5
- package/dist/gen/header.js +0 -1
- package/dist/gen/index.js +14 -1
- package/dist/generated/bundledTemplates.js +17 -19
- package/dist/index.js +17 -1
- package/dist/swagger/operations.js +16 -7
- package/dist/swagger/typesExtractor.js +12 -11
- package/dist/types.d.ts +55 -5
- package/dist/utils/documentLoader.js +1 -3
- package/dist/utils/fileUtils.js +22 -0
- package/dist/utils/refResolver.js +9 -21
- package/dist/utils/templateEngine.js +18 -0
- package/dist/utils/templateManager.js +123 -14
- package/dist/utils/templateValidator.js +127 -0
- package/dist/utils/utils.js +19 -13
- package/package.json +5 -4
- package/templates/axios/operation.ejs +25 -21
- package/templates/fetch/operation.ejs +4 -0
- package/templates/ng1/operation.ejs +11 -3
- package/templates/ng2/baseClient.ejs +9 -49
- package/templates/ng2/client.ejs +3 -7
- package/templates/ng2/operation.ejs +34 -17
- package/templates/swr/baseClient.ejs +7 -0
- package/templates/swr/client.ejs +63 -0
- package/templates/swr/swrMutationOperation.ejs +32 -0
- package/templates/swr/swrOperation.ejs +18 -0
- package/templates/tsq/baseClient.ejs +1 -0
- package/templates/tsq/client.ejs +67 -0
- package/templates/tsq/mutationOperation.ejs +31 -0
- package/templates/tsq/queryOperation.ejs +19 -0
- package/templates/xior/operation.ejs +25 -21
- package/templates/swr-axios/barrel.ejs +0 -58
- package/templates/swr-axios/baseClient.ejs +0 -20
- package/templates/swr-axios/client.ejs +0 -21
- package/templates/swr-axios/operation.ejs +0 -40
- package/templates/swr-axios/swrOperation.ejs +0 -50
- package/templates/tsq-xior/barrel.ejs +0 -0
- package/templates/tsq-xior/baseClient.ejs +0 -14
- package/templates/tsq-xior/client.ejs +0 -22
- package/templates/tsq-xior/operation.ejs +0 -40
- package/templates/tsq-xior/queryOperation.ejs +0 -30
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _case = require('case');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _swagger = require('../swagger');
|
|
5
|
+
var _utils = require('../utils/utils');
|
|
6
|
+
var _templateValidator = require('../utils/templateValidator');
|
|
7
|
+
var _genOperations = require('./genOperations');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const MOCK_FILE_HEADER = `\
|
|
12
|
+
/* tslint:disable */
|
|
13
|
+
/* eslint-disable */
|
|
14
|
+
//----------------------
|
|
15
|
+
// <auto-generated>
|
|
16
|
+
// Generated using Swaggie (https://github.com/yhnavein/swaggie)
|
|
17
|
+
// Please avoid doing any manual changes in this file
|
|
18
|
+
// </auto-generated>
|
|
19
|
+
//----------------------
|
|
20
|
+
// biome-ignore-all lint: auto-generated code
|
|
21
|
+
// deno-lint-ignore-file
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generates a companion mock/stub file for a generated API client.
|
|
26
|
+
*
|
|
27
|
+
* The mock file exports:
|
|
28
|
+
* - `createClientMocks()` — spyOn stubs for every `*Client` method (all templates)
|
|
29
|
+
* - `ClientMocks` type alias
|
|
30
|
+
* - `createApiHookMocks()` — spyOn stubs for every hook with ergonomic shorthand
|
|
31
|
+
* helpers (`mockSWR`, `mockQuery`, etc.) — L2 templates only (swr/tsq)
|
|
32
|
+
* - `ApiHookMocks` type alias — L2 templates only
|
|
33
|
+
*
|
|
34
|
+
* @param spec The OpenAPI document
|
|
35
|
+
* @param options Generator options (must have `testingFramework` set)
|
|
36
|
+
* @param relativeApiImport Relative import path from the mock file to the real client file
|
|
37
|
+
*/
|
|
38
|
+
function generateMocks(
|
|
39
|
+
spec,
|
|
40
|
+
options,
|
|
41
|
+
relativeApiImport
|
|
42
|
+
) {
|
|
43
|
+
const fw = options.testingFramework;
|
|
44
|
+
const template = options.template;
|
|
45
|
+
|
|
46
|
+
if (template === 'ng1') {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Mock generation is not supported for the "ng1" template. ' +
|
|
49
|
+
'Use "ng2", "axios", "fetch", "xior", "swr", or "tsq" instead.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isNg2 = template === 'ng2';
|
|
54
|
+
const l2 = getL2Name(template);
|
|
55
|
+
|
|
56
|
+
const operations = _swagger.getOperations.call(void 0, spec);
|
|
57
|
+
const groups = _utils.groupOperationsByGroupName.call(void 0, operations);
|
|
58
|
+
const groupNames = Object.keys(groups);
|
|
59
|
+
const servicePrefix = options.servicePrefix;
|
|
60
|
+
|
|
61
|
+
// Collect prepared operations per group up-front so we iterate once
|
|
62
|
+
const preparedGroups = groupNames
|
|
63
|
+
.map((name) => {
|
|
64
|
+
const fullName = servicePrefix + name;
|
|
65
|
+
const camelName = _case.camel.call(void 0, fullName);
|
|
66
|
+
const ops = _genOperations.prepareOperations.call(void 0, groups[name], options, spec.components);
|
|
67
|
+
return { name, camelName, ops };
|
|
68
|
+
})
|
|
69
|
+
.filter((g) => g.ops.length > 0);
|
|
70
|
+
|
|
71
|
+
let result = MOCK_FILE_HEADER;
|
|
72
|
+
|
|
73
|
+
// Framework import
|
|
74
|
+
result += buildFrameworkImport(fw);
|
|
75
|
+
if (l2 === 'swr') {
|
|
76
|
+
result += buildSwrTypeImport();
|
|
77
|
+
}
|
|
78
|
+
result += '\n';
|
|
79
|
+
|
|
80
|
+
if (isNg2) {
|
|
81
|
+
// ng2: type-only import (no Angular DI triggered), MockedService<T> utility, vi.fn()-based stubs
|
|
82
|
+
result += buildNg2TypeImport(preparedGroups, relativeApiImport);
|
|
83
|
+
result += '\n';
|
|
84
|
+
result += buildMockedServiceType();
|
|
85
|
+
result += '\n';
|
|
86
|
+
result += buildNg2CreateClientMocks(preparedGroups, fw);
|
|
87
|
+
result += '\n';
|
|
88
|
+
result += 'export type ClientMocks = ReturnType<typeof createClientMocks>;\n';
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Standard path (axios / fetch / xior / swr / tsq) ───────────────────────
|
|
93
|
+
|
|
94
|
+
// Real client import — needed for spyOn targets
|
|
95
|
+
result += `import * as realApi from '${relativeApiImport}';\n`;
|
|
96
|
+
result += '\n';
|
|
97
|
+
|
|
98
|
+
// SWR/TSQ helper functions and default return values (L2 only)
|
|
99
|
+
if (l2 === 'swr') {
|
|
100
|
+
result += buildSwrDefaults(fw);
|
|
101
|
+
result += '\n';
|
|
102
|
+
result += buildSwrHelpers(fw);
|
|
103
|
+
result += '\n';
|
|
104
|
+
} else if (l2 === 'tsq') {
|
|
105
|
+
result += buildTsqDefaults(fw);
|
|
106
|
+
result += '\n';
|
|
107
|
+
result += buildTsqHelpers(fw);
|
|
108
|
+
result += '\n';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── createClientMocks ───────────────────────────────────────────────────────
|
|
112
|
+
result += buildCreateClientMocks(preparedGroups, fw);
|
|
113
|
+
result += '\n';
|
|
114
|
+
result += 'export type ClientMocks = ReturnType<typeof createClientMocks>;\n';
|
|
115
|
+
|
|
116
|
+
// ── createApiHookMocks (L2 only) ────────────────────────────────────────────
|
|
117
|
+
if (l2 === 'swr') {
|
|
118
|
+
result += '\n';
|
|
119
|
+
result += buildCreateSwrHookMocks(preparedGroups, fw);
|
|
120
|
+
result += '\n';
|
|
121
|
+
result += 'export type ApiHookMocks = ReturnType<typeof createApiHookMocks>;\n';
|
|
122
|
+
} else if (l2 === 'tsq') {
|
|
123
|
+
result += '\n';
|
|
124
|
+
result += buildCreateTsqHookMocks(preparedGroups, fw);
|
|
125
|
+
result += '\n';
|
|
126
|
+
result += 'export type ApiHookMocks = ReturnType<typeof createApiHookMocks>;\n';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
} exports.default = generateMocks;
|
|
131
|
+
|
|
132
|
+
function getL2Name(template) {
|
|
133
|
+
if (!Array.isArray(template)) return null;
|
|
134
|
+
const [l2] = template;
|
|
135
|
+
return _templateValidator.isL2Template.call(void 0, l2) ? (l2 ) : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Derives the Angular service class name from a camelCase group name.
|
|
140
|
+
* e.g. "pet" → "PetService", "petstorePet" → "PetstorePetService"
|
|
141
|
+
*/
|
|
142
|
+
function toServiceClassName(camelName) {
|
|
143
|
+
return camelName.charAt(0).toUpperCase() + camelName.slice(1) + 'Service';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildNg2TypeImport(groups, relativeApiImport) {
|
|
147
|
+
const names = groups.map((g) => toServiceClassName(g.camelName)).join(', ');
|
|
148
|
+
return `import type { ${names} } from '${relativeApiImport}';\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildMockedServiceType() {
|
|
152
|
+
return `\
|
|
153
|
+
export type MockedService<T> = {
|
|
154
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => R : T[K];
|
|
155
|
+
};
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildNg2CreateClientMocks(
|
|
160
|
+
groups,
|
|
161
|
+
fw
|
|
162
|
+
) {
|
|
163
|
+
const lines = ['export function createClientMocks() {', ' return {'];
|
|
164
|
+
|
|
165
|
+
for (const { camelName, ops } of groups) {
|
|
166
|
+
const serviceClass = toServiceClassName(camelName);
|
|
167
|
+
lines.push(` ${camelName}Client: {`);
|
|
168
|
+
for (const op of ops) {
|
|
169
|
+
if (fw === 'vitest') {
|
|
170
|
+
lines.push(` ${op.name}: vi.fn<typeof ${serviceClass}.prototype.${op.name}>(),`);
|
|
171
|
+
} else {
|
|
172
|
+
// Jest: bare jest.fn() — no typed fn<F> overload available
|
|
173
|
+
lines.push(` ${op.name}: jest.fn(),`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (fw === 'vitest') {
|
|
177
|
+
lines.push(` } satisfies MockedService<${serviceClass}>,`);
|
|
178
|
+
} else {
|
|
179
|
+
lines.push(' },');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lines.push(' };');
|
|
184
|
+
lines.push('}');
|
|
185
|
+
return lines.join('\n') + '\n';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildFrameworkImport(fw) {
|
|
189
|
+
return fw === 'vitest'
|
|
190
|
+
? "import { vi } from 'vitest';\n"
|
|
191
|
+
: "import { jest } from '@jest/globals';\n";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildSwrTypeImport() {
|
|
195
|
+
return "import type { KeyedMutator } from 'swr';\n";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Returns the spy function expression for the given framework. */
|
|
199
|
+
function spyFn(fw) {
|
|
200
|
+
return fw === 'vitest' ? 'vi.spyOn' : 'jest.spyOn';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Returns a new fn() call for the given framework. */
|
|
204
|
+
function fn(fw) {
|
|
205
|
+
return fw === 'vitest' ? 'vi.fn()' : 'jest.fn()';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Returns the framework object identifier (vi / jest). */
|
|
209
|
+
function fwRef(fw) {
|
|
210
|
+
return fw === 'vitest' ? 'vi' : 'jest';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildSwrDefaults(fw) {
|
|
214
|
+
const ref = fwRef(fw);
|
|
215
|
+
return `\
|
|
216
|
+
const defaultSWRReturn = {
|
|
217
|
+
data: undefined,
|
|
218
|
+
isLoading: false,
|
|
219
|
+
error: undefined,
|
|
220
|
+
mutate: ${fn(fw)} as unknown as KeyedMutator<any>,
|
|
221
|
+
isValidating: false,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const defaultSWRMutationReturn = {
|
|
225
|
+
trigger: ${ref}.fn(() => Promise.resolve(undefined)),
|
|
226
|
+
isMutating: false,
|
|
227
|
+
error: undefined,
|
|
228
|
+
data: undefined,
|
|
229
|
+
reset: ${fn(fw)},
|
|
230
|
+
};
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildSwrHelpers(fw) {
|
|
235
|
+
const ref = fwRef(fw);
|
|
236
|
+
return `\
|
|
237
|
+
interface MockSWRReturn {
|
|
238
|
+
/** The data to return from the mock */
|
|
239
|
+
data: unknown;
|
|
240
|
+
/** Whether to return a loading state (default: false) */
|
|
241
|
+
isLoading?: boolean;
|
|
242
|
+
/** Whether to return an error (default: undefined) */
|
|
243
|
+
error?: Error | undefined | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface MockSWRMutationReturn {
|
|
247
|
+
/** The data to return from the mock */
|
|
248
|
+
data: unknown;
|
|
249
|
+
/** Whether to return a mutating state (default: false) */
|
|
250
|
+
isMutating?: boolean;
|
|
251
|
+
/** Whether to return an error (default: undefined) */
|
|
252
|
+
error?: Error | undefined | null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Augments a spy with a \`mockSWR\` shorthand for useSWR query hooks. */
|
|
256
|
+
function withMockSWR<Fn extends (...args: never[]) => unknown>(spy: ${ref}.SpiedFunction<Fn>) {
|
|
257
|
+
return Object.assign(spy, {
|
|
258
|
+
mockSWR({ data, isLoading, error }: MockSWRReturn) {
|
|
259
|
+
spy.mockReturnValue({
|
|
260
|
+
...defaultSWRReturn,
|
|
261
|
+
data,
|
|
262
|
+
isLoading: isLoading ?? false,
|
|
263
|
+
error: error ?? undefined,
|
|
264
|
+
} as ReturnType<Fn>);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Augments a spy with a \`mockSWRMutation\` shorthand for useSWRMutation hooks. */
|
|
270
|
+
function withMockSWRMutation<Fn extends (...args: never[]) => unknown>(spy: ${ref}.SpiedFunction<Fn>) {
|
|
271
|
+
return Object.assign(spy, {
|
|
272
|
+
mockSWRMutation({
|
|
273
|
+
data,
|
|
274
|
+
isMutating,
|
|
275
|
+
error,
|
|
276
|
+
}: MockSWRMutationReturn) {
|
|
277
|
+
spy.mockReturnValue({
|
|
278
|
+
...defaultSWRMutationReturn,
|
|
279
|
+
trigger: ${ref}.fn(() => Promise.resolve(undefined)),
|
|
280
|
+
isMutating: isMutating ?? false,
|
|
281
|
+
error: error ?? undefined,
|
|
282
|
+
data,
|
|
283
|
+
} as ReturnType<Fn>);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildTsqDefaults(fw) {
|
|
291
|
+
return `\
|
|
292
|
+
const defaultQueryReturn = {
|
|
293
|
+
data: undefined, isLoading: false, isPending: false, isFetching: false,
|
|
294
|
+
isSuccess: false, error: null, refetch: ${fn(fw)}, status: 'pending' as const,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const defaultMutationReturn = {
|
|
298
|
+
mutate: ${fn(fw)}, mutateAsync: ${fn(fw)}, isPending: false,
|
|
299
|
+
isSuccess: false, error: null, data: undefined, reset: ${fn(fw)},
|
|
300
|
+
};
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function buildTsqHelpers(fw) {
|
|
305
|
+
const ref = fwRef(fw);
|
|
306
|
+
return `\
|
|
307
|
+
// ─── TanStack Query mock helpers ─────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/** Augments a spy with a \`mockQuery\` shorthand for useQuery hooks. */
|
|
310
|
+
function withMockQuery<T extends ReturnType<typeof ${ref}.spyOn>>(spy: T) {
|
|
311
|
+
return Object.assign(spy, {
|
|
312
|
+
mockQuery({ data, isLoading, error }: { data?: unknown; isLoading?: boolean; error?: Error }) {
|
|
313
|
+
const pending = isLoading ?? false;
|
|
314
|
+
spy.mockReturnValue({
|
|
315
|
+
...defaultQueryReturn,
|
|
316
|
+
data,
|
|
317
|
+
isLoading: pending,
|
|
318
|
+
isPending: pending,
|
|
319
|
+
isSuccess: data !== undefined && !pending,
|
|
320
|
+
error: error ?? null,
|
|
321
|
+
status: pending ? 'pending' : ('success' as const),
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Augments a spy with a \`mockMutation\` shorthand for useMutation hooks. */
|
|
328
|
+
function withMockMutation<T extends ReturnType<typeof ${ref}.spyOn>>(spy: T) {
|
|
329
|
+
return Object.assign(spy, {
|
|
330
|
+
mockMutation({ data, isPending, error }: { data?: unknown; isPending?: boolean; error?: Error }) {
|
|
331
|
+
spy.mockReturnValue({
|
|
332
|
+
...defaultMutationReturn,
|
|
333
|
+
mutate: ${fn(fw)},
|
|
334
|
+
mutateAsync: ${fn(fw)},
|
|
335
|
+
isPending: isPending ?? false,
|
|
336
|
+
error: error ?? null,
|
|
337
|
+
data,
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function buildCreateClientMocks(
|
|
346
|
+
groups,
|
|
347
|
+
fw
|
|
348
|
+
) {
|
|
349
|
+
const spy = spyFn(fw);
|
|
350
|
+
const lines = ['export function createClientMocks() {', ' return {'];
|
|
351
|
+
|
|
352
|
+
for (const { camelName, ops } of groups) {
|
|
353
|
+
lines.push(` ${camelName}Client: {`);
|
|
354
|
+
for (const op of ops) {
|
|
355
|
+
lines.push(
|
|
356
|
+
` ${op.name}: ${spy}(realApi.${camelName}Client, '${op.name}').mockReturnValue(undefined as any),`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
lines.push(' },');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
lines.push(' };');
|
|
363
|
+
lines.push('}');
|
|
364
|
+
return lines.join('\n') + '\n';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function buildCreateSwrHookMocks(
|
|
368
|
+
groups,
|
|
369
|
+
fw
|
|
370
|
+
) {
|
|
371
|
+
const spy = spyFn(fw);
|
|
372
|
+
const lines = ['export function createApiHookMocks() {', ' return {'];
|
|
373
|
+
|
|
374
|
+
for (const { camelName, ops } of groups) {
|
|
375
|
+
const getOps = ops.filter((o) => o.method === 'GET');
|
|
376
|
+
const mutOps = ops.filter((o) => o.method !== 'GET');
|
|
377
|
+
|
|
378
|
+
lines.push(` ${camelName}: {`);
|
|
379
|
+
lines.push(' queries: {');
|
|
380
|
+
for (const op of getOps) {
|
|
381
|
+
const hookName = toHookName(op.name, 'use');
|
|
382
|
+
lines.push(
|
|
383
|
+
` ${hookName}: withMockSWR(${spy}(realApi.${camelName}.queries, '${hookName}').mockReturnValue(defaultSWRReturn)),`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
lines.push(' },');
|
|
387
|
+
lines.push(' mutations: {');
|
|
388
|
+
for (const op of mutOps) {
|
|
389
|
+
const hookName = 'use' + _case.pascal.call(void 0, op.name);
|
|
390
|
+
lines.push(
|
|
391
|
+
` ${hookName}: withMockSWRMutation(${spy}(realApi.${camelName}.mutations, '${hookName}').mockReturnValue(defaultSWRMutationReturn as any)),`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
lines.push(' },');
|
|
395
|
+
lines.push(' },');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
lines.push(' };');
|
|
399
|
+
lines.push('}');
|
|
400
|
+
return lines.join('\n') + '\n';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function buildCreateTsqHookMocks(
|
|
404
|
+
groups,
|
|
405
|
+
fw
|
|
406
|
+
) {
|
|
407
|
+
const spy = spyFn(fw);
|
|
408
|
+
const lines = ['export function createApiHookMocks() {', ' return {'];
|
|
409
|
+
|
|
410
|
+
for (const { camelName, ops } of groups) {
|
|
411
|
+
const getOps = ops.filter((o) => o.method === 'GET');
|
|
412
|
+
const mutOps = ops.filter((o) => o.method !== 'GET');
|
|
413
|
+
|
|
414
|
+
lines.push(` ${camelName}: {`);
|
|
415
|
+
lines.push(' queries: {');
|
|
416
|
+
for (const op of getOps) {
|
|
417
|
+
const hookName = toHookName(op.name, 'use');
|
|
418
|
+
lines.push(
|
|
419
|
+
` ${hookName}: withMockQuery(${spy}(realApi.${camelName}.queries, '${hookName}').mockReturnValue(defaultQueryReturn)),`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
lines.push(' },');
|
|
423
|
+
lines.push(' mutations: {');
|
|
424
|
+
for (const op of mutOps) {
|
|
425
|
+
const hookName = 'use' + _case.pascal.call(void 0, op.name);
|
|
426
|
+
lines.push(
|
|
427
|
+
` ${hookName}: withMockMutation(${spy}(realApi.${camelName}.mutations, '${hookName}').mockReturnValue(defaultMutationReturn)),`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
lines.push(' },');
|
|
431
|
+
lines.push(' },');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
lines.push(' };');
|
|
435
|
+
lines.push('}');
|
|
436
|
+
return lines.join('\n') + '\n';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Converts an operation name to a React hook name.
|
|
441
|
+
* Strips a leading "get" prefix (case-insensitive) then capitalises, then
|
|
442
|
+
* prepends the given prefix (e.g. "use").
|
|
443
|
+
*
|
|
444
|
+
* @example toHookName('getPetById', 'use') → 'usePetById'
|
|
445
|
+
* @example toHookName('findPetsByStatus', 'use') → 'useFindPetsByStatus'
|
|
446
|
+
*/
|
|
447
|
+
function toHookName(operationName, prefix) {
|
|
448
|
+
const stripped = operationName.toLowerCase().startsWith('get')
|
|
449
|
+
? operationName.slice(3)
|
|
450
|
+
: operationName;
|
|
451
|
+
const capitalised = stripped.charAt(0).toUpperCase() + stripped.slice(1);
|
|
452
|
+
return prefix + capitalised;
|
|
453
|
+
}
|
|
@@ -10,10 +10,17 @@ var _swagger = require('../swagger');
|
|
|
10
10
|
|
|
11
11
|
var _utils = require('../utils/utils');
|
|
12
12
|
var _templateEngine = require('../utils/templateEngine');
|
|
13
|
+
var _templateValidator = require('../utils/templateValidator');
|
|
13
14
|
var _createBarrel = require('./createBarrel');
|
|
14
15
|
var _header = require('./header');
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
var _jsDocs = require('./jsDocs');
|
|
18
25
|
|
|
19
26
|
/**
|
|
@@ -26,11 +33,23 @@ var _jsDocs = require('./jsDocs');
|
|
|
26
33
|
const operations = _swagger.getOperations.call(void 0, spec);
|
|
27
34
|
const groups = _utils.groupOperationsByGroupName.call(void 0, operations);
|
|
28
35
|
const servicePrefix = options.servicePrefix;
|
|
29
|
-
|
|
36
|
+
const baseClientData = {
|
|
30
37
|
servicePrefix,
|
|
31
38
|
baseUrl: options.baseUrl,
|
|
32
39
|
...options.queryParamsSerialization,
|
|
33
|
-
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// When a composite [L2, L1] template is used, the L2 base client contains
|
|
43
|
+
// reactive library imports (e.g. useSWR, useQuery). These are placed first
|
|
44
|
+
// so all imports appear at the top of the file before the HTTP client setup.
|
|
45
|
+
let baseClients = '';
|
|
46
|
+
if (_templateEngine.hasTemplateFile.call(void 0, 'baseClientL2.ejs')) {
|
|
47
|
+
baseClients += _templateEngine.renderFile.call(void 0, 'baseClientL2.ejs', baseClientData);
|
|
48
|
+
}
|
|
49
|
+
baseClients += _templateEngine.renderFile.call(void 0, 'baseClient.ejs', baseClientData);
|
|
50
|
+
|
|
51
|
+
const clientDirective = options.useClient ? "'use client';\n" : '';
|
|
52
|
+
let result = clientDirective + _header.FILE_HEADER + baseClients;
|
|
34
53
|
|
|
35
54
|
for (const name in groups) {
|
|
36
55
|
const group = groups[name];
|
|
@@ -43,6 +62,7 @@ var _jsDocs = require('./jsDocs');
|
|
|
43
62
|
const renderedFile = _templateEngine.renderFile.call(void 0, 'client.ejs', {
|
|
44
63
|
...clientData,
|
|
45
64
|
servicePrefix,
|
|
65
|
+
httpConfigType: _templateValidator.getHttpConfigType.call(void 0, options.template),
|
|
46
66
|
});
|
|
47
67
|
|
|
48
68
|
result += renderedFile;
|
|
@@ -101,32 +121,44 @@ function prepareClient(
|
|
|
101
121
|
|
|
102
122
|
try {
|
|
103
123
|
const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op, components);
|
|
104
|
-
const returnType =
|
|
124
|
+
const returnType = respObject
|
|
125
|
+
? _swagger.getParameterType.call(void 0, respObject, options)
|
|
126
|
+
: options.preferAny
|
|
127
|
+
? 'any'
|
|
128
|
+
: 'unknown';
|
|
105
129
|
|
|
106
|
-
const body = getRequestBody(op.requestBody, components, options);
|
|
130
|
+
const body = op.requestBody ? getRequestBody(op.requestBody, components, options) : null;
|
|
107
131
|
const queryParams = getParams(op.parameters , options, ['query']);
|
|
108
|
-
|
|
132
|
+
let params = getParams(op.parameters , options);
|
|
133
|
+
let queryParamObject;
|
|
109
134
|
|
|
110
135
|
if (body) {
|
|
111
136
|
params.unshift(body);
|
|
112
137
|
}
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
if (params.every((p) => p.original
|
|
116
|
-
params.sort(
|
|
139
|
+
// If all parameters have 'x-position' defined, sort them by it
|
|
140
|
+
if (params.every((p) => p.original && 'x-position' in p.original)) {
|
|
141
|
+
params.sort(
|
|
142
|
+
(a, b) =>
|
|
143
|
+
(a.original )['x-position'] -
|
|
144
|
+
(b.original )['x-position']
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
shouldGroupQueryParams(queryParams, options.queryParamsSerialization.queryParamsAsObject)
|
|
150
|
+
) {
|
|
151
|
+
queryParamObject = createQueryParamObject(queryParams);
|
|
152
|
+
params = replaceQueryParamsWithObject(params, queryParamObject);
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
markParametersAsSkippable(params);
|
|
120
156
|
|
|
121
|
-
const headers = getParams(
|
|
122
|
-
op.parameters ,
|
|
123
|
-
options,
|
|
124
|
-
['header']
|
|
125
|
-
);
|
|
157
|
+
const headers = getParams(op.parameters , options, ['header']);
|
|
126
158
|
// Some libraries need explicit Content-Type for request bodies.
|
|
127
159
|
if (_optionalChain([body, 'optionalAccess', _2 => _2.contentType]) === 'urlencoded') {
|
|
128
160
|
upsertFixedHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');
|
|
129
|
-
} else if (_optionalChain([body, 'optionalAccess', _3 => _3.contentType]) === 'json' && options.template === 'fetch') {
|
|
161
|
+
} else if (_optionalChain([body, 'optionalAccess', _3 => _3.contentType]) === 'json' && _templateValidator.getL1Template.call(void 0, options.template) === 'fetch') {
|
|
130
162
|
upsertFixedHeader(headers, 'Content-Type', 'application/json');
|
|
131
163
|
}
|
|
132
164
|
|
|
@@ -135,10 +167,11 @@ function prepareClient(
|
|
|
135
167
|
returnType,
|
|
136
168
|
responseContentType,
|
|
137
169
|
method: op.method.toUpperCase(),
|
|
138
|
-
name: getOperationName(op.operationId, op.group),
|
|
170
|
+
name: getOperationName(_nullishCoalesce(op.operationId, () => ( null)), op.group),
|
|
139
171
|
url: prepareUrl(op.path),
|
|
140
172
|
parameters: params,
|
|
141
173
|
query: queryParams,
|
|
174
|
+
queryParamObject,
|
|
142
175
|
body,
|
|
143
176
|
headers,
|
|
144
177
|
};
|
|
@@ -157,6 +190,59 @@ function prepareClient(
|
|
|
157
190
|
});
|
|
158
191
|
} exports.prepareOperations = prepareOperations;
|
|
159
192
|
|
|
193
|
+
function shouldGroupQueryParams(
|
|
194
|
+
queryParams,
|
|
195
|
+
setting
|
|
196
|
+
) {
|
|
197
|
+
if (queryParams.length < 1 || setting === false) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (setting === true) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return queryParams.length > setting;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function createQueryParamObject(queryParams) {
|
|
209
|
+
const properties = queryParams
|
|
210
|
+
.map((param) => {
|
|
211
|
+
const isOptional = param.optional ? '?' : '';
|
|
212
|
+
const nullableType = param.optional ? ' | null' : '';
|
|
213
|
+
return `${param.name}${isOptional}: ${param.type}${nullableType};`;
|
|
214
|
+
})
|
|
215
|
+
.join(' ');
|
|
216
|
+
const containsRequiredQueryParams = queryParams.some((param) => !param.optional);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
originalName: 'queryParams',
|
|
220
|
+
name: 'queryParams',
|
|
221
|
+
type: `{ ${properties} }`,
|
|
222
|
+
optional: !containsRequiredQueryParams,
|
|
223
|
+
jsDoc: `Grouped query parameters object (${queryParams
|
|
224
|
+
.map((param) => param.originalName)
|
|
225
|
+
.join(', ')})`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function replaceQueryParamsWithObject(
|
|
230
|
+
params,
|
|
231
|
+
queryParamObject
|
|
232
|
+
) {
|
|
233
|
+
const isQueryParam = (param) =>
|
|
234
|
+
!!param.original && 'in' in param.original && param.original.in === 'query';
|
|
235
|
+
|
|
236
|
+
const firstQueryParamIndex = params.findIndex(isQueryParam);
|
|
237
|
+
if (firstQueryParamIndex < 0) {
|
|
238
|
+
return params;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const paramsWithoutQuery = params.filter((param) => !isQueryParam(param));
|
|
242
|
+
paramsWithoutQuery.splice(firstQueryParamIndex, 0, queryParamObject);
|
|
243
|
+
return paramsWithoutQuery;
|
|
244
|
+
}
|
|
245
|
+
|
|
160
246
|
/**
|
|
161
247
|
* Marks parameters as skippable based on their position relative to the last required parameter.
|
|
162
248
|
*
|
|
@@ -202,7 +288,11 @@ function prepareUrl(path) {
|
|
|
202
288
|
* it can happen very easily and we need to handle it gracefully.
|
|
203
289
|
*/
|
|
204
290
|
function fixDuplicateOperations(operations) {
|
|
205
|
-
if (!operations
|
|
291
|
+
if (!operations) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (operations.length < 2) {
|
|
206
296
|
return operations;
|
|
207
297
|
}
|
|
208
298
|
|
|
@@ -287,7 +377,7 @@ function prepareUrl(path) {
|
|
|
287
377
|
*/
|
|
288
378
|
function getParamName(name) {
|
|
289
379
|
if (!name) {
|
|
290
|
-
return name;
|
|
380
|
+
return _nullishCoalesce(name, () => ( ''));
|
|
291
381
|
}
|
|
292
382
|
|
|
293
383
|
return _utils.escapeIdentifier.call(void 0,
|
|
@@ -324,13 +414,15 @@ function getRequestBody(
|
|
|
324
414
|
const isFormData = contentType === 'form-data';
|
|
325
415
|
|
|
326
416
|
if (bodyContent) {
|
|
417
|
+
const reqBodyAny = reqBody ;
|
|
418
|
+
const xName = reqBodyAny['x-name'];
|
|
327
419
|
return {
|
|
328
|
-
originalName: _nullishCoalesce(
|
|
329
|
-
name: getParamName(_nullishCoalesce(
|
|
420
|
+
originalName: _nullishCoalesce(xName, () => ( 'body')),
|
|
421
|
+
name: getParamName(_nullishCoalesce(xName, () => ( 'body'))),
|
|
330
422
|
type: isFormData ? 'FormData' : _swagger.getParameterType.call(void 0, bodyContent, options),
|
|
331
423
|
optional: !reqBody.required,
|
|
332
424
|
original: reqBody,
|
|
333
|
-
contentType,
|
|
425
|
+
contentType: _nullishCoalesce(contentType, () => ( undefined)),
|
|
334
426
|
};
|
|
335
427
|
}
|
|
336
428
|
|