diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 0ae48d4e9c..f6c2b9f3b9 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -36,6 +36,7 @@ "@backstage/backend-common": "^0.15.1-next.1", "@backstage/backend-plugin-api": "^0.1.2-next.0", "@backstage/backend-tasks": "^0.3.5-next.0", + "@backstage/errors": "^1.1.0", "@backstage/plugin-permission-node": "^0.6.5-next.1", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts index 94ec77eea8..c75167ea3b 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts @@ -237,7 +237,43 @@ describe('ServiceRegistry', () => { const factory = registry.get(ref1)!; await expect(factory('catalog')).rejects.toThrow( - "Failed to instantiate service '1' for 'catalog'. The following dependent services are missing: '2'", + "Failed to instantiate service '1' for 'catalog' because the following dependent services are missing: '2'", + ); + }); + + it('should decorate error messages thrown by the top-level factory function', async () => { + const myFactory = createServiceFactory({ + service: ref1, + deps: {}, + factory() { + throw new Error('top-level error'); + }, + }); + + const registry = new ServiceRegistry([myFactory]); + const factory = registry.get(ref1)!; + + await expect(factory('catalog')).rejects.toThrow( + "Failed to instantiate service '1' because the top-level factory function threw an error, Error: top-level error", + ); + }); + + it('should decorate error messages thrown by the plugin-level factory function', async () => { + const myFactory = createServiceFactory({ + service: ref1, + deps: {}, + async factory() { + return pluginId => { + throw new Error(`error in plugin ${pluginId}`); + }; + }, + }); + + const registry = new ServiceRegistry([myFactory]); + const factory = registry.get(ref1)!; + + await expect(factory('catalog')).rejects.toThrow( + "Failed to instantiate service '1' for 'catalog' because the factory function threw an error, Error: error in plugin catalog", ); }); }); diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index f8d99bffd8..fc79bad14a 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -18,6 +18,7 @@ import { FactoryFunc, ServiceRef, } from '@backstage/backend-plugin-api'; +import { stringifyError } from '@backstage/errors'; export class ServiceRegistry { readonly #providedFactories: Map; @@ -71,12 +72,22 @@ export class ServiceRegistry { if (missingRefs.length) { const missing = missingRefs.map(r => `'${r.id}'`).join(', '); throw new Error( - `Failed to instantiate service '${ref.id}' for '${pluginId}'. The following dependent services are missing: ${missing}`, + `Failed to instantiate service '${ref.id}' for '${pluginId}' because the following dependent services are missing: ${missing}`, ); } implementation = { - factoryFunc: factory.factory(factoryDeps), + factoryFunc: Promise.resolve() + .then(() => factory!.factory(factoryDeps)) + .catch(error => { + throw new Error( + `Failed to instantiate service '${ + ref.id + }' because the top-level factory function threw an error, ${stringifyError( + error, + )}`, + ); + }), byPlugin: new Map(), }; @@ -85,7 +96,19 @@ export class ServiceRegistry { let result = implementation.byPlugin.get(pluginId) as Promise; if (!result) { - result = implementation.factoryFunc.then(func => func(pluginId)); + result = implementation.factoryFunc.then(func => + Promise.resolve() + .then(() => func(pluginId)) + .catch(error => { + throw new Error( + `Failed to instantiate service '${ + ref.id + }' for '${pluginId}' because the factory function threw an error, ${stringifyError( + error, + )}`, + ); + }), + ); implementation.byPlugin.set(pluginId, result); } diff --git a/yarn.lock b/yarn.lock index f0a567bfe3..1313484b21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2846,6 +2846,7 @@ __metadata: "@backstage/backend-plugin-api": ^0.1.2-next.0 "@backstage/backend-tasks": ^0.3.5-next.0 "@backstage/cli": ^0.19.0-next.1 + "@backstage/errors": ^1.1.0 "@backstage/plugin-permission-node": ^0.6.5-next.1 express: ^4.17.1 express-promise-router: ^4.1.0