diff --git a/modules/di/src/exceptions.js b/modules/di/src/exceptions.js index 023bce6590..cebf1f1d11 100644 --- a/modules/di/src/exceptions.js +++ b/modules/di/src/exceptions.js @@ -50,6 +50,24 @@ export class AsyncBindingError extends ProviderError { } } +export class CyclicDependencyError extends ProviderError { + constructor(key:Key){ + super(key, function(keys:List) { + return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`; + }); + } +} + +export class InstantiationError extends ProviderError { + constructor(originalException, key:Key){ + super(key, function(keys:List) { + var first = stringify(ListWrapper.first(keys).token); + return `Error during instantiation of ${first}!${constructResolvingPath(keys)}.`+ + ` ORIGINAL ERROR: ${originalException}`; + }); + } +} + export class InvalidBindingError extends DIError { constructor(binding){ this.message = `Invalid binding ${binding}`; diff --git a/modules/di/src/injector.js b/modules/di/src/injector.js index 45b7802435..6003e1949f 100644 --- a/modules/di/src/injector.js +++ b/modules/di/src/injector.js @@ -1,10 +1,13 @@ import {Map, List, MapWrapper, ListWrapper} from 'facade/collection'; import {Binding, BindingBuilder, bind} from './binding'; -import {ProviderError, NoProviderError, InvalidBindingError, AsyncBindingError} from './exceptions'; +import {ProviderError, NoProviderError, InvalidBindingError, + AsyncBindingError, CyclicDependencyError, InstantiationError} from './exceptions'; import {Type, isPresent, isBlank} from 'facade/lang'; import {Future, FutureWrapper} from 'facade/async'; import {Key} from './key'; +var _creating = new Object(); + export class Injector { constructor(bindings:List) { var flatten = _flattenBindings(bindings); @@ -44,6 +47,9 @@ export class Injector { if (key.token === Injector) return this._injector(returnFuture); var instance = this._get(this._instances, keyId); + if (instance === _creating) { + throw new CyclicDependencyError(key); + } if (isPresent(instance)) return instance; var binding = this._get(this._bindings, keyId); @@ -73,6 +79,8 @@ export class Injector { } _instantiate(key:Key, binding:Binding, returnFuture) { + ListWrapper.set(this._instances, key.id, _creating); + if (binding.providedAsFuture && !returnFuture) { throw new AsyncBindingError(key); } @@ -85,14 +93,20 @@ export class Injector { } _instantiateSync(key:Key, binding:Binding) { + var deps; + try { + deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, d.asFuture)); + } catch (e) { + if (e instanceof ProviderError) e.addKey(key); + throw e; + } + try { - var deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, d.asFuture)); var instance = binding.factory(deps); ListWrapper.set(this._instances, key.id, instance); return instance; } catch (e) { - if (e instanceof ProviderError) e.addKey(key); - throw e; + throw new InstantiationError(e, key); } } @@ -101,11 +115,6 @@ export class Injector { var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, true)); return FutureWrapper.wait(futures). then(binding.factory). - catch(function(e) { - console.log('sdfsdfsd', e) - //e.addKey(key) - //return e; - }). then(function(instance) { ListWrapper.set(instances, key.id, instance); return instance diff --git a/modules/di/test/di/injector_spec.js b/modules/di/test/di/injector_spec.js index 4f3290f419..ff6e045015 100644 --- a/modules/di/test/di/injector_spec.js +++ b/modules/di/test/di/injector_spec.js @@ -2,6 +2,11 @@ import {describe, it, iit, expect, beforeEach} from 'test_lib/test_lib'; import {Injector, Inject, bind} from 'di/di'; class Engine {} +class BrokenEngine { + constructor() { + throw "Broken Engine"; + } +} class DashboardSoftware {} class Dashboard { constructor(software: DashboardSoftware){} @@ -33,6 +38,10 @@ class CarWithInject { } } +class CyclicEngine { + constructor(car:Car){} +} + class NoAnnotations { constructor(secretDependency){} } @@ -151,5 +160,29 @@ export function main() { expect(() => injector.get(CarWithDashboard)). toThrowError('No provider for DashboardSoftware! (CarWithDashboard -> Dashboard -> DashboardSoftware)'); }); + + it('should throw when trying to instantiate a cyclic dependency', function() { + var injector = new Injector([ + Car, + bind(Engine).toClass(CyclicEngine) + ]); + + expect(() => injector.get(Car)) + .toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)'); + }); + + it('should show the full path when error happens in a constructor', function() { + var injector = new Injector([ + Car, + bind(Engine).toClass(BrokenEngine) + ]); + + try { + injector.get(Car); + throw "Must throw"; + } catch (e) { + expect(e.message).toContain("Error during instantiation of Engine! (Car -> Engine)"); + } + }); }); -} +} \ No newline at end of file