adapt OpenAPI documentation generation to new error definitions

This commit is contained in:
Johann150 2022-10-27 19:59:11 +02:00
parent 1dd935dc0c
commit fb76843c19
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
4 changed files with 145 additions and 170 deletions

View file

@ -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',
},
},
},
},
};

View file

@ -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 = [];

View 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"
}

View file

@ -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'],