From afb67603b390368dc8a8db4e24f45aa663f2ed9e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Oct 2021 18:58:23 +0200 Subject: [PATCH] errors: added new assertion APIs Signed-off-by: Patrik Oldsberg --- packages/errors/api-report.md | 19 +++++ packages/errors/src/errors/assertion.test.ts | 79 ++++++++++++++++++++ packages/errors/src/errors/assertion.ts | 48 ++++++++++++ packages/errors/src/errors/index.ts | 2 + 4 files changed, 148 insertions(+) create mode 100644 packages/errors/src/errors/assertion.test.ts create mode 100644 packages/errors/src/errors/assertion.ts diff --git a/packages/errors/api-report.md b/packages/errors/api-report.md index 429807d9db..4290ea0db1 100644 --- a/packages/errors/api-report.md +++ b/packages/errors/api-report.md @@ -5,6 +5,11 @@ ```ts import { JsonObject } from '@backstage/config'; +// Warning: (ae-missing-release-tag) "assertError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function assertError(val: unknown): asserts val is ErrorLike; + // @public export class AuthenticationError extends CustomErrorBase {} @@ -23,6 +28,15 @@ export function deserializeError( data: SerializedError, ): T; +// Warning: (ae-missing-release-tag) "ErrorLike" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ErrorLike = { + name: string; + message: string; + stack?: string; +}; + // @public export type ErrorResponse = { error: SerializedError; @@ -38,6 +52,11 @@ export type ErrorResponse = { // @public export class InputError extends CustomErrorBase {} +// Warning: (ae-missing-release-tag) "isError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function isError(val: unknown): val is ErrorLike; + // @public export class NotAllowedError extends CustomErrorBase {} diff --git a/packages/errors/src/errors/assertion.test.ts b/packages/errors/src/errors/assertion.test.ts new file mode 100644 index 0000000000..28367c2a68 --- /dev/null +++ b/packages/errors/src/errors/assertion.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { assertError, isError } from './assertion'; +import { NotFoundError } from './common'; +import { CustomErrorBase } from './CustomErrorBase'; + +const areErrors = [ + { name: 'e', message: '' }, + new Error(), + new NotFoundError(), + Object.create({ name: 'e', message: '' }), + Object.assign(Object.create({ name: 'e' }), { + get message() { + return ''; + }, + }), + new (class extends class { + message = ''; + } { + name = 'e'; + })(), + new (class SubclassError extends CustomErrorBase {})(), + new (class SubclassError extends NotFoundError {})(), +]; + +const notErrors = [ + null, + 0, + 'loller', + Symbol(), + [], + BigInt(0), + false, + true, + { name: 'e' }, + { message: '' }, + { name: '', message: 'oh no' }, + new (class {})(), +]; + +describe('assertError', () => { + it.each(areErrors)('should assert that things are errors %#', error => { + expect(assertError(error)).toBeUndefined(); + }); + + it.each(notErrors)( + 'should assert that things are not errors %#', + notError => { + expect(() => assertError(notError)).toThrow(); + }, + ); +}); + +describe('isError', () => { + it.each(areErrors)('should assert that things are errors %#', error => { + expect(isError(error)).toBe(true); + }); + + it.each(notErrors)( + 'should assert that things are not errors %#', + notError => { + expect(isError(notError)).toBe(false); + }, + ); +}); diff --git a/packages/errors/src/errors/assertion.ts b/packages/errors/src/errors/assertion.ts new file mode 100644 index 0000000000..a0b2d59c11 --- /dev/null +++ b/packages/errors/src/errors/assertion.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type ErrorLike = { + name: string; + message: string; + stack?: string; +}; + +export function isError(val: unknown): val is ErrorLike { + if (typeof val !== 'object' || val === null || Array.isArray(val)) { + return false; + } + const maybe = val as Partial; + if (typeof maybe.name !== 'string' || maybe.name === '') { + return false; + } + if (typeof maybe.message !== 'string') { + return false; + } + return true; +} + +export function assertError(val: unknown): asserts val is ErrorLike { + if (typeof val !== 'object' || val === null || Array.isArray(val)) { + throw new Error(`Encountered invalid error, not an object, got '${val}'`); + } + const maybe = val as Partial; + if (typeof maybe.name !== 'string' || maybe.name === '') { + throw new Error(`Encountered error object without a name, got '${val}'`); + } + if (typeof maybe.message !== 'string') { + throw new Error(`Encountered error object without a message, got '${val}'`); + } +} diff --git a/packages/errors/src/errors/index.ts b/packages/errors/src/errors/index.ts index cc9d15b1ff..02670f4de9 100644 --- a/packages/errors/src/errors/index.ts +++ b/packages/errors/src/errors/index.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +export { assertError, isError } from './assertion'; +export type { ErrorLike } from './assertion'; export { AuthenticationError, ConflictError,