Skip to content

Commit

Permalink
swagger-cli validateがvalidとなるapi.jsonを作れるようにする (misskey-dev#12403)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Nov 22, 2023
1 parent a4f8863 commit c284d41
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### Server
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402

## 2023.11.1

Expand Down
8 changes: 8 additions & 0 deletions packages/backend/generate_api_json.js
Original file line number Diff line number Diff line change
@@ -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');
3 changes: 2 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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-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-and-coverage": "pnpm jest-and-coverage"
"test-and-coverage": "pnpm jest-and-coverage",
"generate-api-json": "node ./generate_api_json.js"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ export const meta = {
requireCredential: false,

res: {
oneOf: [{
type: 'object',
ref: 'FederationInstance',
}, {
type: 'null',
}],
type: 'object',
optional: false, nullable: true,
ref: 'FederationInstance',
},
} as const;

Expand Down
42 changes: 29 additions & 13 deletions packages/backend/src/server/api/openapi/gen-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

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 { schemas, convertSchemaToOpenApiSchema } from './schemas.js';

Expand Down Expand Up @@ -33,16 +33,17 @@ export function genOpenapiSpec(config: Config) {
schemas: schemas,

securitySchemes: {
ApiKeyAuth: {
type: 'apiKey',
in: 'body',
name: 'i',
bearerAuth: {
type: 'http',
scheme: 'bearer',
},
},
},
};

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;

if (endpoint.meta.errors) {
Expand Down Expand Up @@ -79,6 +80,13 @@ export function genOpenapiSpec(config: Config) {
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 = {
operationId: endpoint.name,
summary: endpoint.name,
Expand All @@ -92,17 +100,19 @@ export function genOpenapiSpec(config: Config) {
} : {}),
...(endpoint.meta.requireCredential ? {
security: [{
ApiKeyAuth: [],
bearerAuth: [],
}],
} : {}),
requestBody: {
required: true,
content: {
[requestType]: {
schema,
...(hasBody ? {
requestBody: {
required: true,
content: {
[requestType]: {
schema,
},
},
},
},
} : {}),
responses: {
...(endpoint.meta.res ? {
'200': {
Expand All @@ -118,6 +128,11 @@ export function genOpenapiSpec(config: Config) {
description: 'OK (without any results)',
},
}),
...(endpoint.meta.res?.optional === true || endpoint.meta.res?.nullable === true ? {
'204': {
description: 'OK (without any results)',
},
} : {}),
'400': {
description: 'Client error',
content: {
Expand Down Expand Up @@ -190,6 +205,7 @@ export function genOpenapiSpec(config: Config) {
};

spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? { get: info } : {}),
post: info,
};
}
Expand Down
10 changes: 8 additions & 2 deletions packages/backend/src/server/api/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import type { Schema } from '@/misc/json-schema.js';
import { refs } from '@/misc/json-schema.js';

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) {
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)) {
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
Expand Down

0 comments on commit c284d41

Please sign in to comment.