fails validation if the config has a parse error (#4139)

This commit is contained in:
Dimitri Mitropoulos
2021-10-20 15:34:28 -04:00
committed by GitHub
parent 331b18126f
commit 4e0300a06f
3 changed files with 110 additions and 25 deletions

View File

@@ -11,7 +11,26 @@ const electronShowErrorBox = mocked(electron.dialog.showErrorBox);
const getConfigSettings = mocked(_getConfigSettings);
describe('validateInsomniaConfig', () => {
it('should show error box and exit if there is an error', () => {
it('should show error box and exit if there is a parse error', () => {
// Arrange
const errorReturn = {
error: {
syntaxError: new SyntaxError('mock syntax error'),
fileContents: '{ "mock": ["insomnia", "config"] }',
configPath: '/mock/insomnia/config/path',
},
};
getConfigSettings.mockReturnValue(errorReturn);
// Act
validateInsomniaConfig();
// Assert
expect(electronShowErrorBox).toHaveBeenCalled();
expect(electronAppExit).toHaveBeenCalled();
});
it('should show error box and exit if there is a config error', () => {
// Arrange
const errorReturn = {
error: {

View File

@@ -1,27 +1,48 @@
import electron from 'electron';
import { getConfigSettings } from '../models/helpers/settings';
import { getConfigSettings, isConfigError, isParseError } from '../models/helpers/settings';
import { exitApp } from './electron-helpers';
export const validateInsomniaConfig = () => {
const configSettings = getConfigSettings();
if ('error' in configSettings) {
const errors = configSettings.error.humanReadableErrors?.map(({ message, path, suggestion }, index) => ([
if (!('error' in configSettings)) {
return;
}
if (isParseError(configSettings)) {
const { syntaxError, configPath } = configSettings.error;
electron.dialog.showErrorBox('Invalid Insomnia Config', [
'Failed to parse JSON file for Insomnia Config.',
'',
'[Path]',
configPath,
'',
'[Syntax Error]',
syntaxError.message,
].join('\n'));
} else if (isConfigError(configSettings)) {
const { humanReadableErrors, configPath } = configSettings.error;
const errors = humanReadableErrors.map(({ message, path, suggestion }, index) => ([
`[Error ${index + 1}]`,
`Path: ${path}`,
`${message}.${suggestion ? ` ${suggestion}` : ''}`,
]).join('\n')).join('\n\n');
electron.dialog.showErrorBox('Invalid Insomnia Config',
[
`Your Insomnia Config was found to be invalid. Please check the path below for the following error${configSettings.error.humanReadableErrors?.length > 1 ? 's' : ''}:`,
'',
'[Path]',
configSettings.error.configPath,
'',
errors,
].join('\n'),
electron.dialog.showErrorBox('Invalid Insomnia Config', [
`Your Insomnia Config was found to be invalid. Please check the path below for the following error${configSettings.error.humanReadableErrors?.length > 1 ? 's' : ''}:`,
'',
'[Path]',
configPath,
'',
errors,
].join('\n'));
} else {
electron.dialog.showErrorBox(
'An unexpected error occured while parsing Insomnia Config',
JSON.stringify(configSettings),
);
exitApp();
}
exitApp();
};

View File

@@ -9,27 +9,49 @@ import { ValueOf } from 'type-fest';
import { isDevelopment } from '../../common/constants';
import { getDataDirectory, getPortableExecutableDir } from '../../common/electron-helpers';
interface FailedParseResult {
syntaxError: SyntaxError;
fileContents: string;
configPath: string;
}
const isFailedParseResult = (input: any): input is FailedParseResult => (
input ? input.syntaxError instanceof SyntaxError : false
);
/** takes an unresolved (or resolved will work fine too) filePath of the insomnia config and reads the insomniaConfig from disk */
export const readConfigFile = (filePath?: string) => {
if (!filePath) {
export const readConfigFile = (configPath?: string): unknown | FailedParseResult | undefined => {
if (!configPath) {
return undefined;
}
let fileContents = '';
try {
fileContents = readFileSync(filePath, 'utf-8');
fileContents = readFileSync(configPath, 'utf-8');
} catch (error: unknown) {
// file not found
return undefined;
}
if (!fileContents) {
const fileIsFoundButEmpty = fileContents === '';
if (fileIsFoundButEmpty) {
return undefined;
}
try {
return JSON.parse(fileContents) as unknown;
} catch (error: unknown) {
console.error('failed to parse insomnia config', { filePath, fileContents }, error);
} catch (syntaxError: unknown) {
// note: all JSON.parse errors are SyntaxErrors
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/JSON_bad_parse
if (syntaxError instanceof SyntaxError) {
console.error('failed to parse insomnia config', { configPath, fileContents, syntaxError });
const failedParseResult: FailedParseResult = {
syntaxError,
fileContents,
configPath: configPath,
};
return failedParseResult;
}
return undefined;
}
};
@@ -55,10 +77,14 @@ export const getConfigFile = () => {
// note: this is written as to avoid unnecessary (synchronous) reads from disk.
// The paths above are in priority order such that if we already found what we're looking for, there's no reason to keep reading other files.
for (const configPath of configPaths) {
const insomniaConfig = readConfigFile(configPath);
if (insomniaConfig !== undefined && configPath !== undefined) {
const fileReadResult = readConfigFile(configPath);
if (isFailedParseResult(fileReadResult)) {
return fileReadResult;
}
if (fileReadResult !== undefined && configPath !== undefined) {
return {
insomniaConfig,
insomniaConfig: fileReadResult,
configPath,
};
}
@@ -79,14 +105,33 @@ interface ConfigError {
};
}
export const isConfigError = (input: any): input is ConfigError => (
input ? input.humanErrors?.length > 0 : false
);
interface ParseError {
error: FailedParseResult;
}
export const isParseError = (input: any): input is ParseError => (
input ? isFailedParseResult(input.error) : false
);
/**
* gets settings from the `insomnia.config.json`
*
* note that it is a business rule that the config is never read again after startup, hence the `once` usage.
*/
export const getConfigSettings: () => (NonNullable<InsomniaConfig['settings']> | ConfigError) = once(() => {
const { configPath, insomniaConfig } = getConfigFile();
export const getConfigSettings: () => (NonNullable<InsomniaConfig['settings']> | ConfigError | ParseError) = once(() => {
const configFileResult = getConfigFile();
if (isFailedParseResult(configFileResult)) {
return {
error: configFileResult,
};
}
const { insomniaConfig, configPath } = configFileResult;
const validationResult = validate(insomniaConfig as InsomniaConfig);
if (isErrorResult(validationResult)) {