fix: api-docが開けない問題を修正 (#13132)

* refactor: 自己参照を使用している箇所に`selfRef`を持たせるように

* feat: スキーマ生成時に自己参照を含むかどうかを指定できるように

* fix: api.jsonにselfRefが含まれているのを修正

* refactor: 他の箇所と同様にselfRefの除去を行うように

* remove: 不要なimportを削除
This commit is contained in:
yukineko 2024-02-02 12:47:07 +09:00 committed by GitHub
parent 3499814498
commit d8bdbd53ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 43 additions and 39 deletions

View file

@ -3,6 +3,6 @@ import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
import { writeFileSync } from "node:fs"; import { writeFileSync } from "node:fs";
const config = loadConfig(); const config = loadConfig();
const spec = genOpenapiSpec(config); const spec = genOpenapiSpec(config, true);
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8'); writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');

View file

@ -119,6 +119,7 @@ export interface Schema extends OfSchema {
readonly example?: any; readonly example?: any;
readonly format?: string; readonly format?: string;
readonly ref?: keyof typeof refs; readonly ref?: keyof typeof refs;
readonly selfRef?: boolean;
readonly enum?: ReadonlyArray<string | null>; readonly enum?: ReadonlyArray<string | null>;
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
readonly maxLength?: number; readonly maxLength?: number;

View file

@ -53,6 +53,7 @@ const sectionBlockSchema = {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'PageBlock', ref: 'PageBlock',
selfRef: true,
}, },
}, },
}, },

View file

@ -6,9 +6,9 @@
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import endpoints, { IEndpoint } 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 { getSchemas, convertSchemaToOpenApiSchema } from './schemas.js';
export function genOpenapiSpec(config: Config) { export function genOpenapiSpec(config: Config, includeSelfRef = false) {
const spec = { const spec = {
openapi: '3.1.0', openapi: '3.1.0',
@ -30,7 +30,7 @@ export function genOpenapiSpec(config: Config) {
paths: {} as any, paths: {} as any,
components: { components: {
schemas: schemas, schemas: getSchemas(includeSelfRef),
securitySchemes: { securitySchemes: {
bearerAuth: { bearerAuth: {
@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) {
} }
} }
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res') : {}; const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res', includeSelfRef) : {};
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) {
} }
const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param') }; const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param', false) };
if (endpoint.meta.requireFile) { if (endpoint.meta.requireFile) {
schema.properties = { schema.properties = {

View file

@ -6,10 +6,10 @@
import type { Schema } from '@/misc/json-schema.js'; 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, type: 'param' | 'res') { export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any {
// optional, nullable, refはスキーマ定義に含まれないので分離しておく // optional, nullable, refはスキーマ定義に含まれないので分離しておく
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { optional, nullable, ref, ...res }: any = schema; const { optional, nullable, ref, selfRef, ...res }: any = schema;
if (schema.type === 'object' && schema.properties) { if (schema.type === 'object' && schema.properties) {
if (type === 'res') { if (type === 'res') {
@ -21,20 +21,20 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're
} }
for (const k of Object.keys(schema.properties)) { for (const k of Object.keys(schema.properties)) {
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type); res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type, includeSelfRef);
} }
} }
if (schema.type === 'array' && schema.items) { if (schema.type === 'array' && schema.items) {
res.items = convertSchemaToOpenApiSchema(schema.items, type); res.items = convertSchemaToOpenApiSchema(schema.items, type, includeSelfRef);
} }
for (const o of ['anyOf', 'oneOf', 'allOf'] as const) { for (const o of ['anyOf', 'oneOf', 'allOf'] as const) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type)); if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type, includeSelfRef));
} }
if (type === 'res' && schema.ref) { if (type === 'res' && schema.ref && (!schema.selfRef || includeSelfRef)) {
const $ref = `#/components/schemas/${schema.ref}`; const $ref = `#/components/schemas/${schema.ref}`;
if (schema.nullable || schema.optional) { if (schema.nullable || schema.optional) {
res.allOf = [{ $ref }]; res.allOf = [{ $ref }];
@ -54,35 +54,37 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're
return res; return res;
} }
export const schemas = { export function getSchemas(includeSelfRef: boolean) {
Error: { return {
type: 'object', Error: {
properties: { type: 'object',
error: { properties: {
type: 'object', error: {
description: 'An error object.', type: 'object',
properties: { description: 'An error object.',
code: { properties: {
type: 'string', code: {
description: 'An error code. Unique within the endpoint.', type: 'string',
}, description: 'An error code. Unique within the endpoint.',
message: { },
type: 'string', message: {
description: 'An error message.', type: 'string',
}, description: 'An error message.',
id: { },
type: 'string', id: {
format: 'uuid', type: 'string',
description: 'An error ID. This ID is static.', format: 'uuid',
description: 'An error ID. This ID is static.',
},
}, },
required: ['code', 'id', 'message'],
}, },
required: ['code', 'id', 'message'],
}, },
required: ['error'],
}, },
required: ['error'],
},
...Object.fromEntries( ...Object.fromEntries(
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res')]), Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res', includeSelfRef)]),
), ),
}; };
}