Skip to content

Commit 17714a2

Browse files
andriimredoclytatomyr
authored andcommitted
refactor: error handling (#2040)
1 parent bc1807a commit 17714a2

File tree

19 files changed

+105
-55
lines changed

19 files changed

+105
-55
lines changed

packages/cli/src/__tests__/commands/join.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ import {
1010
} from '@redocly/openapi-core';
1111
import { handleJoin } from '../../commands/join.js';
1212
import {
13-
exitWithError,
1413
getAndValidateFileExtension,
1514
getFallbackApisOrExit,
1615
sortTopLevelKeysForOas,
1716
writeToFileByExtension,
1817
} from '../../utils/miscellaneous.js';
18+
import { exitWithError } from '../../utils/error.js';
1919
import { configFixture } from '../fixtures/config.js';
2020
import { firstDocument, secondDocument, thirdDocument } from '../fixtures/join/documents.js';
2121

2222
describe('handleJoin', () => {
2323
beforeEach(() => {
2424
vi.mock('../../utils/miscellaneous.js');
25+
vi.mock('../../utils/error.js');
2526
vi.mocked(getAndValidateFileExtension).mockImplementation(
2627
(fileName) => fileName.split('.').pop() as any
2728
);

packages/cli/src/__tests__/commands/lint.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import {
1313
getExecutionTime,
1414
printUnusedWarnings,
1515
handleError,
16-
exitWithError,
1716
loadConfigAndHandleErrors,
1817
checkIfRulesetExist,
1918
} from '../../utils/miscellaneous.js';
19+
import { exitWithError } from '../../utils/error.js';
2020
import { configFixture } from '../fixtures/config.js';
2121
import { performance } from 'perf_hooks';
2222
import { commandWrapper } from '../../wrapper.js';
@@ -52,6 +52,7 @@ describe('handleLint', () => {
5252
vi.mocked(getTotals).mockReturnValue({ errors: 0 } as Totals);
5353

5454
vi.mock('../../utils/miscellaneous.js');
55+
vi.mock('../../utils/error.js');
5556
vi.mocked(loadConfigAndHandleErrors).mockResolvedValue(configFixture);
5657
vi.mocked(getFallbackApisOrExit).mockImplementation(
5758
async (entrypoints) => entrypoints?.map((path: string) => ({ path })) ?? []

packages/cli/src/__tests__/utils.test.ts

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ import {
88
CircularJSONNotSupportedError,
99
sortTopLevelKeysForOas,
1010
cleanColors,
11-
HandledError,
1211
cleanArgs,
1312
getAndValidateFileExtension,
1413
writeToFileByExtension,
1514
} from '../utils/miscellaneous.js';
15+
import * as errorHandling from '../utils/error.js';
1616
import { sanitizeLocale, sanitizePath, getPlatformSpawnArgs } from '../utils/platform.js';
17-
import { type ResolvedApi, type Totals, ResolveError, YamlParseError } from '@redocly/openapi-core';
17+
import {
18+
type ResolvedApi,
19+
type Totals,
20+
ResolveError,
21+
YamlParseError,
22+
HandledError,
23+
} from '@redocly/openapi-core';
1824
import * as openapiCore from '@redocly/openapi-core';
1925
import { blue, red, yellow } from 'colorette';
2026
import * as fs from 'node:fs';
@@ -42,6 +48,12 @@ vi.mock('@redocly/openapi-core', async () => {
4248
stringifyYaml: vi.fn((data, opts) => data as string),
4349
};
4450
});
51+
vi.mock('../../utils/error.js', async () => {
52+
const actual = await vi.importActual('../../utils/error.js');
53+
return {
54+
...actual,
55+
};
56+
});
4557

4658
describe('pathToFilename', () => {
4759
it('should use correct path separator', () => {
@@ -123,6 +135,7 @@ describe('getFallbackApisOrExit', () => {
123135
});
124136

125137
it('should error if file from config do not exist', async () => {
138+
vi.spyOn(errorHandling, 'exitWithError');
126139
vi.mocked(fs.existsSync).mockImplementationOnce(() => false);
127140
expect.assertions(3);
128141
try {
@@ -131,7 +144,7 @@ describe('getFallbackApisOrExit', () => {
131144
expect(process.stderr.write).toHaveBeenCalledWith(
132145
'\nsomeFile.yaml does not exist or is invalid.\n\n'
133146
);
134-
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
147+
expect(errorHandling.exitWithError).toHaveBeenCalledWith('Please provide a valid path.');
135148
expect(e.message).toEqual('Please provide a valid path.');
136149
}
137150
});
@@ -153,6 +166,7 @@ describe('getFallbackApisOrExit', () => {
153166
});
154167

155168
it('should exit with error in case if invalid path provided as args', async () => {
169+
vi.spyOn(errorHandling, 'exitWithError');
156170
const apisConfig = {
157171
apis: {},
158172
};
@@ -165,12 +179,13 @@ describe('getFallbackApisOrExit', () => {
165179
expect(process.stderr.write).toHaveBeenCalledWith(
166180
'\nsomeFile.yaml does not exist or is invalid.\n\n'
167181
);
168-
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
182+
expect(errorHandling.exitWithError).toHaveBeenCalledWith('Please provide a valid path.');
169183
expect(e.message).toEqual('Please provide a valid path.');
170184
}
171185
});
172186

173187
it('should exit with error in case if invalid 2 path provided as args', async () => {
188+
vi.spyOn(errorHandling, 'exitWithError');
174189
const apisConfig = {
175190
apis: {},
176191
};
@@ -182,12 +197,13 @@ describe('getFallbackApisOrExit', () => {
182197
expect(process.stderr.write).toHaveBeenCalledWith(
183198
'\nsomeFile.yaml does not exist or is invalid.\n\n'
184199
);
185-
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
200+
expect(errorHandling.exitWithError).toHaveBeenCalledWith('Please provide a valid path.');
186201
expect(e.message).toEqual('Please provide a valid path.');
187202
}
188203
});
189204

190205
it('should exit with error if only one file exist ', async () => {
206+
vi.spyOn(errorHandling, 'exitWithError');
191207
const apisStub = {
192208
...apis,
193209
notExist: {
@@ -201,16 +217,17 @@ describe('getFallbackApisOrExit', () => {
201217
.mocked(fs.existsSync)
202218
.mockImplementation((path) => (path as string).endsWith('someFile.yaml'));
203219

204-
expect.assertions(4);
220+
expect.assertions(5);
205221

206222
try {
207223
await getFallbackApisOrExit(undefined, configStub);
208224
} catch (e) {
209225
expect(process.stderr.write).toHaveBeenCalledWith(
210226
'\nnotExist.yaml does not exist or is invalid.\n\n'
211227
);
212-
expect(process.stderr.write).toHaveBeenCalledWith('Please provide a valid path.\n\n');
213-
expect(process.stderr.write).toHaveBeenCalledTimes(2);
228+
expect(process.stderr.write).toHaveBeenCalledTimes(1);
229+
expect(errorHandling.exitWithError).toHaveBeenCalledWith('Please provide a valid path.');
230+
expect(errorHandling.exitWithError).toHaveBeenCalledTimes(1);
214231
expect(e.message).toEqual('Please provide a valid path.');
215232
}
216233
existSyncMock.mockClear();
@@ -408,46 +425,49 @@ describe('handleErrors', () => {
408425
});
409426

410427
it('should handle ResolveError', () => {
428+
vi.spyOn(errorHandling, 'exitWithError');
411429
const resolveError = new ResolveError(new Error('File not found.'));
412430
expect(() => handleError(resolveError, ref)).toThrowError(HandledError);
413-
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
414-
expect(process.stderr.write).toHaveBeenCalledWith(
415-
`Failed to resolve API description at openapi/test.yaml:\n\n - File not found.\n\n`
431+
expect(errorHandling.exitWithError).toHaveBeenCalledWith(
432+
`Failed to resolve API description at openapi/test.yaml:\n\n - File not found.`
416433
);
417434
});
418435

419436
it('should handle YamlParseError', () => {
437+
vi.spyOn(errorHandling, 'exitWithError');
420438
const yamlParseError = new YamlParseError(new Error('Invalid yaml.'), {} as any);
421439
expect(() => handleError(yamlParseError, ref)).toThrowError(HandledError);
422-
expect(redColoretteMocks).toHaveBeenCalledTimes(1);
423-
expect(process.stderr.write).toHaveBeenCalledWith(
424-
`Failed to parse API description at openapi/test.yaml:\n\n - Invalid yaml.\n\n`
440+
expect(errorHandling.exitWithError).toHaveBeenCalledWith(
441+
`Failed to parse API description at openapi/test.yaml:\n\n - Invalid yaml.`
425442
);
426443
});
427444

428445
it('should handle CircularJSONNotSupportedError', () => {
446+
vi.spyOn(errorHandling, 'exitWithError');
429447
const circularError = new CircularJSONNotSupportedError(new Error('Circular json'));
430448
expect(() => handleError(circularError, ref)).toThrowError(HandledError);
431-
expect(process.stderr.write).toHaveBeenCalledWith(
449+
expect(errorHandling.exitWithError).toHaveBeenCalledWith(
432450
`Detected circular reference which can't be converted to JSON.\n` +
433-
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.\n\n`
451+
`Try to use ${blue('yaml')} output or remove ${blue('--dereferenced')}.`
434452
);
435453
});
436454

437455
it('should handle SyntaxError', () => {
456+
vi.spyOn(errorHandling, 'exitWithError');
438457
const testError = new SyntaxError('Unexpected identifier');
439458
testError.stack = 'test stack';
440459
expect(() => handleError(testError, ref)).toThrowError(HandledError);
441-
expect(process.stderr.write).toHaveBeenCalledWith(
442-
'Syntax error: Unexpected identifier test stack\n\n'
460+
expect(errorHandling.exitWithError).toHaveBeenCalledWith(
461+
'Syntax error: Unexpected identifier test stack'
443462
);
444463
});
445464

446465
it('should throw unknown error', () => {
466+
vi.spyOn(errorHandling, 'exitWithError');
447467
const testError = new Error('Test error.');
448468
expect(() => handleError(testError, ref)).toThrowError(HandledError);
449-
expect(process.stderr.write).toHaveBeenCalledWith(
450-
`Something went wrong when processing openapi/test.yaml:\n\n - Test error.\n\n`
469+
expect(errorHandling.exitWithError).toHaveBeenCalledWith(
470+
`Something went wrong when processing openapi/test.yaml:\n\n - Test error.`
451471
);
452472
});
453473
});

packages/cli/src/commands/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { logger } from '@redocly/openapi-core';
2-
import { exitWithError } from '../utils/miscellaneous.js';
2+
import { exitWithError } from '../utils/error.js';
33
import { RedoclyOAuthClient } from '../auth/oauth-client.js';
44
import { getReuniteUrl } from '../reunite/api/index.js';
55

packages/cli/src/commands/build-docs/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import { default as redoc } from 'redoc';
55
import { performance } from 'node:perf_hooks';
66
import { getMergedConfig, isAbsoluteUrl, logger } from '@redocly/openapi-core';
77
import { getObjectOrJSON, getPageHTML } from './utils.js';
8-
import {
9-
exitWithError,
10-
getExecutionTime,
11-
getFallbackApisOrExit,
12-
} from '../../utils/miscellaneous.js';
8+
import { getExecutionTime, getFallbackApisOrExit } from '../../utils/miscellaneous.js';
9+
import { exitWithError } from '../../utils/error.js';
1310

1411
import type { BuildDocsArgv } from './types.js';
1512
import type { CommandArgs } from '../../wrapper.js';

packages/cli/src/commands/build-docs/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { default as handlebars } from 'handlebars';
66
import * as path from 'node:path';
77
import { existsSync, lstatSync, readFileSync } from 'node:fs';
88
import { isAbsoluteUrl, logger } from '@redocly/openapi-core';
9-
import { exitWithError } from '../../utils/miscellaneous.js';
109
import * as url from 'node:url';
10+
import { exitWithError } from '../../utils/error.js';
1111

1212
import type { Config } from '@redocly/openapi-core';
1313
import type { BuildDocsOptions } from './types.js';

packages/cli/src/commands/bundle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
checkForDeprecatedOptions,
1515
formatPath,
1616
} from '../utils/miscellaneous.js';
17+
import { AbortFlowError } from '../utils/error.js';
1718

1819
import type { OutputExtensions, Totals, VerifyConfigOptions } from '../types.js';
1920
import type { CommandArgs } from '../wrapper.js';
@@ -148,6 +149,6 @@ export async function handleBundle({
148149
printUnusedWarnings(config.styleguide);
149150

150151
if (!(totals.errors === 0 || argv.force)) {
151-
throw new Error('Bundle failed.');
152+
throw new AbortFlowError('Bundle failed.');
152153
}
153154
}

packages/cli/src/commands/join.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import {
1515
import {
1616
getFallbackApisOrExit,
1717
printExecutionTime,
18-
exitWithError,
1918
sortTopLevelKeysForOas,
2019
getAndValidateFileExtension,
2120
writeToFileByExtension,
2221
} from '../utils/miscellaneous.js';
22+
import { exitWithError } from '../utils/error.js';
2323
import { isObject, isString, keysOf } from '../utils/js-utils.js';
2424
import { COMPONENTS, OPENAPI3_METHOD } from './split/types.js';
2525
import { crawl, startsWithComponents } from './split/index.js';

packages/cli/src/commands/lint.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
} from '@redocly/openapi-core';
1313
import {
1414
checkIfRulesetExist,
15-
exitWithError,
1615
formatPath,
1716
getExecutionTime,
1817
getFallbackApisOrExit,
@@ -22,6 +21,7 @@ import {
2221
printLintTotals,
2322
printUnusedWarnings,
2423
} from '../utils/miscellaneous.js';
24+
import { AbortFlowError, exitWithError } from '../utils/error.js';
2525
import { getCommandNameFromArgs } from '../utils/getCommandNameFromArgs.js';
2626

2727
import type { Arguments } from 'yargs';
@@ -121,7 +121,7 @@ export async function handleLint({
121121
printUnusedWarnings(config.styleguide);
122122

123123
if (!(totals.errors === 0 || argv['generate-ignore-file'])) {
124-
throw new Error('Lint failed.');
124+
throw new AbortFlowError('Lint failed.');
125125
}
126126
}
127127

packages/cli/src/commands/split/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
printExecutionTime,
88
pathToFilename,
99
readYaml,
10-
exitWithError,
1110
escapeLanguageName,
1211
langToExt,
1312
writeToFileByExtension,
1413
getAndValidateFileExtension,
1514
} from '../../utils/miscellaneous.js';
1615
import { isObject, isEmptyObject } from '../../utils/js-utils.js';
16+
import { exitWithError } from '../../utils/error.js';
1717
import {
1818
OPENAPI3_COMPONENT,
1919
COMPONENTS,

0 commit comments

Comments
 (0)