fix(backend): use unique `operationId` in the OpenAPI schema (#15420)

* fix(backend): use unique `operationId` in the OpenAPI schema

* fix: read with UTF-8 encoding
This commit is contained in:
zyoshoka 2025-02-08 17:29:24 +09:00 committed by GitHub
parent a3cc865e11
commit 54fc232a23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 7 deletions

View File

@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
spec.paths['/' + endpoint.name] = { spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? { ...(endpoint.meta.allowGet ? {
get: info, get: {
...info,
operationId: 'get___' + info.operationId,
},
} : {}), } : {}),
post: info, post: {
...info,
operationId: 'post___' + info.operationId,
},
}; };
} }

View File

@ -1,8 +1,9 @@
import { mkdir, writeFile } from 'fs/promises'; import assert from 'assert';
import { mkdir, readFile, writeFile } from 'fs/promises';
import { OpenAPIV3_1 } from 'openapi-types'; import { OpenAPIV3_1 } from 'openapi-types';
import { toPascal } from 'ts-case-convert'; import { toPascal } from 'ts-case-convert';
import OpenAPIParser from '@readme/openapi-parser'; import OpenAPIParser from '@readme/openapi-parser';
import openapiTS from 'openapi-typescript'; import openapiTS, { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
async function generateBaseTypes( async function generateBaseTypes(
openApiDocs: OpenAPIV3_1.Document, openApiDocs: OpenAPIV3_1.Document,
@ -20,7 +21,29 @@ async function generateBaseTypes(
} }
lines.push(''); lines.push('');
const generatedTypes = await openapiTS(openApiJsonPath, { // NOTE: Align `operationId` of GET and POST to avoid duplication of type definitions
const openApi = JSON.parse(await readFile(openApiJsonPath, 'utf8')) as OpenAPI3;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
for (const [key, item] of Object.entries(openApi.paths!)) {
assert('post' in item);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
openApi.paths![key] = {
...('get' in item ? {
get: {
...item.get,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
operationId: ((item as PathItemObject).get as OperationObject).operationId!.replaceAll('get___', ''),
},
} : {}),
post: {
...item.post,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
operationId: ((item as PathItemObject).post as OperationObject).operationId!.replaceAll('post___', ''),
},
};
}
const generatedTypes = await openapiTS(openApi, {
exportType: true, exportType: true,
transform(schemaObject) { transform(schemaObject) {
if ('format' in schemaObject && schemaObject.format === 'binary') { if ('format' in schemaObject && schemaObject.format === 'binary') {
@ -78,7 +101,7 @@ async function generateEndpoints(
for (const operation of postPathItems) { for (const operation of postPathItems) {
const path = operation._path_; const path = operation._path_;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const operationId = operation.operationId!; const operationId = operation.operationId!.replaceAll('get___', '').replaceAll('post___', '');
const endpoint = new Endpoint(path); const endpoint = new Endpoint(path);
endpoints.push(endpoint); endpoints.push(endpoint);
@ -195,7 +218,7 @@ async function generateApiClientJSDoc(
for (const operation of postPathItems) { for (const operation of postPathItems) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const operationId = operation.operationId!; const operationId = operation.operationId!.replaceAll('get___', '').replaceAll('post___', '');
if (operation.description) { if (operation.description) {
endpoints.push({ endpoints.push({