egirlskey/packages/backend/src/server/api/openapi/gen-spec.ts

236 lines
5.9 KiB
TypeScript
Raw Normal View History

import endpoints from '../endpoints';
2019-02-23 19:08:08 +00:00
import { Context } from 'cafy';
import config from '@/config/index';
import { errors as basicErrors } from './errors';
import { schemas, convertSchemaToOpenApiSchema } from './schemas';
2019-02-23 19:08:08 +00:00
export function genOpenapiSpec(lang = 'ja-JP') {
const spec = {
openapi: '3.0.0',
info: {
version: 'v1',
title: 'Misskey API',
2021-12-09 14:58:30 +00:00
'x-logo': { url: '/static-assets/api-doc.png' },
2019-02-23 19:08:08 +00:00
},
externalDocs: {
description: 'Repository',
2021-12-09 14:58:30 +00:00
url: 'https://github.com/misskey-dev/misskey',
2019-02-23 19:08:08 +00:00
},
servers: [{
2021-12-09 14:58:30 +00:00
url: config.apiUrl,
2019-02-23 19:08:08 +00:00
}],
paths: {} as any,
components: {
schemas: schemas,
securitySchemes: {
ApiKeyAuth: {
type: 'apiKey',
in: 'body',
2021-12-09 14:58:30 +00:00
name: 'i',
},
},
},
2019-02-23 19:08:08 +00:00
};
2019-02-24 08:48:54 +00:00
function genProps(props: { [key: string]: Context; }) {
2019-02-23 19:08:08 +00:00
const properties = {} as any;
2019-02-24 08:48:54 +00:00
for (const [k, v] of Object.entries(props)) {
properties[k] = genProp(v);
2019-02-23 19:08:08 +00:00
}
return properties;
}
2019-02-24 08:48:54 +00:00
function genProp(param: Context): any {
2019-02-23 19:08:08 +00:00
const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
return {
2019-02-24 08:48:54 +00:00
description: (param.data || {}).desc,
default: (param.data || {}).default,
2019-02-24 19:18:09 +00:00
deprecated: (param.data || {}).deprecated,
2019-02-24 08:48:54 +00:00
...((param.data || {}).default ? { default: (param.data || {}).default } : {}),
2019-02-23 19:08:08 +00:00
type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
...(param.name === 'ID' ? { example: 'xxxxxxxxxx', format: 'id' } : {}),
2019-02-23 19:08:08 +00:00
nullable: param.isNullable,
...(param.name === 'String' ? {
...((param as any).enum ? { enum: (param as any).enum } : {}),
...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
} : {}),
...(param.name === 'Number' ? {
...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
} : {}),
...(param.name === 'Object' ? {
...(required.length > 0 ? { required } : {}),
2021-12-09 14:58:30 +00:00
properties: (param as any).props ? genProps((param as any).props) : {},
2019-02-23 19:08:08 +00:00
} : {}),
...(param.name === 'Array' ? {
2021-12-09 14:58:30 +00:00
items: (param as any).ctx ? genProp((param as any).ctx) : {},
} : {}),
2019-02-23 19:08:08 +00:00
};
}
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
const porops = {} as any;
const errors = {} as any;
if (endpoint.meta.errors) {
for (const e of Object.values(endpoint.meta.errors)) {
errors[e.code] = {
value: {
2021-12-09 14:58:30 +00:00
error: e,
},
2019-02-23 19:08:08 +00:00
};
}
}
if (endpoint.meta.params) {
2019-02-24 08:35:19 +00:00
for (const [k, v] of Object.entries(endpoint.meta.params)) {
2019-02-24 08:48:54 +00:00
if (v.validator.data == null) v.validator.data = {};
if (v.desc) v.validator.data.desc = v.desc[lang];
2019-02-24 19:18:09 +00:00
if (v.deprecated) v.validator.data.deprecated = v.deprecated;
2019-02-24 08:48:54 +00:00
if (v.default) v.validator.data.default = v.default;
2019-02-24 08:35:19 +00:00
porops[k] = v.validator;
2019-02-23 19:08:08 +00:00
}
}
const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
2019-02-23 19:08:08 +00:00
2019-02-25 00:37:22 +00:00
let desc = (endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.') + '\n\n';
desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
if (endpoint.meta.kind) {
const kind = endpoint.meta.kind;
desc += ` / **Permission**: *${kind}*`;
}
2019-02-25 00:37:22 +00:00
2019-02-23 19:08:08 +00:00
const info = {
operationId: endpoint.name,
summary: endpoint.name,
2019-02-25 00:37:22 +00:00
description: desc,
2019-02-23 19:08:08 +00:00
externalDocs: {
description: 'Source code',
2021-12-09 14:58:30 +00:00
url: `https://github.com/misskey-dev/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`,
2019-02-23 19:08:08 +00:00
},
...(endpoint.meta.tags ? {
2021-12-09 14:58:30 +00:00
tags: [endpoint.meta.tags[0]],
2019-02-23 19:08:08 +00:00
} : {}),
...(endpoint.meta.requireCredential ? {
security: [{
2021-12-09 14:58:30 +00:00
ApiKeyAuth: [],
}],
2019-02-23 19:08:08 +00:00
} : {}),
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
...(required.length > 0 ? { required } : {}),
2021-12-09 14:58:30 +00:00
properties: endpoint.meta.params ? genProps(porops) : {},
},
},
},
2019-02-23 19:08:08 +00:00
},
responses: {
...(endpoint.meta.res ? {
'200': {
description: 'OK (with results)',
content: {
'application/json': {
2021-12-09 14:58:30 +00:00
schema: resSchema,
},
},
},
2019-02-23 19:08:08 +00:00
} : {
'204': {
description: 'OK (without any results)',
2021-12-09 14:58:30 +00:00
},
2019-02-23 19:08:08 +00:00
}),
'400': {
description: 'Client error',
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: { ...errors, ...basicErrors['400'] },
},
},
2019-02-23 19:08:08 +00:00
},
'401': {
description: 'Authentication error',
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: basicErrors['401'],
},
},
2019-02-23 19:08:08 +00:00
},
'403': {
description: 'Forbidden error',
2019-02-23 19:08:08 +00:00
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: basicErrors['403'],
},
},
2019-02-23 19:08:08 +00:00
},
'418': {
description: 'I\'m Ai',
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: basicErrors['418'],
},
},
2019-02-23 19:08:08 +00:00
},
...(endpoint.meta.limit ? {
'429': {
description: 'To many requests',
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: basicErrors['429'],
},
},
},
2019-02-23 19:08:08 +00:00
} : {}),
'500': {
description: 'Internal server error',
content: {
'application/json': {
schema: {
2021-12-09 14:58:30 +00:00
$ref: '#/components/schemas/Error',
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
examples: basicErrors['500'],
},
},
2019-02-23 19:08:08 +00:00
},
2021-12-09 14:58:30 +00:00
},
2019-02-23 19:08:08 +00:00
};
spec.paths['/' + endpoint.name] = {
2021-12-09 14:58:30 +00:00
post: info,
2019-02-23 19:08:08 +00:00
};
}
return spec;
}