Skip to content

Commit

Permalink
refactor: delineate API v1 errors
Browse files Browse the repository at this point in the history
  • Loading branch information
kanadgupta committed Nov 21, 2024
1 parent e864491 commit d932a01
Show file tree
Hide file tree
Showing 17 changed files with 55 additions and 51 deletions.
6 changes: 4 additions & 2 deletions __tests__/commands/changelogs/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import nock from 'nock';
import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';

import Command from '../../../src/commands/changelogs.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';
Expand Down Expand Up @@ -276,7 +276,9 @@ describe('rdme changelogs', () => {
message: `Error uploading ${chalk.underline(`${fullDirectory}/${slug}.md`)}:\n\n${errorObject.message}`,
};

await expect(run([`./${fullDirectory}`, '--key', key])).rejects.toStrictEqual(new APIError(formattedErrorObject));
await expect(run([`./${fullDirectory}`, '--key', key])).rejects.toStrictEqual(
new APIv1Error(formattedErrorObject),
);

getMocks.done();
postMocks.done();
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/changelogs/single.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import nock from 'nock';
import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';

import Command from '../../../src/commands/changelogs.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('rdme changelogs (single)', () => {
message: `Error uploading ${chalk.underline(`${filePath}`)}:\n\n${errorObject.message}`,
};

await expect(run([filePath, '--key', key])).rejects.toStrictEqual(new APIError(formattedErrorObject));
await expect(run([filePath, '--key', key])).rejects.toStrictEqual(new APIv1Error(formattedErrorObject));

getMock.done();
});
Expand Down
6 changes: 4 additions & 2 deletions __tests__/commands/custompages/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import nock from 'nock';
import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';

import Command from '../../../src/commands/custompages.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';
Expand Down Expand Up @@ -308,7 +308,9 @@ describe('rdme custompages', () => {
message: `Error uploading ${chalk.underline(`${fullDirectory}/${slug}.md`)}:\n\n${errorObject.message}`,
};

await expect(run([`./${fullDirectory}`, '--key', key])).rejects.toStrictEqual(new APIError(formattedErrorObject));
await expect(run([`./${fullDirectory}`, '--key', key])).rejects.toStrictEqual(
new APIv1Error(formattedErrorObject),
);

getMocks.done();
postMocks.done();
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/custompages/single.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import nock from 'nock';
import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';

import Command from '../../../src/commands/custompages.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('rdme custompages (single)', () => {
message: `Error uploading ${chalk.underline(`${filePath}`)}:\n\n${errorObject.message}`,
};

await expect(run([filePath, '--key', key])).rejects.toStrictEqual(new APIError(formattedErrorObject));
await expect(run([filePath, '--key', key])).rejects.toStrictEqual(new APIv1Error(formattedErrorObject));

getMock.done();
});
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/docs/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import prompts from 'prompts';
import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect, vi, type MockInstance } from 'vitest';

import Command from '../../../src/commands/docs/index.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock, getAPIV1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
import { after, before } from '../../helpers/get-gha-setup.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
Expand Down Expand Up @@ -349,7 +349,7 @@ describe('rdme docs', () => {
};

await expect(run([`./${fullDirectory}`, '--key', key, '--version', version])).rejects.toStrictEqual(
new APIError(formattedErrorObject),
new APIv1Error(formattedErrorObject),
);

getMocks.done();
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/docs/single.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import nock from 'nock';
import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect } from 'vitest';

import Command from '../../../src/commands/docs/index.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock, getAPIV1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
import hashFileContents from '../../helpers/hash-file-contents.js';
import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('rdme docs (single)', () => {
};

await expect(run([filePath, '--key', key, '--version', version])).rejects.toStrictEqual(
new APIError(formattedErrorObject),
new APIv1Error(formattedErrorObject),
);

getMock.done();
Expand Down
6 changes: 3 additions & 3 deletions __tests__/commands/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import prompts from 'prompts';
import { describe, beforeAll, afterAll, afterEach, it, expect } from 'vitest';

import Command from '../../src/commands/login.js';
import APIError from '../../src/lib/apiError.js';
import { APIv1Error } from '../../src/lib/apiError.js';
import configStore from '../../src/lib/configstore.js';
import { getAPIV1Mock } from '../helpers/get-api-mock.js';
import { runCommand } from '../helpers/setup-oclif-config.js';
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('rdme login', () => {

const mock = getAPIV1Mock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);

await expect(run()).rejects.toStrictEqual(new APIError(errorResponse));
await expect(run()).rejects.toStrictEqual(new APIv1Error(errorResponse));
mock.done();
});

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('rdme login', () => {
.post('/api/v1/login', { email, password, project: projectThatIsNotYours })
.reply(404, errorResponse);

await expect(run()).rejects.toStrictEqual(new APIError(errorResponse));
await expect(run()).rejects.toStrictEqual(new APIv1Error(errorResponse));
mock.done();
});
});
14 changes: 7 additions & 7 deletions __tests__/commands/openapi/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import prompts from 'prompts';
import { describe, beforeAll, beforeEach, afterEach, it, expect, vi, type MockInstance } from 'vitest';

