Skip to content

Commit 23a7c2a

Browse files
feat: anonymous body, response type generates named type
1 parent 1e45cd1 commit 23a7c2a

File tree

7 files changed

+142
-125
lines changed

7 files changed

+142
-125
lines changed

.changeset/cool-zoos-start.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openapi-ts-request': minor
3+
---
4+
5+
feat: anonymous body, response type generates named type

src/generator/serviceGenarator.ts

Lines changed: 98 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { rimrafSync } from 'rimraf';
2222

2323
import {
2424
PriorityRule,
25-
SchemaObjectFormat,
2625
SchemaObjectType,
2726
displayReactQueryMode,
2827
} from '../config';
@@ -108,6 +107,7 @@ export default class ServiceGenerator {
108107
protected config: GenerateServiceProps;
109108
protected openAPIData: OpenAPIObject;
110109
protected schemaList: ISchemaItem[] = [];
110+
protected interfaceTPConfigs: ITypeItem[] = [];
111111

112112
constructor(config: GenerateServiceProps, openAPIData: OpenAPIObject) {
113113
this.config = {
@@ -273,58 +273,6 @@ export default class ServiceGenerator {
273273
const reactQueryMode = this.config.reactQueryMode;
274274
const reactQueryFileName = displayReactQueryFileName(reactQueryMode);
275275

276-
// 处理重复的 typeName
277-
const interfaceTPConfigs = this.getInterfaceTPConfigs();
278-
handleDuplicateTypeNames(interfaceTPConfigs);
279-
280-
// 生成 ts 类型声明
281-
if (!isGenJavaScript) {
282-
this.genFileFromTemplate(
283-
`${interfaceFileName}.ts`,
284-
TypescriptFileType.interface,
285-
{
286-
nullable: this.config.nullable,
287-
list: interfaceTPConfigs,
288-
}
289-
);
290-
}
291-
292-
// 生成枚举翻译
293-
const enums = filter(interfaceTPConfigs, (item) => item.isEnum);
294-
if (!isGenJavaScript && !isOnlyGenTypeScriptType && !isEmpty(enums)) {
295-
this.genFileFromTemplate(
296-
`${displayEnumLabelFileName}.ts`,
297-
TypescriptFileType.displayEnumLabel,
298-
{
299-
list: enums,
300-
namespace: this.config.namespace,
301-
interfaceFileName: interfaceFileName,
302-
}
303-
);
304-
}
305-
306-
const displayTypeLabels = filter(
307-
interfaceTPConfigs,
308-
(item) => !item.isEnum
309-
);
310-
// 生成 type 翻译
311-
if (
312-
!isGenJavaScript &&
313-
!isOnlyGenTypeScriptType &&
314-
this.config.isDisplayTypeLabel &&
315-
!isEmpty(displayTypeLabels)
316-
) {
317-
this.genFileFromTemplate(
318-
`${displayTypeLabelFileName}.ts`,
319-
TypescriptFileType.displayTypeLabel,
320-
{
321-
list: displayTypeLabels,
322-
namespace: this.config.namespace,
323-
interfaceFileName: interfaceFileName,
324-
}
325-
);
326-
}
327-
328276
if (!isOnlyGenTypeScriptType) {
329277
const prettierError = [];
330278

@@ -392,6 +340,58 @@ export default class ServiceGenerator {
392340
}
393341
}
394342

343+
// 处理重复的 typeName
344+
this.interfaceTPConfigs = this.getInterfaceTPConfigs();
345+
handleDuplicateTypeNames(this.interfaceTPConfigs);
346+
347+
// 生成 ts 类型声明
348+
if (!isGenJavaScript) {
349+
this.genFileFromTemplate(
350+
`${interfaceFileName}.ts`,
351+
TypescriptFileType.interface,
352+
{
353+
nullable: this.config.nullable,
354+
list: this.interfaceTPConfigs,
355+
}
356+
);
357+
}
358+
359+
// 生成枚举翻译
360+
const enums = filter(this.interfaceTPConfigs, (item) => item.isEnum);
361+
if (!isGenJavaScript && !isOnlyGenTypeScriptType && !isEmpty(enums)) {
362+
this.genFileFromTemplate(
363+
`${displayEnumLabelFileName}.ts`,
364+
TypescriptFileType.displayEnumLabel,
365+
{
366+
list: enums,
367+
namespace: this.config.namespace,
368+
interfaceFileName: interfaceFileName,
369+
}
370+
);
371+
}
372+
373+
const displayTypeLabels = filter(
374+
this.interfaceTPConfigs,
375+
(item) => !item.isEnum
376+
);
377+
// 生成 type 翻译
378+
if (
379+
!isGenJavaScript &&
380+
!isOnlyGenTypeScriptType &&
381+
this.config.isDisplayTypeLabel &&
382+
!isEmpty(displayTypeLabels)
383+
) {
384+
this.genFileFromTemplate(
385+
`${displayTypeLabelFileName}.ts`,
386+
TypescriptFileType.displayTypeLabel,
387+
{
388+
list: displayTypeLabels,
389+
namespace: this.config.namespace,
390+
interfaceFileName: interfaceFileName,
391+
}
392+
);
393+
}
394+
395395
if (
396396
!isOnlyGenTypeScriptType &&
397397
this.config.isGenJsonSchemas &&
@@ -443,7 +443,7 @@ export default class ServiceGenerator {
443443

444444
private getInterfaceTPConfigs() {
445445
const schemas = this.openAPIData.components?.schemas;
446-
const lastTypes: Array<ITypeItem> = [];
446+
const lastTypes: Array<ITypeItem> = this.interfaceTPConfigs;
447447
const includeTags = this.config?.includeTags || [];
448448

449449
// 强行替换掉请求参数params的类型,生成方法对应的 xxxxParams 类型
@@ -637,6 +637,28 @@ export default class ServiceGenerator {
637637
tmpFunctionRD[functionName] = 1;
638638
}
639639

640+
if (body?.isAnonymous) {
641+
const bodyName = upperFirst(`${functionName}Body`);
642+
this.interfaceTPConfigs.push({
643+
typeName: bodyName,
644+
type: body?.type,
645+
isEnum: false,
646+
props: [],
647+
});
648+
body.type = `${this.config.namespace}.${bodyName}`;
649+
}
650+
651+
if (response?.isAnonymous) {
652+
const responseName = upperFirst(`${functionName}Response`);
653+
this.interfaceTPConfigs.push({
654+
typeName: responseName,
655+
type: response?.type,
656+
isEnum: false,
657+
props: [],
658+
});
659+
response.type = `${this.config.namespace}.${responseName}`;
660+
}
661+
640662
let formattedPath = newApi.path.replace(
641663
/:([^/]*)|{([^}]*)}/gi,
642664
(_, str, str2) => `$\{${str || str2}}`
@@ -909,49 +931,21 @@ export default class ServiceGenerator {
909931
// 如果 requestBody 有 required 属性,则正常展示;如果没有,默认非必填
910932
const required =
911933
typeof requestBody?.required === 'boolean' ? requestBody.required : false;
912-
913-
if (schema.type === 'object' && schema.properties) {
914-
const propertiesList = keys(schema.properties)
915-
.map((propertyKey) => {
916-
const propertyObj = schema.properties[
917-
propertyKey
918-
] as ArraySchemaObject;
919-
920-
if (
921-
propertyObj &&
922-
![SchemaObjectFormat.binary, SchemaObjectFormat.base64].includes(
923-
propertyObj.format as SchemaObjectFormat
924-
) &&
925-
!isBinaryArraySchemaObject(propertyObj)
926-
) {
927-
// 测试了很多用例,很少有用例走到这里
928-
return {
929-
key: propertyKey,
930-
schema: {
931-
...(propertyObj as ArraySchemaObject),
932-
type: this.getType(propertyObj, this.config.namespace),
933-
required: schema.required?.includes(propertyKey) ?? false,
934-
},
935-
};
936-
}
937-
938-
return null;
939-
})
940-
.filter((p) => p);
941-
942-
return {
943-
mediaType,
944-
...schema,
945-
required,
946-
propertiesList,
947-
};
948-
}
949-
950-
return {
934+
const bodySchema = {
951935
mediaType,
952936
required,
953937
type: this.getType(schema, this.config.namespace),
938+
isAnonymous: false,
954939
};
940+
941+
// 具名 body 场景
942+
if (isReferenceObject(schema)) {
943+
bodySchema.type = `${this.config.namespace}.${bodySchema.type}`;
944+
} else {
945+
bodySchema.isAnonymous = true;
946+
}
947+
948+
return bodySchema;
955949
}
956950

957951
private getFileTP(requestBody: RequestBodyObject) {
@@ -1011,6 +1005,7 @@ export default class ServiceGenerator {
10111005
const defaultResponse = {
10121006
mediaType: '*/*',
10131007
type: 'unknown',
1008+
isAnonymous: false,
10141009
};
10151010

10161011
if (!response) {
@@ -1029,6 +1024,11 @@ export default class ServiceGenerator {
10291024

10301025
let schema = (resContent[mediaType].schema ||
10311026
DEFAULT_SCHEMA) as SchemaObject;
1027+
const responseSchema = {
1028+
mediaType,
1029+
type: 'unknown',
1030+
isAnonymous: false,
1031+
};
10321032

10331033
if (isReferenceObject(schema)) {
10341034
const refName = getLastRefName(schema.$ref);
@@ -1041,19 +1041,20 @@ export default class ServiceGenerator {
10411041
resContent[mediaType].schema ||
10421042
DEFAULT_SCHEMA) as SchemaObject;
10431043
}
1044+
1045+
responseSchema.type = `${this.config.namespace}.${this.getType(schema, this.config.namespace)}`;
10441046
}
10451047

10461048
if (isSchemaObject(schema)) {
10471049
keys(schema.properties).map((fieldName) => {
10481050
schema.properties[fieldName]['required'] =
10491051
schema.required?.includes(fieldName) ?? false;
10501052
});
1053+
responseSchema.isAnonymous = true;
1054+
responseSchema.type = this.getType(schema, this.config.namespace);
10511055
}
10521056

1053-
return {
1054-
mediaType,
1055-
type: this.getType(schema, this.config.namespace),
1056-
};
1057+
return responseSchema;
10571058
}
10581059

10591060
private getParamsTP(

src/generator/util.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export function getDefaultType(
135135
}
136136

137137
if (isReferenceObject(schemaObject)) {
138-
return [namespace, getRefName(schemaObject)].filter((s) => s).join('.');
138+
return getRefName(schemaObject);
139+
// return [namespace, getRefName(schemaObject)].filter((s) => s).join('.');
139140
}
140141

141142
let type = schemaObject?.type;
@@ -278,8 +279,7 @@ export function getDefaultType(
278279
* 错误的继续保留字符串。
279280
* */
280281
return `
281-
${property.description ? `/** ${property.description} */` : ''}
282-
'${key}'${required ? '' : '?'}: ${getDefaultType(
282+
${property.description ? `/** ${property.description} */\n` : ''}'${key}'${required ? '' : '?'}: ${getDefaultType(
283283
property,
284284
namespace
285285
)}; `;

templates/reactQuery.njk

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,7 @@ import * as apis from './{{ className }}';
4242
{%- endif -%}
4343

4444
{%- if api.body -%}
45-
body:
46-
{% if api.body.propertiesList %}{
47-
{%- for prop in api.body.propertiesList %}
48-
{% if prop.schema.description -%}
49-
/** {{ prop.schema.description }} */
50-
{% endif -%}
51-
'{{ prop.key }}'{{ "?" if not prop.schema.required }}: {{ prop.schema.type }},
52-
{%- endfor %}
53-
}
54-
{%- else -%}
55-
{{ api.body.type }}
56-
{%- endif -%}
45+
body: {{ api.body.type }}
5746
{{ ";" if api.file }}
5847
{%- endif %}
5948

templates/serviceController.njk

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,7 @@
5656
{%- endif -%}
5757

5858
{%- if api.body -%}
59-
body:
60-
{% if api.body.propertiesList %}
61-
{
62-
{%- for prop in api.body.propertiesList %}
63-
{% if prop.schema.description -%}
64-
/** {{ prop.schema.description }} */
65-
{% endif -%}
66-
'{{ prop.key }}'{{ "?" if not prop.schema.required }}: {{ prop.schema.type }},
67-
{%- endfor %}
68-
}
69-
{%- else -%}
70-
{{ api.body.type }}
71-
{%- endif -%}
59+
body: {{ api.body.type }}
7260
{{ ";" if api.file }}
7361
{%- endif %}
7462

test/example-files/openapi-response-desc.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,34 @@
5151
}
5252
}
5353
],
54+
"requestBody": {
55+
"description": "User data to update",
56+
"required": true,
57+
"content": {
58+
"application/json": {
59+
"schema": {
60+
"type": "object",
61+
"properties": {
62+
"name": {
63+
"type": "string",
64+
"example": "John Doe"
65+
},
66+
"email": {
67+
"type": "string",
68+
"format": "email",
69+
"example": "john@example.com"
70+
},
71+
"age": {
72+
"type": "integer",
73+
"minimum": 0,
74+
"example": 30
75+
}
76+
},
77+
"required": ["name", "email"]
78+
}
79+
}
80+
}
81+
},
5482
"responses": {
5583
"200": {
5684
"description": "",

test/test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ export async function ${api.functionName}(${api.body ? `data: ${api.body.type}`
247247
serversPath: './apis/openapi-response-desc',
248248
});
249249

250+
// 测试生成 匿名response => 具名response
251+
await openAPI.generateService({
252+
schemaPath: `${__dirname}/example-files/openapi-response-desc.json`,
253+
serversPath: './apis/openapi-anonymous-response',
254+
});
255+
250256
// check 文件生成
251257
const fileControllerStr = fs.readFileSync(
252258
path.join(__dirname, 'apis/file/fileController.ts'),

0 commit comments

Comments
 (0)