beer-swagger 1.1.1 → 4.0.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/generate-apis.js +124 -30
- package/package.json +1 -1
package/generate-apis.js
CHANGED
|
@@ -116,7 +116,7 @@ function appendParamSuffix(baseName, paramSuffix) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Add a verb when the HTTP method should be reflected in the name.
|
|
119
|
-
function addVerbForSingleSegment(name, method
|
|
119
|
+
function addVerbForSingleSegment(name, method) {
|
|
120
120
|
const lower = name.toLowerCase();
|
|
121
121
|
if (KEEP_METHOD_NAMES.has(lower)) {
|
|
122
122
|
return name;
|
|
@@ -125,7 +125,7 @@ function addVerbForSingleSegment(name, method, hasPathParams = false) {
|
|
|
125
125
|
const suffix = name[0].toUpperCase() + name.slice(1);
|
|
126
126
|
return `removeBy${suffix}`;
|
|
127
127
|
}
|
|
128
|
-
if (
|
|
128
|
+
if (method === 'post' || method === 'put') {
|
|
129
129
|
return name;
|
|
130
130
|
}
|
|
131
131
|
const verbMap = {
|
|
@@ -185,7 +185,7 @@ function getBodyTypeName(requestBody, schemas) {
|
|
|
185
185
|
// Map a Swagger schema to a runtime return type.
|
|
186
186
|
function mapSchemaToType(schema, schemas, useTypes) {
|
|
187
187
|
if (!schema) {
|
|
188
|
-
return '
|
|
188
|
+
return 'object';
|
|
189
189
|
}
|
|
190
190
|
const {
|
|
191
191
|
$ref,
|
|
@@ -201,18 +201,18 @@ function mapSchemaToType(schema, schemas, useTypes) {
|
|
|
201
201
|
if (responseSchema && responseSchema.properties && responseSchema.properties.data) {
|
|
202
202
|
return mapSchemaToType(responseSchema.properties.data, schemas, useTypes);
|
|
203
203
|
}
|
|
204
|
-
return '
|
|
204
|
+
return 'object';
|
|
205
205
|
}
|
|
206
206
|
const refName = normalizeDtoName(rawRefName);
|
|
207
207
|
const refSchema = schemas[rawRefName] || schemas[refName];
|
|
208
208
|
if (refSchema && refSchema.properties && refSchema.properties.data) {
|
|
209
209
|
return mapSchemaToType(refSchema.properties.data, schemas, useTypes);
|
|
210
210
|
}
|
|
211
|
-
return useTypes ? `Types.${refName}` : '
|
|
211
|
+
return useTypes ? `Types.${refName}` : 'object';
|
|
212
212
|
}
|
|
213
213
|
if (!type && items) {
|
|
214
214
|
const itemType = mapSchemaToType(items, schemas, useTypes);
|
|
215
|
-
if (itemType === '
|
|
215
|
+
if (itemType === '[]') {
|
|
216
216
|
return '[]';
|
|
217
217
|
}
|
|
218
218
|
return `${itemType}[]`;
|
|
@@ -234,7 +234,7 @@ function mapSchemaToType(schema, schemas, useTypes) {
|
|
|
234
234
|
}
|
|
235
235
|
if (type === 'array') {
|
|
236
236
|
const itemType = mapSchemaToType(items, schemas, useTypes);
|
|
237
|
-
if (itemType === '
|
|
237
|
+
if (itemType === '[]') {
|
|
238
238
|
return '[]';
|
|
239
239
|
}
|
|
240
240
|
return `${itemType}[]`;
|
|
@@ -246,9 +246,9 @@ function mapSchemaToType(schema, schemas, useTypes) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// Map parameter schema to a type for method signatures.
|
|
249
|
-
function mapParamSchemaToType(schema, schemas, useTypes) {
|
|
249
|
+
function mapParamSchemaToType(schema, schemas, useTypes, isQuery = false) {
|
|
250
250
|
if (!schema) {
|
|
251
|
-
return '
|
|
251
|
+
return isQuery ? 'Record<string, string | undefined>' : 'object';
|
|
252
252
|
}
|
|
253
253
|
const {
|
|
254
254
|
$ref,
|
|
@@ -259,7 +259,7 @@ function mapParamSchemaToType(schema, schemas, useTypes) {
|
|
|
259
259
|
if ($ref) {
|
|
260
260
|
const refName = normalizeDtoName($ref.split('/')
|
|
261
261
|
.pop());
|
|
262
|
-
return useTypes ? `Types.${refName}` : '
|
|
262
|
+
return useTypes ? `Types.${refName}` : 'object';
|
|
263
263
|
}
|
|
264
264
|
if (!type && items) {
|
|
265
265
|
return '[]';
|
|
@@ -283,9 +283,29 @@ function mapParamSchemaToType(schema, schemas, useTypes) {
|
|
|
283
283
|
return '[]';
|
|
284
284
|
}
|
|
285
285
|
if (type === 'object') {
|
|
286
|
-
return 'object';
|
|
286
|
+
return isQuery ? 'Record<string, string | undefined>' : 'object';
|
|
287
287
|
}
|
|
288
|
-
return 'object';
|
|
288
|
+
return isQuery ? 'Record<string, string | undefined>' : 'object';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Resolve parameter type for a parameter definition.
|
|
292
|
+
function resolveParamType(param, schemas) {
|
|
293
|
+
const schemaToUse = param.schema || (param.type ? { type: param.type, format: param.format, items: param.items } : null);
|
|
294
|
+
const isQuery = param.in === 'query';
|
|
295
|
+
return mapParamSchemaToType(schemaToUse, schemas, true, isQuery);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Detect event streams in successful or default responses.
|
|
299
|
+
function isEventStreamResponse(op) {
|
|
300
|
+
return Object.entries(op.responses || {}).some(([status, response]) => {
|
|
301
|
+
if (!/^2(?:\d{2}|XX)$/i.test(status) && status !== 'default') {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return Object.keys(response.content || {}).some((mediaType) => {
|
|
305
|
+
const type = mediaType.split(';')[0].trim().toLowerCase();
|
|
306
|
+
return type === 'text/event-stream' || type === 'event-stream';
|
|
307
|
+
});
|
|
308
|
+
});
|
|
289
309
|
}
|
|
290
310
|
|
|
291
311
|
// Resolve default response type from Swagger responses.
|
|
@@ -295,7 +315,7 @@ function getDefaultReturnType(op, schemas, useTypes) {
|
|
|
295
315
|
const { content = {} } = res;
|
|
296
316
|
const firstContent = content['application/json'] || content['application/*+json'] || Object.values(content)[0];
|
|
297
317
|
if (!firstContent) {
|
|
298
|
-
return '
|
|
318
|
+
return 'object';
|
|
299
319
|
}
|
|
300
320
|
return mapSchemaToType(firstContent.schema, schemas, useTypes);
|
|
301
321
|
}
|
|
@@ -455,8 +475,8 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
455
475
|
const pathSegments = p.split('/')
|
|
456
476
|
.filter(Boolean)
|
|
457
477
|
.filter((part) => !part.startsWith('{'));
|
|
458
|
-
const shouldAddVerb = pathSegments.length === 1
|
|
459
|
-
name = shouldAddVerb ? addVerbForSingleSegment(inferred, method
|
|
478
|
+
const shouldAddVerb = pathSegments.length === 1;
|
|
479
|
+
name = shouldAddVerb ? addVerbForSingleSegment(inferred, method) : inferred;
|
|
460
480
|
}
|
|
461
481
|
if (!KEEP_METHOD_NAMES.has(name.toLowerCase())) {
|
|
462
482
|
name = fixVerbCase(name);
|
|
@@ -498,6 +518,11 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
498
518
|
const className = toClassName(tag);
|
|
499
519
|
const facadePrefix = toFacadePrefix(className);
|
|
500
520
|
const sortedEntries = [...entries].sort((a, b) => {
|
|
521
|
+
const aDynamic = a.params.some((p) => p.in === 'path') || a.path.includes('{') ? 1 : 0;
|
|
522
|
+
const bDynamic = b.params.some((p) => p.in === 'path') || b.path.includes('{') ? 1 : 0;
|
|
523
|
+
if (aDynamic !== bDynamic) {
|
|
524
|
+
return aDynamic - bDynamic;
|
|
525
|
+
}
|
|
501
526
|
const aScore = (a.hasBody ? 1 : 0) + (a.params.length ? 1 : 0);
|
|
502
527
|
const bScore = (b.hasBody ? 1 : 0) + (b.params.length ? 1 : 0);
|
|
503
528
|
return aScore - bScore;
|
|
@@ -518,21 +543,28 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
518
543
|
if (!KEEP_METHOD_NAMES.has(name.toLowerCase()) && (name === 'get' || name === 'delete') && facadePrefix) {
|
|
519
544
|
name = `${name}${facadePrefix}`;
|
|
520
545
|
}
|
|
546
|
+
const isDynamicPath = entryParams.some((p) => p.in === 'path') || entryPath.includes('{');
|
|
547
|
+
if (seen.has(name) && isDynamicPath && (entryMethod === 'post' || entryMethod === 'put')) {
|
|
548
|
+
const hasSavePrefix = name.startsWith('save') && (name.length === 4 || /^[A-Z]/.test(name.slice(4)));
|
|
549
|
+
const saveName = hasSavePrefix ? name : `save${toPascal(name)}`;
|
|
550
|
+
name = saveName;
|
|
551
|
+
}
|
|
552
|
+
const baseName = name;
|
|
521
553
|
if (seen.has(name)) {
|
|
522
554
|
const bodyTypeName = entryHasBody ? getBodyTypeName(entryOp.requestBody, schemas) : '';
|
|
523
555
|
if (bodyTypeName) {
|
|
524
|
-
name = `${
|
|
556
|
+
name = `${baseName}By${toPascal(bodyTypeName)}`;
|
|
525
557
|
}
|
|
526
558
|
}
|
|
527
559
|
if (seen.has(name)) {
|
|
528
560
|
const paramSuffix = toParamSuffix(entryParams, paramNameMap);
|
|
529
561
|
if (paramSuffix) {
|
|
530
|
-
name = appendParamSuffix(
|
|
562
|
+
name = appendParamSuffix(baseName, paramSuffix);
|
|
531
563
|
}
|
|
532
564
|
}
|
|
533
565
|
if (seen.has(name)) {
|
|
534
566
|
const methodSuffix = entryMethod[0].toUpperCase() + entryMethod.slice(1);
|
|
535
|
-
name = `${
|
|
567
|
+
name = `${baseName}${methodSuffix}`;
|
|
536
568
|
}
|
|
537
569
|
seen.add(name);
|
|
538
570
|
const defaultType = entryDefaultReturnType;
|
|
@@ -543,17 +575,16 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
543
575
|
|
|
544
576
|
const paramArgs = orderedParams.map((p) => {
|
|
545
577
|
const {
|
|
546
|
-
schema: paramSchema,
|
|
547
578
|
required,
|
|
548
579
|
name: paramName
|
|
549
580
|
} = p;
|
|
550
581
|
const mappedName = paramNameMap.get(paramName) || paramName;
|
|
551
|
-
const paramType =
|
|
582
|
+
const paramType = resolveParamType(p, schemas);
|
|
552
583
|
const optional = required ? '' : '?';
|
|
553
584
|
return `${mappedName}${optional}: ${paramType}`;
|
|
554
585
|
});
|
|
555
586
|
|
|
556
|
-
let bodyType = '
|
|
587
|
+
let bodyType = 'object';
|
|
557
588
|
const { requestBody: opRequestBody } = entryOp;
|
|
558
589
|
if (entryHasBody && opRequestBody && opRequestBody.content) {
|
|
559
590
|
const { content } = opRequestBody;
|
|
@@ -605,17 +636,43 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
605
636
|
methods.push(' */');
|
|
606
637
|
}
|
|
607
638
|
|
|
608
|
-
const
|
|
609
|
-
|
|
639
|
+
const queryParams = entryParams.filter((p) => p.in === 'query');
|
|
640
|
+
let paramsObject = 'undefined';
|
|
641
|
+
if (queryParams.length === 1) {
|
|
642
|
+
const p = queryParams[0];
|
|
643
|
+
const paramType = resolveParamType(p, schemas);
|
|
644
|
+
const mappedName = paramNameMap.get(p.name) || p.name;
|
|
645
|
+
if (paramType === 'Record<string, string | undefined>') {
|
|
646
|
+
paramsObject = mappedName;
|
|
647
|
+
} else {
|
|
648
|
+
const key = formatObjectKey(p.name);
|
|
649
|
+
const prop = mappedName === p.name ? key : `${key}: ${mappedName}`;
|
|
650
|
+
paramsObject = `{ ${prop} }`;
|
|
651
|
+
}
|
|
652
|
+
} else if (queryParams.length > 1) {
|
|
653
|
+
const entries = queryParams.map((p) => {
|
|
654
|
+
const paramType = resolveParamType(p, schemas);
|
|
610
655
|
const mappedName = paramNameMap.get(p.name) || p.name;
|
|
656
|
+
if (paramType === 'Record<string, string | undefined>') {
|
|
657
|
+
return `...${mappedName}`;
|
|
658
|
+
}
|
|
611
659
|
const key = formatObjectKey(p.name);
|
|
612
660
|
return mappedName === p.name ? key : `${key}: ${mappedName}`;
|
|
613
661
|
});
|
|
614
|
-
|
|
662
|
+
paramsObject = `{ ${entries.join(', ')} }`;
|
|
663
|
+
}
|
|
615
664
|
|
|
616
665
|
const pathTemplate = withPathParams(entryPath, entryParams, paramNameMap);
|
|
617
666
|
const pathLiteral = pathTemplate.includes('${') ? `\`${pathTemplate}\`` : `'${pathTemplate}'`;
|
|
618
667
|
|
|
668
|
+
if (isEventStreamResponse(entryOp)) {
|
|
669
|
+
methods.push(` public ${name}(${sigParts}): EventSource {`);
|
|
670
|
+
methods.push(` return this.sse(${pathLiteral}, ${paramsObject});`);
|
|
671
|
+
methods.push(' }');
|
|
672
|
+
methods.push('');
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
|
|
619
676
|
if (entryMethod === 'get') {
|
|
620
677
|
methods.push(` public async ${name}<T = ${defaultType}>(${sigParts}): Promise<JsonResponse<T>> {`);
|
|
621
678
|
methods.push(` return await this.get<T>(${pathLiteral}, ${paramsObject});`);
|
|
@@ -733,7 +790,7 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
|
|
|
733
790
|
// Map Swagger schema to a TS type for DTO output.
|
|
734
791
|
function mapSchemaToTsType(schema, schemas) {
|
|
735
792
|
if (!schema) {
|
|
736
|
-
return '
|
|
793
|
+
return 'object';
|
|
737
794
|
}
|
|
738
795
|
const {
|
|
739
796
|
$ref,
|
|
@@ -751,7 +808,7 @@ function mapSchemaToTsType(schema, schemas) {
|
|
|
751
808
|
if (refSchema && refSchema.properties && refSchema.properties.data) {
|
|
752
809
|
return mapSchemaToTsType(refSchema.properties.data, schemas);
|
|
753
810
|
}
|
|
754
|
-
return '
|
|
811
|
+
return 'object';
|
|
755
812
|
}
|
|
756
813
|
return normalizeDtoName(refName);
|
|
757
814
|
}
|
|
@@ -763,6 +820,9 @@ function mapSchemaToTsType(schema, schemas) {
|
|
|
763
820
|
return schemaEnum.map((v) => (typeof v === 'string' ? `'${v}'` : String(v)))
|
|
764
821
|
.join(' | ');
|
|
765
822
|
}
|
|
823
|
+
if (type === 'any') {
|
|
824
|
+
return 'any';
|
|
825
|
+
}
|
|
766
826
|
if (type === 'string') {
|
|
767
827
|
return 'string';
|
|
768
828
|
}
|
|
@@ -784,11 +844,38 @@ function mapSchemaToTsType(schema, schemas) {
|
|
|
784
844
|
}
|
|
785
845
|
if (type === 'object') {
|
|
786
846
|
if (additionalProperties) {
|
|
787
|
-
return 'Record<string,
|
|
847
|
+
return 'Record<string, object>';
|
|
788
848
|
}
|
|
789
|
-
return '
|
|
849
|
+
return 'object';
|
|
790
850
|
}
|
|
791
|
-
return '
|
|
851
|
+
return 'object';
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Swagger treats schemas without a type constraint as any.
|
|
855
|
+
function isAnySchema(schema) {
|
|
856
|
+
if (schema === true || schema === null || schema === undefined) {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
if (typeof schema !== 'object' || Array.isArray(schema)) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (schema.type === 'any') {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
const typeConstraints = [
|
|
866
|
+
'$ref',
|
|
867
|
+
'type',
|
|
868
|
+
'items',
|
|
869
|
+
'enum',
|
|
870
|
+
'const',
|
|
871
|
+
'properties',
|
|
872
|
+
'additionalProperties',
|
|
873
|
+
'allOf',
|
|
874
|
+
'oneOf',
|
|
875
|
+
'anyOf',
|
|
876
|
+
'not'
|
|
877
|
+
];
|
|
878
|
+
return !typeConstraints.some((key) => schema[key] !== undefined);
|
|
792
879
|
}
|
|
793
880
|
|
|
794
881
|
// Generate DTO/type definitions file content.
|
|
@@ -829,7 +916,14 @@ function generateTypes(spec) {
|
|
|
829
916
|
const def = typeDefs.get(normalizedName);
|
|
830
917
|
const requiredSet = new Set(required || []);
|
|
831
918
|
for (const [prop, propSchema] of Object.entries(properties)) {
|
|
832
|
-
|
|
919
|
+
if (isAnySchema(propSchema)) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const propType = mapSchemaToTsType(propSchema, schemas);
|
|
923
|
+
if (propType === 'any') {
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
def.props[prop] = propType;
|
|
833
927
|
def.required[prop] = requiredSet.has(prop);
|
|
834
928
|
const { description } = propSchema || {};
|
|
835
929
|
const desc = description
|