import Command from '../../../src/commands/openapi/index.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import config from '../../../src/lib/config.js';
import petstoreWeird from '../../__fixtures__/petstore-simple-weird-version.json' with { type: 'json' };
import { getAPIV1Mock, getAPIV1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
Expand Down Expand Up @@ -762,7 +762,7 @@ describe('rdme openapi', () => {
'--version',
invalidVersion,
]),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

return mock.done();
});
Expand Down Expand Up @@ -824,7 +824,7 @@ describe('rdme openapi', () => {

await expect(
run([require.resolve('@readme/oas-examples/3.1/json/petstore.json'), '--key', 'key']),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

return mock.done();
});
Expand Down Expand Up @@ -874,7 +874,7 @@ describe('rdme openapi', () => {

await expect(
run(['./__tests__/__fixtures__/swagger-with-invalid-extensions.json', '--key', key, '--version', version]),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

mockWithHeader.done();
return mock.done();
Expand Down Expand Up @@ -909,7 +909,7 @@ describe('rdme openapi', () => {
'--version',
version,
]),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

putMock.done();
return mock.done();
Expand All @@ -932,7 +932,7 @@ describe('rdme openapi', () => {

await expect(
run(['./__tests__/__fixtures__/swagger-with-invalid-extensions.json', '--key', key, '--version', version]),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

return mock.done();
});
Expand Down Expand Up @@ -965,7 +965,7 @@ describe('rdme openapi', () => {

await expect(
run([require.resolve('@readme/oas-examples/2.0/json/petstore.json'), '--key', key, '--version', version]),
).rejects.toStrictEqual(new APIError(errorObject));
).rejects.toStrictEqual(new APIv1Error(errorObject));

mockWithHeader.done();
return mock.done();
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/versions/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import prompts from 'prompts';
import { describe, beforeAll, afterEach, it, expect } from 'vitest';

import Command from '../../../src/commands/versions/create.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';

Expand Down Expand Up @@ -136,7 +136,7 @@ describe('rdme versions:create', () => {

const mockRequest = getAPIV1Mock().post('/api/v1/version').basicAuth({ user: key }).reply(400, errorResponse);

await expect(run(['--key', key, version, '--fork', '0.0.5'])).rejects.toStrictEqual(new APIError(errorResponse));
await expect(run(['--key', key, version, '--fork', '0.0.5'])).rejects.toStrictEqual(new APIv1Error(errorResponse));
mockRequest.done();
});

Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/versions/delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import nock from 'nock';
import { describe, beforeAll, afterEach, it, expect } from 'vitest';

import Command from '../../../src/commands/versions/delete.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';

Expand Down Expand Up @@ -49,7 +49,7 @@ describe('rdme versions:delete', () => {
.basicAuth({ user: key })
.reply(200, { version });

await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIError(errorResponse));
await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIv1Error(errorResponse));
mockRequest.done();
});
});
4 changes: 2 additions & 2 deletions __tests__/commands/versions/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import prompts from 'prompts';
import { describe, beforeAll, afterEach, it, expect } from 'vitest';

import Command from '../../../src/commands/versions/update.js';
import APIError from '../../../src/lib/apiError.js';
import { APIv1Error } from '../../../src/lib/apiError.js';
import { getAPIV1Mock } from '../../helpers/get-api-mock.js';
import { runCommand } from '../../helpers/setup-oclif-config.js';

Expand Down Expand Up @@ -348,7 +348,7 @@ describe('rdme versions:update', () => {
.basicAuth({ user: key })
.reply(400, errorResponse);

await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIError(errorResponse));
await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIv1Error(errorResponse));
mockRequest.done();
});

