Skip to content

Commit 5b4edcc

Browse files
[#39] Refactoring: improve error handling
- initial implementation
1 parent f1e539d commit 5b4edcc

26 files changed

+180
-93
lines changed

src/lib/decorators/ComponentDecorator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { InjectUtil, InjectionData } from "./InjectionDecorators";
22
import { CONTROLLER_DECORATOR_TOKEN } from "./ControllerDecorator";
33
import { INTERCEPTOR_DECORATOR_TOKEN } from "../interceptors/InterceptorDecorator";
4+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
45

56
export class ComponentData {
67
classToken: Symbol;
@@ -28,7 +29,7 @@ export function Component() {
2829
export function Profile(profile: string) {
2930
return function (target) {
3031
if (!ComponentUtil.isComponent(target)) {
31-
throw new Error('@Profile can be set only on @Component!');
32+
throw new DecoratorUsageError(`@Profile can be set only on @Component! (${target.name})`);
3233
}
3334
ComponentUtil.getComponentData(target).profile = profile;
3435
};

src/lib/decorators/ComponentScanDecorator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path_module from "path";
44
import { ConfigurationData, ConfigurationUtil } from "./ConfigurationDecorator";
55
import { ComponentUtil } from "./ComponentDecorator";
66
import { RequireUtils } from "../helpers/RequireUtils";
7+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
78

89
/**
910
*A decorator for setting up project files to be component-scanned.
@@ -13,7 +14,7 @@ import { RequireUtils } from "../helpers/RequireUtils";
1314
export function ComponentScan(path) {
1415
return function (target) {
1516
if (!ConfigurationUtil.isConfigurationClass(target)) {
16-
throw new Error('@ComponentScan is allowed on @Configuration classes only!');
17+
throw new DecoratorUsageError(`@ComponentScan is allowed on @Configuration classes only! (${target.name})`);
1718
}
1819
ConfigurationUtil.addComponentScanPath(target, path);
1920
};

src/lib/decorators/ConfigurationDecorator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ComponentFactory } from "../di/ComponentFactory";
22
import { PropertySourceUtil } from "./PropertySourceDecorator";
33
import { ComponentScanUtil } from "./ComponentScanDecorator";
4+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
45

56
const CONFIGURATION_HOLDER_TOKEN = Symbol('configuration_holder_token');
67

@@ -38,7 +39,7 @@ export class ConfigurationData {
3839
export function Configuration() {
3940
return function (target) {
4041
if (target[CONFIGURATION_HOLDER_TOKEN]) {
41-
throw new Error('Duplicate @Configuration decorator');
42+
throw new DecoratorUsageError(`Duplicate @Configuration decorator' (${target.name})`);
4243
}
4344
target[CONFIGURATION_HOLDER_TOKEN] = new ConfigurationData();
4445

@@ -50,7 +51,7 @@ export class ConfigurationUtil {
5051

5152
static getConfigurationData(target): ConfigurationData {
5253
if (!this.isConfigurationClass(target)) {
53-
throw new Error('Given target is not a @Configuration class');
54+
throw new Error(`${target.name} is not a @Configuration class`);
5455
}
5556
return target[CONFIGURATION_HOLDER_TOKEN];
5657
}

src/lib/decorators/ImportDecorator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { ConfigurationUtil } from "./ConfigurationDecorator";
2+
import { BadArgumentError } from "../errors/BadArgumentError";
3+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
24

35
/**
46
* Decorator used for composing configuration classes by importing other configuration classes.
@@ -8,8 +10,15 @@ import { ConfigurationUtil } from "./ConfigurationDecorator";
810
* */
911
export function Import(...configurationClasses) {
1012
return function (targetConfigurationClass) {
13+
if (!ConfigurationUtil.isConfigurationClass(targetConfigurationClass)) {
14+
// tslint:disable-next-line
15+
throw new DecoratorUsageError(`@Import is allowed on @Configuration classes only! (${targetConfigurationClass.name})`);
16+
}
1117
let targetConfigurationData = ConfigurationUtil.getConfigurationData(targetConfigurationClass);
1218
for (let configurationClass of configurationClasses) {
19+
if (!ConfigurationUtil.isConfigurationClass(configurationClass)) {
20+
throw new BadArgumentError(`${configurationClass.name} is not a @Configuration() class`);
21+
}
1322
let configurationData = ConfigurationUtil.getConfigurationData(configurationClass);
1423
for (let component of configurationData.componentFactory.components) {
1524
targetConfigurationData.componentFactory.components.push(component);

src/lib/decorators/InjectionDecorators.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ComponentUtil } from "./ComponentDecorator";
22
import { TypeUtils } from "../helpers/TypeUtils";
3+
import { InjectionError } from "../errors/InjectionError";
34

45
const INJECT_DECORATOR_TOKEN = Symbol('injector_decorator_token');
56

@@ -31,7 +32,7 @@ export function Inject(dependencyToken?: Symbol) {
3132
if (ComponentUtil.isComponent(type)) {
3233
token = ComponentUtil.getClassToken(type);
3334
} else {
34-
throw new Error('Cannot inject dependency which is not a @Component!');
35+
throw new InjectionError(`Cannot inject dependency which is not a @Component! (${type.name})`);
3536
}
3637
}
3738
// NOTE assumption: if type not declared or any then type is Object and isArray is false

src/lib/decorators/LifeCycleHooksDecorators.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
2+
13
const LIFE_CYCLE_HOOKS_TOKEN = Symbol('life_cycle_hooks_token');
24

35
export class LifeCycleHooksConfig {
@@ -13,7 +15,8 @@ export function PostConstruct() {
1315
let conf = LifeCycleHooksUtil.initIfDoesntExist(target);
1416
if (conf.postConstructMethod) {
1517
let errorParams = [conf.postConstructMethod, methodName].join(', ');
16-
throw new Error(`@PostConstruct used on multiple methods within a component (${errorParams})`);
18+
// tslint:disable-next-line
19+
throw new DecoratorUsageError(`@PostConstruct used on multiple methods (${errorParams}) within a component`);
1720
}
1821
conf.postConstructMethod = methodName;
1922
};
@@ -27,7 +30,7 @@ export function PreDestroy() {
2730
let conf = LifeCycleHooksUtil.initIfDoesntExist(target);
2831
if (conf.preDestroyMethod) {
2932
let errorParams = [conf.preDestroyMethod, methodName].join(', ');
30-
throw new Error(`@PreDestroy used on multiple methods within a component (${errorParams})`);
33+
throw new DecoratorUsageError(`@PreDestroy used on multiple methods within a component (${errorParams})`);
3134
}
3235
conf.preDestroyMethod = methodName;
3336
};

src/lib/decorators/PropertySourceDecorator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as _ from "lodash";
22
import { ConfigurationUtil } from "./ConfigurationDecorator";
33
import { GeneralUtils } from "../helpers/GeneralUtils";
44
import {RequireUtils} from "../helpers/RequireUtils";
5+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
56

67
/**
78
* A decorator for defining a JSON property source for the configuration properties.
@@ -11,7 +12,8 @@ import {RequireUtils} from "../helpers/RequireUtils";
1112
export function PropertySource(path: string) {
1213
return function (target) {
1314
if (!ConfigurationUtil.isConfigurationClass(target)) {
14-
throw new Error('@PropertySource can be used only on @Configuration classes.');
15+
// tslint:disable-next-line
16+
throw new DecoratorUsageError(`@PropertySource can be used only on @Configuration classes! (${target.name})`);
1517
}
1618
ConfigurationUtil.addPropertySourcePath(target, path);
1719
};

src/lib/decorators/QualifierDecorator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { ComponentUtil } from "./ComponentDecorator";
2+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
23

34
export function Qualifier(token: Symbol) {
45
return function (target) {
56
if (!ComponentUtil.isComponent(target)) {
6-
throw new Error('@Qualifier can be used only on @Component classes');
7+
throw new DecoratorUsageError(`@Qualifier can be used only on @Component classes! (${target.name})`);
78
}
89
ComponentUtil.getAliasTokens(target).push(token);
910
};

src/lib/decorators/RequestMappingDecorator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as _ from "lodash";
22
import { DecoratorUtil, DecoratorType } from "../helpers/DecoratorUtils";
3+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
4+
import { BadArgumentError } from "../errors/BadArgumentError";
35

46
// NOTE: These are methods defined on the Express Router
57
// http://expressjs.com/en/4x/api.html#router
@@ -46,7 +48,8 @@ export function RequestMapping(config: RequestMappingConfig) {
4648
let target = args[0];
4749
if (type === DecoratorType.METHOD) {
4850
if (config.method === undefined) {
49-
throw new Error("When using @RequestMapping on methods you must provide the request method type");
51+
// tslint:disable-next-line
52+
throw new BadArgumentError('When using @RequestMapping on methods you must provide the request method type');
5053
}
5154
let method = args[1];
5255
let routerConfig = RequestMappingUtil.initRouterConfigIfDoesntExist(target);
@@ -60,7 +63,7 @@ export function RequestMapping(config: RequestMappingConfig) {
6063
// TODO: refactor when new options are added on @RequestMapping for classes
6164
target[CLASS_ROUTER_CONFIG] = config.path;
6265
} else {
63-
throw new Error("@RequestMapping decorator can only be used on classes and methods!");
66+
throw new DecoratorUsageError(`@RequestMapping can only be used on classes and methods! (${target.name})`);
6467
}
6568
};
6669
}

src/lib/di/ApplicationContext.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Router } from "express";
66
import * as _ from "lodash";
77
import { LifeCycleHooksUtil } from "../decorators/LifeCycleHooksDecorators";
88
import { ProcessHandler } from "../helpers/ProcessHandler";
9+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
910

1011
export class ApplicationContextState {
1112
static NOT_INITIALIZED = 'NOT_INITIALIZED';
@@ -133,7 +134,7 @@ export class ApplicationContext {
133134
if (postConstructMethod) {
134135
let instance = this.injector.getComponent(componentData.classToken);
135136
if (!_.isFunction(instance[postConstructMethod])) {
136-
throw new Error(`@PostConstruct is not on a method (${postConstructMethod})`);
137+
throw new DecoratorUsageError(`@PostConstruct is not on a method (${postConstructMethod})`);
137138
}
138139
let invocationResult = instance[postConstructMethod]();
139140
postConstructInvocations.push(invocationResult);
@@ -150,7 +151,7 @@ export class ApplicationContext {
150151
if (preDestroyMethod) {
151152
let instance = this.injector.getComponent(componentData.classToken);
152153
if (!_.isFunction(instance[preDestroyMethod])) {
153-
throw new Error(`@PreDestroy is not on a method (${preDestroyMethod})`);
154+
throw new DecoratorUsageError(`@PreDestroy is not on a method (${preDestroyMethod})`);
154155
}
155156
let invocationResult = instance[preDestroyMethod]();
156157
preDestroyInvocations.push(invocationResult);

src/lib/di/Injector.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as _ from "lodash";
2+
import { InjectionError } from "../errors/InjectionError";
23

34
export class Injector {
45
private components: Map<Symbol, Array<Object>>;
@@ -18,10 +19,10 @@ export class Injector {
1819
getComponent(token: Symbol): Object {
1920
let components = this.components.get(token);
2021
if (_.isUndefined(components)) {
21-
throw new Error('No such component');
22+
throw new InjectionError('No such component');
2223
}
2324
if (components.length > 1) {
24-
throw new Error(`Ambiguous injection. ${components.length} components found in the injector.`);
25+
throw new InjectionError(`Ambiguous injection. ${components.length} components found in the injector.`);
2526
}
2627
return components[0];
2728
}

src/lib/errors/BadArgumentError.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export class BadArgumentError extends Error {
2+
constructor ( message ) {
3+
super();
4+
(<any> Error).captureStackTrace( this, this.constructor );
5+
this.name = 'BadArgumentError';
6+
this.message = message;
7+
}
8+
}

src/lib/errors/DecoratorUsageError.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export class DecoratorUsageError extends Error {
2+
constructor ( message ) {
3+
super();
4+
(<any> Error).captureStackTrace( this, this.constructor );
5+
this.name = 'DecoratorUsageError';
6+
this.message = message;
7+
}
8+
}

src/lib/errors/InjectionError.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export class InjectionError extends Error {
2+
constructor ( message ) {
3+
super();
4+
(<any> Error).captureStackTrace( this, this.constructor );
5+
this.name = 'InjectionError';
6+
this.message = message;
7+
}
8+
}

src/lib/helpers/validation/Preconditions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { BadArgumentError } from "../../errors/BadArgumentError";
2+
13
export class Preconditions {
24

35
/**
@@ -9,7 +11,7 @@ export class Preconditions {
911
static assertDefined(argument) {
1012
// replace with _.isUndefined(argument)
1113
if (argument === undefined) {
12-
throw new Error('Given argument is not defined');
14+
throw new BadArgumentError('Given argument is not defined');
1315
}
1416
}
1517
}

test/lib/decorators/ComponentDecorator.spec.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import {InjectionData} from "../../../src/lib/decorators/InjectionDecorators";
77
import {Controller} from "../../../src/lib/decorators/ControllerDecorator";
88
import {Interceptor} from "../../../src/lib/interceptors/InterceptorDecorator";
9+
import { DecoratorUsageError } from "../../../src/lib/errors/DecoratorUsageError";
910

1011
describe('ComponentDecorator', function () {
1112

@@ -32,7 +33,7 @@ describe('ProfileDecorator', function () {
3233
// given
3334
@Profile('dev')
3435
@Component()
35-
class A {};
36+
class A {}
3637

3738
// when
3839
let componentData = ComponentUtil.getComponentData(A);
@@ -46,70 +47,70 @@ describe('ProfileDecorator', function () {
4647
class B {}
4748

4849
// when / then
49-
expect(Profile('dev').bind(this, B)).to.throw(Error);
50+
expect(Profile('dev').bind(this, B)).to.throw(DecoratorUsageError);
5051
});
5152
});
5253

5354
describe('ComponentUtil', function () {
5455

5556
it('should return if class is component', function () {
56-
//given
57+
// given
5758
@Component()
5859
class A {}
5960
class B {}
6061

61-
//when / then
62+
// when / then
6263
expect(ComponentUtil.isComponent(A)).to.be.true;
6364
expect(ComponentUtil.isComponent(B)).to.be.false;
6465
});
6566

6667
it('should get class token', function () {
67-
//given
68+
// given
6869
@Component()
6970
class A {}
7071

71-
//when
72+
// when
7273
let classTokenA = ComponentUtil.getClassToken(A);
7374

74-
//then
75+
// then
7576
expect(classTokenA).to.be.a('symbol');
7677
expect(classTokenA).to.eql(ComponentUtil.getComponentData(A).classToken);
7778
});
7879

7980
it('should get alias tokens', function () {
80-
//given
81+
// given
8182
let tokenArray = [Symbol('tokenOne'), Symbol('tokenTwo')];
8283

8384
@Component()
8485
class A {}
8586

8687
ComponentUtil.getComponentData(A).aliasTokens = tokenArray;
8788

88-
//when
89+
// when
8990
let aliasTokensA = ComponentUtil.getAliasTokens(A);
9091

91-
//then
92+
// then
9293
expect(aliasTokensA).to.eql(tokenArray);
9394
});
9495

9596
it('should get the injection data for the given target', function () {
96-
//given
97+
// given
9798
let givenInjectionData = new InjectionData();
9899

99100
@Component()
100101
class A {}
101102

102103
ComponentUtil.getComponentData(A).injectionData = givenInjectionData;
103104

104-
//when
105+
// when
105106
let injectionData = ComponentUtil.getInjectionData(A);
106107

107-
//then
108+
// then
108109
expect(injectionData).to.eql(givenInjectionData);
109110
});
110111

111112
it('should return if instance is controller', function () {
112-
//given
113+
// given
113114
@Controller()
114115
class A {}
115116

@@ -118,14 +119,14 @@ describe('ComponentUtil', function () {
118119

119120
class C {}
120121

121-
//when / then
122+
// when / then
122123
expect(ComponentUtil.isController(A)).to.be.true;
123124
expect(ComponentUtil.isController(B)).to.be.false;
124125
expect(ComponentUtil.isController(C)).to.be.false;
125126
});
126127

127128
it('should return if instance is interceptor', function () {
128-
//given
129+
// given
129130
@Interceptor()
130131
class A {}
131132

@@ -134,7 +135,7 @@ describe('ComponentUtil', function () {
134135

135136
class C {}
136137

137-
//when / then
138+
// when / then
138139
expect(ComponentUtil.isInterceptor(A)).to.be.true;
139140
expect(ComponentUtil.isInterceptor(B)).to.be.false;
140141
expect(ComponentUtil.isInterceptor(C)).to.be.false;

0 commit comments

Comments
 (0)