diff --git a/src/components/Context/context.spec.tsx b/src/components/Context/context.spec.tsx
index 1c4592e6e10..7da7eb9fb03 100644
--- a/src/components/Context/context.spec.tsx
+++ b/src/components/Context/context.spec.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { render } from 'ink-testing-library';
import { ListContexts, ShowCurrentContext, AddContext, SetCurrent } from './Context';
import { ContextTestingHelper } from '../../constants';
+import * as messages from '../../messages';
const testing = new ContextTestingHelper();
@@ -9,7 +10,7 @@ describe('listing contexts', () => {
test('should render error when no context file found', () => {
testing.deleteDummyContextFile();
const { lastFrame } = render();
- expect(lastFrame()).toMatch('No contexts saved yet.');
+ expect(lastFrame()).toMatch(messages.NO_CONTEXTS_SAVED);
});
test('Should render the context list', () => {
@@ -27,7 +28,7 @@ describe('rendering current context', () => {
testing.deleteDummyContextFile();
const { lastFrame } = render();
const message = lastFrame();
- expect(message).toMatch('No contexts saved yet.');
+ expect(message).toMatch(messages.NO_CONTEXTS_SAVED);
});
test('showing current context ', () => {
@@ -41,7 +42,7 @@ describe('AddContext ', () => {
test('should return message', () => {
testing.createDummyContextFile();
const { lastFrame } = render();
- expect(lastFrame()).toMatch('New context added');
+ expect(lastFrame()).toMatch(messages.NEW_CONTEXT_ADDED('home'));
});
});
@@ -49,7 +50,7 @@ describe('SetContext ', () => {
test('Should render error message is key is not in store', () => {
testing.createDummyContextFile();
const { lastFrame } = render();
- expect(lastFrame()).toMatch('The context you are trying to use is not present');
+ expect(lastFrame()).toMatch(messages.CONTEXT_NOT_FOUND('name'));
});
test('Should render the update context', () => {
diff --git a/src/components/Context/contexterror.tsx b/src/components/Context/contexterror.tsx
index db5c8545229..0c4cca4f867 100644
--- a/src/components/Context/contexterror.tsx
+++ b/src/components/Context/contexterror.tsx
@@ -1,21 +1,7 @@
import React from 'react';
import { Text } from 'ink';
-import { ContextFileNotFoundError, DeletingCurrentContextError, KeyNotFoundError } from '../../hooks/context';
-
const ContextError: React.FunctionComponent<{ error: Error }> = ({ error }) => {
- if (error instanceof ContextFileNotFoundError) {
- return No contexts saved yet.;
- }
-
- if (error instanceof KeyNotFoundError) {
- return The context you are trying to use is not present;
- }
-
- if (error instanceof DeletingCurrentContextError) {
- return You are trying to delete a context that is set as current.;
- }
-
return {error.message};
};
diff --git a/src/help-message.spec.ts b/src/help-message.spec.ts
new file mode 100644
index 00000000000..76ccf227bf4
--- /dev/null
+++ b/src/help-message.spec.ts
@@ -0,0 +1,30 @@
+import { HelpMessageBuilder } from './help-message';
+
+let helpBuilder: HelpMessageBuilder;
+
+describe('HelpMessageBuilder should', () => {
+ beforeAll(() => {
+ helpBuilder = new HelpMessageBuilder();
+ });
+ it('return root Help message', () => {
+ expect(typeof helpBuilder.showHelp()).toMatch('string');
+ expect(helpBuilder.showHelp()).toMatch(
+ 'usage: asyncapi [options] [command]\n\n'+
+ 'flags:\n'+
+ ' -h, --help display help for command\n'+
+ ' -v, --version output the version number\n'+
+ '\n'+
+ 'commands:\n'+
+ ' validate [options] [command] Validate asyncapi file\n'+
+ ' context [options] [command] Manage context\n'
+ );
+ });
+
+ it('return validate help message', () => {
+ expect(typeof helpBuilder.showCommandHelp('validate')).toMatch('string');
+ });
+
+ it('return context help message', () => {
+ expect(typeof helpBuilder.showCommandHelp('context')).toMatch('string');
+ });
+});
diff --git a/src/help-message.ts b/src/help-message.ts
new file mode 100644
index 00000000000..e620d05b66f
--- /dev/null
+++ b/src/help-message.ts
@@ -0,0 +1,99 @@
+import { injectable, container } from 'tsyringe';
+
+export type CommandName = 'validate' | 'context';
+
+export type Command = {
+ [name in CommandName]: {
+ usage: string;
+ shortDescription: string;
+ longDescription?: string;
+ flags: string[];
+ subCommands?: string[];
+ };
+};
+
+@injectable()
+export class HelpMessage {
+ private helpFlag = '-h, --help display help for command';
+
+ readonly usage: string = 'asyncapi [options] [command]';
+
+ readonly flags = [
+ this.helpFlag,
+ '-v, --version output the version number',
+ ];
+
+ readonly commands: Command = {
+ validate: {
+ usage: 'asyncapi validate [options]',
+ shortDescription: 'Validate asyncapi file',
+ flags: [
+ this.helpFlag,
+ '-f, --file Path of the AsyncAPI file',
+ '-c, --context Context to use',
+ '-w, --watch Watch mode'
+ ]
+ },
+ context: {
+ usage: 'asyncapi context [command] [options]',
+ shortDescription: 'Manage context',
+ longDescription: 'Context is what makes it easier for you to work with multiple AsyncAPI files.\nYou can add multiple different files to a context.\nThis way you do not have to pass --file flag with path to the file every time but just --context flag with reference name.\nYou can also set a default context, so neither --file nor --context flags are needed',
+ flags: [this.helpFlag],
+ subCommands: [
+ 'list list all saved contexts',
+ 'current see current context',
+ 'use set given context as default/current',
+ 'add add/update context',
+ 'remove remove a context'
+ ]
+ }
+ }
+}
+
+export class HelpMessageBuilder {
+ private helpMessage: HelpMessage = container.resolve(HelpMessage);
+
+ showHelp() {
+ let helpText = '';
+ helpText += `usage: ${this.helpMessage.usage}\n\n`;
+ helpText += 'flags:\n';
+ for (const flag of this.helpMessage.flags) {
+ helpText += ` ${flag}\n`;
+ }
+ helpText += '\n';
+
+ if (this.helpMessage.commands) {
+ helpText += 'commands:\n';
+ for (const [name, obj] of Object.entries(this.helpMessage.commands)) {
+ helpText += ` ${name} [options] [command] ${obj.shortDescription}\n`;
+ }
+ }
+
+ return helpText;
+ }
+
+ showCommandHelp(command: CommandName) {
+ let helpText = '';
+ const commandHelpObject = this.helpMessage.commands[command as CommandName];
+ helpText += `usage: ${commandHelpObject.usage}\n\n`;
+
+ if (commandHelpObject.longDescription) {
+ helpText += `${commandHelpObject.longDescription}\n\n`;
+ }
+
+ helpText += 'flags: \n';
+ for (const flag of commandHelpObject.flags) {
+ helpText += ` ${flag}\n`;
+ }
+
+ if (commandHelpObject.subCommands) {
+ helpText += '\n';
+ helpText += 'commands:\n';
+ for (const command of commandHelpObject.subCommands) {
+ helpText += ` ${command}\n`;
+ }
+ }
+
+ return helpText;
+ }
+}
diff --git a/src/hooks/context/contextService.ts b/src/hooks/context/contextService.ts
index fe45399c4bd..b5e17324de6 100644
--- a/src/hooks/context/contextService.ts
+++ b/src/hooks/context/contextService.ts
@@ -1,5 +1,5 @@
import { injectable } from 'tsyringe';
-import { Context, ContextFileNotFoundError,KeyNotFoundError, SpecFileNotFoundError } from './models';
+import { Context, ContextFileNotFoundError, ContextNotFoundError, SpecFileNotFoundError } from './models';
import { CONTEXTFILE_PATH } from '../../constants';
import * as fs from 'fs';
import * as path from 'path';
@@ -26,7 +26,7 @@ export class ContextService {
}
addContext(context: Context, key: string, specFile: SpecificationFile): Context {
- if (specFile.isNotValid()) {throw new SpecFileNotFoundError();}
+ if (specFile.isNotValid()) {throw new SpecFileNotFoundError(specFile.getSpecificationName());}
context.store[String(key)] = specFile.getSpecificationName();
return context;
}
@@ -38,7 +38,7 @@ export class ContextService {
}
updateCurrent(context: Context, key: string): Context {
- if (!context.store[String(key)]) {throw new KeyNotFoundError();}
+ if (!context.store[String(key)]) {throw new ContextNotFoundError(key);}
context.current = key;
return context;
}
diff --git a/src/hooks/context/hook.spec.ts b/src/hooks/context/hook.spec.ts
index 6546cb64dab..a786a99c254 100644
--- a/src/hooks/context/hook.spec.ts
+++ b/src/hooks/context/hook.spec.ts
@@ -1,7 +1,8 @@
import { useContextFile, useSpecfile } from './hooks';
-import { ContextFileNotFoundError, KeyNotFoundError, ContextNotFoundError } from './models';
+import { ContextFileNotFoundError, ContextNotFoundError } from './models';
import { ContextTestingHelper } from '../../constants';
import { SpecificationFile } from '../validation';
+import * as messages from '../../messages';
const testingVariables = new ContextTestingHelper();
@@ -42,7 +43,7 @@ describe('useContextFile().addContext ', () => {
testingVariables.deleteDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
- expect(response).toMatch('New context added');
+ expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
testingVariables.deleteDummyContextFile();
});
@@ -50,14 +51,14 @@ describe('useContextFile().addContext ', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
- expect(response).toMatch('New context added');
+ expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
});
test('Auto set current when when adding context for the fist time', () => {
testingVariables.deleteDummyContextFile();
const { response, error } = useContextFile().addContext('home', new SpecificationFile('./test/specification.yml'));
expect(error).toBeUndefined();
- expect(response).toMatch('New context added');
+ expect(response).toMatch(messages.NEW_CONTEXT_ADDED('home'));
const { response: res, error: err } = useContextFile().current();
expect(err).toBeUndefined();
expect(res?.key).toMatch('home');
@@ -77,7 +78,7 @@ describe('useContextFile.updateCurrent ', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().setCurrent('name');
expect(response).toBeUndefined();
- expect(error instanceof KeyNotFoundError).toBeTruthy();
+ expect(error instanceof ContextNotFoundError).toBeTruthy();
});
test('Should update the current context', () => {
@@ -93,13 +94,13 @@ describe('useContextFile().deleteContext ', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().deleteContext('code');
expect(error).toBeUndefined();
- expect(response).toMatch('context deleted successfully');
+ expect(response).toMatch(messages.CONTEXT_DELETED);
});
test('return error if deleting current context', () => {
testingVariables.createDummyContextFile();
const { response, error } = useContextFile().deleteContext('home');
- expect(response).toMatch('context deleted successfully');
+ expect(response).toMatch(messages.CONTEXT_DELETED);
expect(error).toBeUndefined();
});
});
diff --git a/src/hooks/context/hooks.tsx b/src/hooks/context/hooks.tsx
index 9dd54a8a3ba..1fa8f0b0962 100644
--- a/src/hooks/context/hooks.tsx
+++ b/src/hooks/context/hooks.tsx
@@ -2,6 +2,7 @@ import { Context, ContextFileNotFoundError, ContextNotFoundError, MissingCurrent
import { ContextService } from './contextService';
import { container } from 'tsyringe';
import { SpecificationFile } from '../validation';
+import * as messages from '../../messages';
export type Result = {
response?: any,
@@ -36,7 +37,7 @@ export const useContextFile = (): any => {
const ctx = contextService.loadContextFile();
const updatedContext = contextService.addContext(ctx, key, specFile);
contextService.save(updatedContext);
- const response = 'New context added';
+ const response = messages.NEW_CONTEXT_ADDED(key);
return { response };
} catch (error) {
if (error instanceof ContextFileNotFoundError) {
@@ -44,7 +45,7 @@ export const useContextFile = (): any => {
try {
const newContext = contextService.addContext(context, key, specFile);
contextService.save(contextService.updateCurrent(newContext, key));
- const response = 'New context added';
+ const response = messages.NEW_CONTEXT_ADDED(key);
return { response };
} catch (error) {
return { error };
@@ -69,11 +70,11 @@ export const useContextFile = (): any => {
const ctx = contextService.loadContextFile();
if (Object.keys(ctx.store).length === 1) {
contextService.deleteContextFile();
- return { response: 'context deleted successfully' };
+ return { response: messages.CONTEXT_DELETED };
}
const updatedContext = contextService.deleteContext(ctx, key);
contextService.save(updatedContext);
- const response = 'context deleted successfully';
+ const response = messages.CONTEXT_DELETED;
return { response };
} catch (error) {
return { error };
@@ -102,7 +103,7 @@ export const useContextFile = (): any => {
try {
const ctx = contextService.loadContextFile();
const ctxValue = ctx.store[String(key)];
- if (!ctxValue) { throw new ContextNotFoundError(); }
+ if (!ctxValue) { throw new ContextNotFoundError(key); }
const response = new SpecificationFile(ctxValue);
return { response };
} catch (error) {
@@ -136,7 +137,7 @@ export const useSpecfile = (flags: useSpecFileInput): useSpecFileOutput => {
if (flags.context) {
const ctxFile = ctx.store[flags.context];
- if (!ctxFile) { throw new ContextNotFoundError(); }
+ if (!ctxFile) { throw new ContextNotFoundError(flags.context); }
const specFile = new SpecificationFile(ctxFile);
return { specFile };
}
diff --git a/src/hooks/context/models.ts b/src/hooks/context/models.ts
index 06e728cbafa..46f1a462122 100644
--- a/src/hooks/context/models.ts
+++ b/src/hooks/context/models.ts
@@ -1,55 +1,42 @@
+import * as messages from '../../messages';
export interface Context {
- current: string,
- store: {
- [name: string]: string
- }
+ current: string,
+ store: {
+ [name: string]: string
+ }
}
export class SpecFileNotFoundError extends Error {
- constructor() {
+ constructor(specPath: string) {
super();
- this.message = 'specification file not found in that path.';
+ this.message = messages.ValidationMessage(specPath).error();
}
}
export class ContextFileNotFoundError extends Error {
constructor() {
super();
- this.message = 'No contexts saved yet, run asyncapi --help to know more.';
+ this.message = messages.NO_CONTEXTS_SAVED;
}
}
export class ContextNotFoundError extends Error {
- constructor() {
- super();
- this.message = 'This context key does not exist.';
- }
-}
-
-export class KeyNotFoundError extends Error {
- constructor() {
- super();
- this.message = 'Key not found.';
- }
-}
-
-export class DeletingCurrentContextError extends Error {
- constructor() {
+ constructor(contextName: string) {
super();
- this.message = 'You are trying to delete a context that is currently in use.';
+ this.message = messages.CONTEXT_NOT_FOUND(contextName);
}
}
export class MissingCurrentContextError extends Error {
constructor() {
super();
- this.message = 'No context is set as current, please set a current context.';
+ this.message = messages.MISSING_CURRENT_CONTEXT;
}
}
export class MissingArgumentstError extends Error {
constructor() {
super();
- this.message = 'Missing arguments';
+ this.message = messages.MISSING_ARGUMENTS;
}
}
diff --git a/src/hooks/validation/hook.spec.ts b/src/hooks/validation/hook.spec.ts
index 4422aac7e3d..b21bd2c1a33 100644
--- a/src/hooks/validation/hook.spec.ts
+++ b/src/hooks/validation/hook.spec.ts
@@ -2,6 +2,7 @@ import { container } from 'tsyringe';
import { SpecificationFile, ValidationInput, ValidationResponse } from './models';
import { ValidationService } from './ValidationService';
import { useValidate } from './hook';
+import {ValidationMessage} from '../../messages';
function ValidationServiceMock() {
return {
@@ -39,7 +40,8 @@ describe('UseValidate should', () => {
expect(useValidateResponse.success).toBeFalsy();
expect(useValidateResponse.message).toEqual('');
- expect(useValidateResponse.errors[0]).toBe(`File: ${invalidFileValidationInput.file.getSpecificationName()} does not exists or is not a file!`);
+
+ expect(useValidateResponse.errors[0]).toBe(ValidationMessage(invalidFileValidationInput.file.getSpecificationName()).error());
});
test('return success when the validation is correct', async () => {
@@ -47,7 +49,7 @@ describe('UseValidate should', () => {
const useValidateResponse = await useValidate().validate(fileThatExistsValidationInput);
expect(useValidateResponse.success).toBeTruthy();
- expect(useValidateResponse.message).toEqual(`File: ${fileThatExistsValidationInput.file.getSpecificationName()} successfully validated!`);
+ expect(useValidateResponse.message).toEqual(ValidationMessage(fileThatExistsValidationInput.file.getSpecificationName()).message());
});
test('return validation service errors when the validation has failed', async () => {
diff --git a/src/hooks/validation/hook.tsx b/src/hooks/validation/hook.tsx
index 8523b2c5e9d..b467044bf2b 100644
--- a/src/hooks/validation/hook.tsx
+++ b/src/hooks/validation/hook.tsx
@@ -2,6 +2,7 @@ import { container } from 'tsyringe';
import { UseValidateResponse, ValidationInput, ValidationResponse } from './models';
import { ValidationService } from './ValidationService';
+import { ValidationMessage } from '../../messages';
export function useValidate() {
const validationService: ValidationService = container.resolve(ValidationService);
@@ -10,11 +11,11 @@ export function useValidate() {
async validate({ file }: ValidationInput): Promise {
try {
if (file.isNotValid()) {
- return Promise.resolve(UseValidateResponse.withError(`File: ${file.getSpecificationName()} does not exists or is not a file!`));
+ return Promise.resolve(UseValidateResponse.withError(ValidationMessage(file.getSpecificationName()).error()));
}
const response: ValidationResponse = await validationService.execute(file);
if (response.success) {
- return Promise.resolve(UseValidateResponse.withMessage(`File: ${file.getSpecificationName()} successfully validated!`));
+ return Promise.resolve(UseValidateResponse.withMessage(ValidationMessage(file.getSpecificationName()).message()));
}
return Promise.resolve(UseValidateResponse.withErrors(response.errors));
} catch (error) {
diff --git a/src/messages.tsx b/src/messages.tsx
new file mode 100644
index 00000000000..199689ae98c
--- /dev/null
+++ b/src/messages.tsx
@@ -0,0 +1,16 @@
+export const NO_CONTEXTS_SAVED = 'No contexts saved yet, run asyncapi --help to learn more';
+
+export const CONTEXT_NOT_FOUND = (contextName: string) => `Context ${contextName} does not exists.`;
+
+export const MISSING_CURRENT_CONTEXT = 'No context is set as current, please set a current context.';
+
+export const MISSING_ARGUMENTS = 'Missing arguments.';
+
+export const NEW_CONTEXT_ADDED = (contextName: string) => `New context added.\n\nYou can set it as your current context:\n asyncapi context use ${contextName}\nYou can use this context when needed with --context flag: asyncapi validate --context ${contextName}`;
+
+export const CONTEXT_DELETED = 'context deleted successfully';
+
+export const ValidationMessage = (filePath: string) => ({
+ error: () => `File: ${filePath} does not exists or is not a file!`,
+ message: () => `File: ${filePath} successfully validated!`
+});