Expand Down
10 changes: 5 additions & 5 deletions __tests__/lib/apiError.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';

import APIError from '../../src/lib/apiError.js';
import { APIv1Error } from '../../src/lib/apiError.js';

const response = {
error: 'VERSION_FORK_EMPTY',
Expand All @@ -16,9 +16,9 @@ const response = {
],
};

describe('APIError', () => {
describe('APIv1Error', () => {
it('should handle ReadMe API errors', () => {
const error = new APIError(response);
const error = new APIv1Error(response);

expect(error.code).toBe(response.error);
expect(error.message).toBe(
Expand All @@ -27,14 +27,14 @@ describe('APIError', () => {
});

it('should handle API errors from a fetch `res` object', () => {
const error = new APIError({ error: response });
const error = new APIv1Error({ error: response });

expect(error.code).toBe(response.error);
});

it('should be able to handle generic non-API errors', () => {
const msg = 'i am an generic javascript error';
const error = new APIError(msg);
const error = new APIv1Error(msg);

expect(error.code).toBe(msg);
expect(error.message).toBe(msg);
Expand Down
4 changes: 2 additions & 2 deletions src/commands/openapi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ export default class OpenAPICommand extends BaseCommand<typeof OpenAPICommand> {

const error = (res: Response) => {
return handleAPIv1Res(res).catch(err => {
// If we receive an APIError, no changes needed! Throw it as is.
if (err.name === 'APIError') {
// If we receive an APIv1Error, no changes needed! Throw it as is.
if (err.name === 'APIv1Error') {
throw err;
}

Expand Down
14 changes: 7 additions & 7 deletions src/lib/apiError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface APIErrorResponse {
export interface APIv1ErrorResponse {
docs?: string;
error: string;
help?: string;
Expand All @@ -7,27 +7,27 @@ export interface APIErrorResponse {
suggestion?: string;
}

export default class APIError extends Error {
export class APIv1Error extends Error {
code: string;

constructor(res: APIErrorResponse | string | { error: APIErrorResponse }) {
let err: APIErrorResponse | string;
constructor(res: APIv1ErrorResponse | string | { error: APIv1ErrorResponse }) {
let err: APIv1ErrorResponse | string;

// Special handling to for fetch `res` arguments where `res.error` will contain our API error
// response.
if (typeof res === 'object') {
if (typeof res?.error === 'object') {
err = res.error;
} else {
err = res as APIErrorResponse;
err = res as APIv1ErrorResponse;
}
} else {
err = res;
}

super(err as unknown as string);

this.name = 'APIError';
this.name = 'APIv1Error';

if (typeof err === 'object') {
this.code = err.error;
Expand All @@ -41,7 +41,7 @@ export default class APIError extends Error {
this.message = err.message;
}

this.name = 'APIError';
this.name = 'APIv1Error';
} else {
this.code = err;
this.message = err;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/readmeAPIFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProxyAgent } from 'undici';

import pkg from '../package.json' with { type: 'json' };

import APIError from './apiError.js';
import { APIv1Error } from './apiError.js';
import config from './config.js';
import { git } from './createGHA/index.js';
import isCI, { ciName, isGHA } from './isCI.js';
Expand Down Expand Up @@ -212,12 +212,12 @@ export async function readmeAPIv1Fetch(
/**
* Small handler for handling responses from API v1.
*
* If we receive JSON errors, we throw an APIError exception.
* If we receive JSON errors, we throw an APIv1Error exception.
*
* If we receive non-JSON responses, we consider them errors and throw them.
*
* @param rejectOnJsonError if omitted (or set to true), the function will return
* an `APIError` if the JSON body contains an `error` property. If set to false,
* an `APIv1Error` if the JSON body contains an `error` property. If set to false,
* the function will return a resolved promise containing the JSON object.
*
*/
Expand All @@ -230,7 +230,7 @@ export async function handleAPIv1Res(res: Response, rejectOnJsonError = true) {
const body = (await res.json()) as any;
debug(`received status code ${res.status} from ${res.url} with JSON response: ${JSON.stringify(body)}`);
if (body.error && rejectOnJsonError) {
return Promise.reject(new APIError(body));
return Promise.reject(new APIv1Error(body));
}
return body;
}
Expand Down
Loading

0 comments on commit d932a01

Please sign in to comment.