diff --git a/openapi.yaml b/openapi.yaml index 28343f2d..e0246576 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -228,7 +228,7 @@ paths: /help: get: - summary: Retrieve help information for the given command and any number of subcommands. + summary: Retrieve help information for the given command. operationId: help tags: - help @@ -237,7 +237,7 @@ paths: in: query style: form explode: true - description: The command (and any subcommands) for which help information is needed. Subcommands can be separated by '/' or space ' '. + description: The command for which help information is needed. required: true schema: type: string @@ -247,22 +247,21 @@ paths: content: application/json: schema: - type: object - properties: - message: - type: string + oneOf: + - $ref: '#/components/schemas/HelpListResponse' + - $ref: '#/components/schemas/HelpCommandResponse' "400": - description: Failed to get help. The given AsyncAPI command is not valid. + description: Bad request content: application/json: schema: - $ref: "#/components/schemas/Problem" - "422": - description: The given AsyncAPI document(s) is/are not valid due to invalid parameters in the request. + $ref: '#/components/schemas/Problem' + "404": + description: Command not found content: - application/json: + application/problem+json: schema: - $ref: "#/components/schemas/Problem" + $ref: '#/components/schemas/Problem' default: description: Unexpected problem. content: @@ -459,6 +458,25 @@ components: type: [object, string] description: The diff between the two AsyncAPI documents. + HelpListResponse: + type: object + properties: + commands: + type: array + items: + type: string + description: A list of all available commands. + HelpCommandResponse: + type: object + description: Detailed help information for a specific command. + properties: + command: + type: string + description: The name of the command. + description: + type: string + description: Detailed description of the command. + Problem: type: object properties: diff --git a/src/controllers/help.controller.ts b/src/controllers/help.controller.ts index de43fda7..8bb0a831 100644 --- a/src/controllers/help.controller.ts +++ b/src/controllers/help.controller.ts @@ -1,7 +1,8 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { Controller } from '../interfaces'; import axios from 'axios'; import yaml from 'js-yaml'; +import { ProblemException } from '../exceptions/problem.exception'; export const fetchCommands = async (user, repo) => { try { @@ -14,6 +15,12 @@ export const fetchCommands = async (user, repo) => { return yaml.load(response.data); } catch (error) { console.error(`Error fetching commands: ${error}`); + throw new ProblemException({ + type: 'fetch-commands-error', + title: 'Error Fetching Commands', + status: 500, + detail: error.message + }); } }; @@ -67,13 +74,18 @@ const buildResponseObject = (matchedPathKey: string, method: string, operationDe export class HelpController implements Controller { public basepath = '/help'; - public boot(): Router { + public async boot(): Promise { const router: Router = Router(); - router.get(/^\/help(\/.*)?$/, async (req: Request, res: Response) => { + router.get(/^\/help(\/.*)?$/, async (req: Request, res: Response, next: NextFunction) => { const commands = getCommandsFromRequest(req); - const openapiSpec: any = await fetchCommands('asyncapi', 'server-api'); - if (!openapiSpec) return res.status(500).json({ message: 'Error fetching help information' }); + let openapiSpec: any; + + try { + openapiSpec = await fetchCommands('asyncapi', 'server-api'); + } catch (err) { + return next(err); + } if (commands.length === 0) { const routes = Object.keys(openapiSpec.paths).map(path => ({ command: path.replace(/^\//, ''), url: `${this.basepath}${path}` })); @@ -82,12 +94,26 @@ export class HelpController implements Controller { const pathKeys = Object.keys(openapiSpec.paths); const matchedPathKey = getPathKeysMatchingCommands(commands, pathKeys); - if (!matchedPathKey) return res.status(404).json({ message: 'Failed to get help. The given AsyncAPI command is not valid.' }); + if (!matchedPathKey) { + return next(new ProblemException({ + type: 'invalid-asyncapi-command', + title: 'Invalid AsyncAPI Command', + status: 404, + detail: 'The given AsyncAPI command is not valid.' + })); + } const pathInfo = openapiSpec.paths[matchedPathKey]; const method = commands.length > 1 ? 'get' : 'post'; const operationDetails = pathInfo[method]; - if (!operationDetails) return res.status(404).json({ message: 'Failed to get help. The given AsyncAPI command is not valid.' }); + if (!operationDetails) { + return next(new ProblemException({ + type: 'invalid-asyncapi-command', + title: 'Invalid AsyncAPI Command', + status: 404, + detail: 'The given AsyncAPI command is not valid.' + })); + } const { requestBody } = operationDetails; let requestBodyComponent: any = {}; @@ -105,4 +131,4 @@ export class HelpController implements Controller { return router; } -} +} \ No newline at end of file diff --git a/src/controllers/tests/help.controller.test.ts b/src/controllers/tests/help.controller.test.ts index a139fd4b..c552032f 100644 --- a/src/controllers/tests/help.controller.test.ts +++ b/src/controllers/tests/help.controller.test.ts @@ -1,13 +1,13 @@ import request from 'supertest'; import { App } from '../../app'; +import { ProblemException } from '../../exceptions/problem.exception'; import { HelpController, fetchCommands } from '../help.controller'; jest.mock('../help.controller', () => ({ -...(jest.requireActual('../help.controller') as any), -fetchCommands: jest.fn() + ...(jest.requireActual('../help.controller') as any), + fetchCommands: jest.fn() })); - describe('HelpController', () => { let app; beforeAll(async () => { @@ -110,12 +110,16 @@ describe('HelpController', () => { const response = await request(app.getServer()) .get('/v1/help/invalidCommand') .expect(404); - - expect(response.body.message).toBe('Failed to get help. The given AsyncAPI command is not valid.'); + + expect(response.body).toEqual({ + type: 'https://api.asyncapi.com/problem/invalid-asyncapi-command', + title: 'Invalid AsyncAPI Command', + status: 404, + detail: 'The given AsyncAPI command is not valid.' + }); }); it('should return 404 error for a command without a method', async () => { - (fetchCommands as jest.Mock).mockResolvedValue({ paths: { "/someCommand": {} @@ -125,8 +129,13 @@ describe('HelpController', () => { const response = await request(app.getServer()) .get('/v1/help/someCommand') .expect(404); - - expect(response.body.message).toBe('Failed to get help. The given AsyncAPI command is not valid.'); + + expect(response.body).toEqual({ + type: 'https://api.asyncapi.com/problem/invalid-asyncapi-command', + title: 'Invalid AsyncAPI Command', + status: 404, + detail: 'The given AsyncAPI command is not valid.' + }); }); }); }); \ No newline at end of file