backend-app-api: improved error messaging when service factories throw errors

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-09-02 11:44:17 +02:00
parent 1f384c5644
commit 181c066d2d
4 changed files with 65 additions and 4 deletions
+1
View File
@@ -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",
@@ -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",
);
});
});
@@ -18,6 +18,7 @@ import {
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { stringifyError } from '@backstage/errors';
export class ServiceRegistry {
readonly #providedFactories: Map<string, ServiceFactory>;
@@ -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<any>;
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);
}
+1
View File
@@ -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