import {ListWrapper} from 'angular2/src/core/facade/collection'; import {stringify, isBlank} from 'angular2/src/core/facade/lang'; import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions'; import {Key} from './key'; import {Injector} from './injector'; function findFirstClosedCycle(keys: any[]): any[] { var res = []; for (var i = 0; i < keys.length; ++i) { if (ListWrapper.contains(res, keys[i])) { res.push(keys[i]); return res; } else { res.push(keys[i]); } } return res; } function constructResolvingPath(keys: any[]): string { if (keys.length > 1) { var reversed = findFirstClosedCycle(ListWrapper.reversed(keys)); var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token)); return " (" + tokenStrs.join(' -> ') + ")"; } else { return ""; } } /** * Base class for all errors arising from misconfigured bindings. */ export class AbstractBindingError extends BaseException { /** @internal */ message: string; /** @internal */ keys: Key[]; /** @internal */ injectors: Injector[]; /** @internal */ constructResolvingMessage: Function; constructor(injector: Injector, key: Key, constructResolvingMessage: Function) { super("DI Exception"); this.keys = [key]; this.injectors = [injector]; this.constructResolvingMessage = constructResolvingMessage; this.message = this.constructResolvingMessage(this.keys); } addKey(injector: Injector, key: Key): void { this.injectors.push(injector); this.keys.push(key); this.message = this.constructResolvingMessage(this.keys); } get context() { return this.injectors[this.injectors.length - 1].debugContext(); } } /** * Thrown when trying to retrieve a dependency by `Key` from {@link Injector}, but the * {@link Injector} does not have a {@link Binding} for {@link Key}. * * ### Example ([live demo](http://plnkr.co/edit/vq8D3FRB9aGbnWJqtEPE?p=preview)) * * ```typescript * class A { * constructor(b:B) {} * } * * expect(() => Injector.resolveAndCreate([A])).toThrowError(); * ``` */ export class NoBindingError extends AbstractBindingError { constructor(injector: Injector, key: Key) { super(injector, key, function(keys: any[]) { var first = stringify(ListWrapper.first(keys).token); return `No provider for ${first}!${constructResolvingPath(keys)}`; }); } } /** * Thrown when dependencies form a cycle. * * ### Example ([live demo](http://plnkr.co/edit/wYQdNos0Tzql3ei1EV9j?p=info)) * * ```typescript * var injector = Injector.resolveAndCreate([ * bind("one").toFactory((two) => "two", [[new Inject("two")]]), * bind("two").toFactory((one) => "one", [[new Inject("one")]]) * ]); * * expect(() => injector.get("one")).toThrowError(); * ``` * * Retrieving `A` or `B` throws a `CyclicDependencyError` as the graph above cannot be constructed. */ export class CyclicDependencyError extends AbstractBindingError { constructor(injector: Injector, key: Key) { super(injector, key, function(keys: any[]) { return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`; }); } } /** * Thrown when a constructing type returns with an Error. * * The `InstantiationError` class contains the original error plus the dependency graph which caused * this object to be instantiated. * * ### Example ([live demo](http://plnkr.co/edit/7aWYdcqTQsP0eNqEdUAf?p=preview)) * * ```typescript * class A { * constructor() { * throw new Error('message'); * } * } * * var injector = Injector.resolveAndCreate([A]); * try { * injector.get(A); * } catch (e) { * expect(e instanceof InstantiationError).toBe(true); * expect(e.originalException.message).toEqual("message"); * expect(e.originalStack).toBeDefined(); * } * ``` */ export class InstantiationError extends WrappedException { /** @internal */ keys: Key[]; /** @internal */ injectors: Injector[]; /** @internal */ constructor(injector: Injector, originalException, originalStack, key: Key) { super("DI Exception", originalException, originalStack, null); this.keys = [key]; this.injectors = [injector]; } addKey(injector: Injector, key: Key): void { this.injectors.push(injector); this.keys.push(key); } get wrapperMessage(): string { var first = stringify(ListWrapper.first(this.keys).token); return `Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`; } get causeKey(): Key { return this.keys[0]; } get context() { return this.injectors[this.injectors.length - 1].debugContext(); } } /** * Thrown when an object other then {@link Binding} (or `Type`) is passed to {@link Injector} * creation. * * ### Example ([live demo](http://plnkr.co/edit/YatCFbPAMCL0JSSQ4mvH?p=preview)) * * ```typescript * expect(() => Injector.resolveAndCreate(["not a type"])).toThrowError(); * ``` */ export class InvalidBindingError extends BaseException { constructor(binding) { super("Invalid binding - only instances of Binding and Type are allowed, got: " + binding.toString()); } } /** * Thrown when the class has no annotation information. * * Lack of annotation information prevents the {@link Injector} from determining which dependencies * need to be injected into the constructor. * * ### Example ([live demo](http://plnkr.co/edit/rHnZtlNS7vJOPQ6pcVkm?p=preview)) * * ```typescript * class A { * constructor(b) {} * } * * expect(() => Injector.resolveAndCreate([A])).toThrowError(); * ``` * * This error is also thrown when the class not marked with {@link @Injectable} has parameter types. * * ```typescript * class B {} * * class A { * constructor(b:B) {} // no information about the parameter types of A is available at runtime. * } * * expect(() => Injector.resolveAndCreate([A,B])).toThrowError(); * ``` */ export class NoAnnotationError extends BaseException { constructor(typeOrFunc, params: any[][]) { super(NoAnnotationError._genMessage(typeOrFunc, params)); } private static _genMessage(typeOrFunc, params: any[][]) { var signature = []; for (var i = 0, ii = params.length; i < ii; i++) { var parameter = params[i]; if (isBlank(parameter) || parameter.length == 0) { signature.push('?'); } else { signature.push(ListWrapper.map(parameter, stringify).join(' ')); } } return "Cannot resolve all parameters for " + stringify(typeOrFunc) + "(" + signature.join(', ') + "). " + 'Make sure they all have valid type or annotations.'; } } /** * Thrown when getting an object by index. * * ### Example ([live demo](http://plnkr.co/edit/bRs0SX2OTQiJzqvjgl8P?p=preview)) * * ```typescript * class A {} * * var injector = Injector.resolveAndCreate([A]); * * expect(() => injector.getAt(100)).toThrowError(); * ``` */ export class OutOfBoundsError extends BaseException { constructor(index) { super(`Index ${index} is out-of-bounds.`); } } // TODO: add a working example after alpha38 is released /** * Thrown when a multi binding and a regular binding are bound to the same token. * * ### Example * * ```typescript * expect(() => Injector.resolveAndCreate([ * new Binding("Strings", {toValue: "string1", multi: true}), * new Binding("Strings", {toValue: "string2", multi: false}) * ])).toThrowError(); * ``` */ export class MixingMultiBindingsWithRegularBindings extends BaseException { constructor(binding1, binding2) { super("Cannot mix multi bindings and regular bindings, got: " + binding1.toString() + " " + binding2.toString()); } }