forked from FoundKeyGang/FoundKey
adapt OpenAPI documentation generation to new error definitions
This commit is contained in:
parent
1dd935dc0c
commit
fb76843c19
4 changed files with 145 additions and 170 deletions
|
@ -1,69 +0,0 @@
|
||||||
|
|
||||||
export const errors = {
|
|
||||||
'400': {
|
|
||||||
'INVALID_PARAM': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'Invalid param.',
|
|
||||||
code: 'INVALID_PARAM',
|
|
||||||
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'401': {
|
|
||||||
'CREDENTIAL_REQUIRED': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'Credential required.',
|
|
||||||
code: 'CREDENTIAL_REQUIRED',
|
|
||||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'403': {
|
|
||||||
'AUTHENTICATION_FAILED': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'Authentication failed. Please ensure your token is correct.',
|
|
||||||
code: 'AUTHENTICATION_FAILED',
|
|
||||||
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'418': {
|
|
||||||
'I_AM_A_TEAPOT': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'I am a teapot.',
|
|
||||||
code: 'I_AM_A_TEAPOT',
|
|
||||||
id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'429': {
|
|
||||||
'RATE_LIMIT_EXCEEDED': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'Rate limit exceeded. Please try again later.',
|
|
||||||
code: 'RATE_LIMIT_EXCEEDED',
|
|
||||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'500': {
|
|
||||||
'INTERNAL_ERROR': {
|
|
||||||
value: {
|
|
||||||
error: {
|
|
||||||
message: 'Internal error occurred. Please contact us if the error persists.',
|
|
||||||
code: 'INTERNAL_ERROR',
|
|
||||||
id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,7 +1,8 @@
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
|
import { errors as errorDefinitions } from '../error.js';
|
||||||
import endpoints from '../endpoints.js';
|
import endpoints from '../endpoints.js';
|
||||||
import { errors as basicErrors } from './errors.js';
|
|
||||||
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||||
|
import { httpCodes } from './http-codes.js';
|
||||||
|
|
||||||
export function genOpenapiSpec() {
|
export function genOpenapiSpec() {
|
||||||
const spec = {
|
const spec = {
|
||||||
|
@ -43,19 +44,75 @@ export function genOpenapiSpec() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
|
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
|
||||||
const errors = {} as any;
|
// generate possible responses, first starting with errors
|
||||||
|
const responses = [
|
||||||
|
// general error codes that can always happen
|
||||||
|
'INVALID_PARAM',
|
||||||
|
'INTERNAL_ERROR',
|
||||||
|
// error codes that happen only if authentication is required
|
||||||
|
...(!endpoint.meta.requireCredential ? [] : [
|
||||||
|
'ACCESS_DENIED',
|
||||||
|
'AUTHENTICATION_REQUIRED',
|
||||||
|
'AUTHENTICATION_FAILED',
|
||||||
|
'SUSPENDED',
|
||||||
|
]),
|
||||||
|
// error codes that happen only if a rate limit is defined
|
||||||
|
...(!endpoint.meta.limit ? [] : [
|
||||||
|
'RATE_LIMIT_EXCEEDED',
|
||||||
|
]),
|
||||||
|
// error codes that happen only if a file is required
|
||||||
|
...(!endpoint.meta.requireFile ? [] : [
|
||||||
|
'FILE_REQUIRED',
|
||||||
|
]),
|
||||||
|
// endpoint specific error codes
|
||||||
|
...(endpoint.meta.errors ?? []),
|
||||||
|
]
|
||||||
|
.reduce((acc, code) => {
|
||||||
|
const { message, httpStatusCode } = errorDefinitions[code];
|
||||||
|
const httpCode = httpStatusCode.toString();
|
||||||
|
|
||||||
if (endpoint.meta.errors) {
|
if(!(httpCode in acc)) {
|
||||||
for (const e of Object.values(endpoint.meta.errors)) {
|
acc[httpCode] = {
|
||||||
errors[e.code] = {
|
description: httpCodes[httpCode],
|
||||||
value: {
|
content: {
|
||||||
error: e,
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
'$ref': '#/components/schemas/Error',
|
||||||
|
},
|
||||||
|
examples: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
|
acc[httpCode].content['application/json'].examples[code] = {
|
||||||
|
value: {
|
||||||
|
error: {
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
endpoint: endpoint.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// add successful response
|
||||||
|
if (endpoint.meta.res) {
|
||||||
|
responses['200'] = {
|
||||||
|
description: 'OK',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: convertSchemaToOpenApiSchema(endpoint.meta.res),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
responses['204'] = {
|
||||||
|
description: 'No Content',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
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';
|
||||||
desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
|
desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
|
||||||
|
@ -107,90 +164,7 @@ export function genOpenapiSpec() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
responses: {
|
responses,
|
||||||
...(endpoint.meta.res ? {
|
|
||||||
'200': {
|
|
||||||
description: 'OK (with results)',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: resSchema,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} : {
|
|
||||||
'204': {
|
|
||||||
description: 'OK (without any results)',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'400': {
|
|
||||||
description: 'Client error',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: { ...errors, ...basicErrors['400'] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'401': {
|
|
||||||
description: 'Authentication error',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: basicErrors['401'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'403': {
|
|
||||||
description: 'Forbidden error',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: basicErrors['403'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'418': {
|
|
||||||
description: 'I\'m Ai',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: basicErrors['418'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(endpoint.meta.limit ? {
|
|
||||||
'429': {
|
|
||||||
description: 'Too many requests',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: basicErrors['429'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} : {}),
|
|
||||||
'500': {
|
|
||||||
description: 'Internal server error',
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
schema: {
|
|
||||||
$ref: '#/components/schemas/Error',
|
|
||||||
},
|
|
||||||
examples: basicErrors['500'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const path = {
|
const path = {
|
||||||
|
@ -200,6 +174,7 @@ export function genOpenapiSpec() {
|
||||||
path.get = { ...info };
|
path.get = { ...info };
|
||||||
// API Key authentication is not permitted for GET requests
|
// API Key authentication is not permitted for GET requests
|
||||||
path.get.security = path.get.security.filter(elem => !Object.prototype.hasOwnProperty.call(elem, 'ApiKeyAuth'));
|
path.get.security = path.get.security.filter(elem => !Object.prototype.hasOwnProperty.call(elem, 'ApiKeyAuth'));
|
||||||
|
|
||||||
// fix the way parameters are passed
|
// fix the way parameters are passed
|
||||||
delete path.get.requestBody;
|
delete path.get.requestBody;
|
||||||
path.get.parameters = [];
|
path.get.parameters = [];
|
||||||
|
|
67
packages/backend/src/server/api/openapi/http-codes.ts
Normal file
67
packages/backend/src/server/api/openapi/http-codes.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
export const httpCodes: Record<string, string> = {
|
||||||
|
"100": "Continue",
|
||||||
|
"101": "Switching Protocols",
|
||||||
|
"102": "Processing",
|
||||||
|
"103": "Early Hints",
|
||||||
|
"200": "OK",
|
||||||
|
"201": "Created",
|
||||||
|
"202": "Accepted",
|
||||||
|
"203": "Non-Authoritative Information",
|
||||||
|
"204": "No Content",
|
||||||
|
"205": "Reset Content",
|
||||||
|
"206": "Partial Content",
|
||||||
|
"207": "Multi-Status",
|
||||||
|
"208": "Already Reported",
|
||||||
|
"226": "IM Used",
|
||||||
|
"300": "Multiple Choices",
|
||||||
|
"301": "Moved Permanently",
|
||||||
|
"302": "Found",
|
||||||
|
"303": "See Other",
|
||||||
|
"304": "Not Modified",
|
||||||
|
"305": "Use Proxy",
|
||||||
|
"307": "Temporary Redirect",
|
||||||
|
"308": "Permanent Redirect",
|
||||||
|
"400": "Bad Request",
|
||||||
|
"401": "Unauthorized",
|
||||||
|
"402": "Payment Required",
|
||||||
|
"403": "Forbidden",
|
||||||
|
"404": "Not Found",
|
||||||
|
"405": "Method Not Allowed",
|
||||||
|
"406": "Not Acceptable",
|
||||||
|
"407": "Proxy Authentication Required",
|
||||||
|
"408": "Request Timeout",
|
||||||
|
"409": "Conflict",
|
||||||
|
"410": "Gone",
|
||||||
|
"411": "Length Required",
|
||||||
|
"412": "Precondition Failed",
|
||||||
|
"413": "Content Too Large",
|
||||||
|
"414": "URI Too Long",
|
||||||
|
"415": "Unsupported Media Type",
|
||||||
|
"416": "Range Not Satisfiable",
|
||||||
|
"417": "Expectation Failed",
|
||||||
|
"418": "I'm a Teapot",
|
||||||
|
"421": "Misdirected Request",
|
||||||
|
"422": "Unprocessable Content",
|
||||||
|
"423": "Locked",
|
||||||
|
"424": "Failed Dependency",
|
||||||
|
"425": "Too Early",
|
||||||
|
"426": "Upgrade Required",
|
||||||
|
"427": "Unassigned",
|
||||||
|
"428": "Precondition Required",
|
||||||
|
"429": "Too Many Requests",
|
||||||
|
"430": "Unassigned",
|
||||||
|
"431": "Request Header Fields Too Large",
|
||||||
|
"451": "Unavailable For Legal Reasons",
|
||||||
|
"500": "Internal Server Error",
|
||||||
|
"501": "Not Implemented",
|
||||||
|
"502": "Bad Gateway",
|
||||||
|
"503": "Service Unavailable",
|
||||||
|
"504": "Gateway Timeout",
|
||||||
|
"505": "HTTP Version Not Supported",
|
||||||
|
"506": "Variant Also Negotiates",
|
||||||
|
"507": "Insufficient Storage",
|
||||||
|
"508": "Loop Detected",
|
||||||
|
"509": "Unassigned",
|
||||||
|
"510": "Not Extended",
|
||||||
|
"511": "Network Authentication Required"
|
||||||
|
}
|
|
@ -36,19 +36,21 @@ export const schemas = {
|
||||||
properties: {
|
properties: {
|
||||||
code: {
|
code: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'An error code. Unique within the endpoint.',
|
description: 'A machine and human readable error code.',
|
||||||
|
},
|
||||||
|
endpoint: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the API endpoint the error happened in.',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'An error message.',
|
description: 'A human readable error description in English.',
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: 'string',
|
|
||||||
format: 'uuid',
|
|
||||||
description: 'An error ID. This ID is static.',
|
|
||||||
},
|
},
|
||||||
|
info: {
|
||||||
|
description: 'Potentially more information, primarily intended for developers.',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
required: ['code', 'id', 'message'],
|
required: ['code', 'endpoint', 'message'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ['error'],
|
required: ['error'],
|
||||||
|
|
Loading…
Reference in a new issue