swagger-cli validateがvalidとなるapi.jsonを作れるようにする (#12403)
* api.jsonがswagger-cli validateでエラーにならないように生成ロジックを修正 * フィールドの消し方に不備があったので変更 * バックエンドを起動しなくてもapi.jsonを作れるようにした * deepCopyしてからレスポンス部分を作るようにした * fix CHANGELOG.md * securitySchemesの定義を復活&ApiCallServiceの実装的にベアラトークンなのでその形で * bodyが無い(空オブジェクト)のときはrequestBodyを描画しないようにする * allowGetがtrueな項目はget用の記載も作成 --------- Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
a4f8863786
commit
c284d41b5b
6 changed files with 51 additions and 22 deletions
|
@ -25,6 +25,7 @@
|
||||||
### Server
|
### Server
|
||||||
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
|
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
|
||||||
- Fix: ロールタイムラインが保存されない問題を修正
|
- Fix: ロールタイムラインが保存されない問題を修正
|
||||||
|
- Fix: api.jsonの生成ロジックを改善 #12402
|
||||||
|
|
||||||
## 2023.11.1
|
## 2023.11.1
|
||||||
|
|
||||||
|
|
8
packages/backend/generate_api_json.js
Normal file
8
packages/backend/generate_api_json.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { loadConfig } from './built/config.js'
|
||||||
|
import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
|
||||||
|
import { writeFileSync } from "node:fs";
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const spec = genOpenapiSpec(config);
|
||||||
|
|
||||||
|
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
|
@ -23,7 +23,8 @@
|
||||||
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
|
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
|
||||||
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
||||||
"test": "pnpm jest",
|
"test": "pnpm jest",
|
||||||
"test-and-coverage": "pnpm jest-and-coverage"
|
"test-and-coverage": "pnpm jest-and-coverage",
|
||||||
|
"generate-api-json": "node ./generate_api_json.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
|
|
|
@ -16,12 +16,9 @@ export const meta = {
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
oneOf: [{
|
type: 'object',
|
||||||
type: 'object',
|
optional: false, nullable: true,
|
||||||
ref: 'FederationInstance',
|
ref: 'FederationInstance',
|
||||||
}, {
|
|
||||||
type: 'null',
|
|
||||||
}],
|
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import endpoints from '../endpoints.js';
|
import endpoints, { IEndpoint } from '../endpoints.js';
|
||||||
import { errors as basicErrors } from './errors.js';
|
import { errors as basicErrors } from './errors.js';
|
||||||
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||||
|
|
||||||
|
@ -33,16 +33,17 @@ export function genOpenapiSpec(config: Config) {
|
||||||
schemas: schemas,
|
schemas: schemas,
|
||||||
|
|
||||||
securitySchemes: {
|
securitySchemes: {
|
||||||
ApiKeyAuth: {
|
bearerAuth: {
|
||||||
type: 'apiKey',
|
type: 'http',
|
||||||
in: 'body',
|
scheme: 'bearer',
|
||||||
name: 'i',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
|
// 書き換えたりするのでディープコピーしておく。そのまま編集するとメモリ上の値が汚れて次回以降の出力に影響する
|
||||||
|
const copiedEndpoints = JSON.parse(JSON.stringify(endpoints)) as IEndpoint[];
|
||||||
|
for (const endpoint of copiedEndpoints.filter(ep => !ep.meta.secure)) {
|
||||||
const errors = {} as any;
|
const errors = {} as any;
|
||||||
|
|
||||||
if (endpoint.meta.errors) {
|
if (endpoint.meta.errors) {
|
||||||
|
@ -79,6 +80,13 @@ export function genOpenapiSpec(config: Config) {
|
||||||
schema.required = [...schema.required ?? [], 'file'];
|
schema.required = [...schema.required ?? [], 'file'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schema.required && schema.required.length <= 0) {
|
||||||
|
// 空配列は許可されない
|
||||||
|
schema.required = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1);
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
operationId: endpoint.name,
|
operationId: endpoint.name,
|
||||||
summary: endpoint.name,
|
summary: endpoint.name,
|
||||||
|
@ -92,17 +100,19 @@ export function genOpenapiSpec(config: Config) {
|
||||||
} : {}),
|
} : {}),
|
||||||
...(endpoint.meta.requireCredential ? {
|
...(endpoint.meta.requireCredential ? {
|
||||||
security: [{
|
security: [{
|
||||||
ApiKeyAuth: [],
|
bearerAuth: [],
|
||||||
}],
|
}],
|
||||||
} : {}),
|
} : {}),
|
||||||
requestBody: {
|
...(hasBody ? {
|
||||||
required: true,
|
requestBody: {
|
||||||
content: {
|
required: true,
|
||||||
[requestType]: {
|
content: {
|
||||||
schema,
|
[requestType]: {
|
||||||
|
schema,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
} : {}),
|
||||||
responses: {
|
responses: {
|
||||||
...(endpoint.meta.res ? {
|
...(endpoint.meta.res ? {
|
||||||
'200': {
|
'200': {
|
||||||
|
@ -118,6 +128,11 @@ export function genOpenapiSpec(config: Config) {
|
||||||
description: 'OK (without any results)',
|
description: 'OK (without any results)',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
...(endpoint.meta.res?.optional === true || endpoint.meta.res?.nullable === true ? {
|
||||||
|
'204': {
|
||||||
|
description: 'OK (without any results)',
|
||||||
|
},
|
||||||
|
} : {}),
|
||||||
'400': {
|
'400': {
|
||||||
description: 'Client error',
|
description: 'Client error',
|
||||||
content: {
|
content: {
|
||||||
|
@ -190,6 +205,7 @@ export function genOpenapiSpec(config: Config) {
|
||||||
};
|
};
|
||||||
|
|
||||||
spec.paths['/' + endpoint.name] = {
|
spec.paths['/' + endpoint.name] = {
|
||||||
|
...(endpoint.meta.allowGet ? { get: info } : {}),
|
||||||
post: info,
|
post: info,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,16 @@ import type { Schema } from '@/misc/json-schema.js';
|
||||||
import { refs } from '@/misc/json-schema.js';
|
import { refs } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||||
const res: any = schema;
|
// optional, refはスキーマ定義に含まれないので分離しておく
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { optional, ref, ...res }: any = schema;
|
||||||
|
|
||||||
if (schema.type === 'object' && schema.properties) {
|
if (schema.type === 'object' && schema.properties) {
|
||||||
res.required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
||||||
|
if (required.length > 0) {
|
||||||
|
// 空配列は許可されない
|
||||||
|
res.required = required;
|
||||||
|
}
|
||||||
|
|
||||||
for (const k of Object.keys(schema.properties)) {
|
for (const k of Object.keys(schema.properties)) {
|
||||||
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
|
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
|
||||||
|
|
Loading…
Reference in a new issue