Skip to content

Commit b886442

Browse files
[#39] Refactoring: improve error handling
- created DecoratorUtils.getSubjectName() and used it in DecoratorUsageError-s - throwing DecoratorUsageError when decorators used on wrong [class/method/property] , solving issue [#22] - changed @autowire() to @Autowired()
1 parent 5b4edcc commit b886442

29 files changed

+300
-77
lines changed

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export {ComponentScan} from "./lib/decorators/ComponentScanDecorator";
77
export {Import} from "./lib/decorators/ImportDecorator";
88
export {Configuration} from "./lib/decorators/ConfigurationDecorator";
99
export {Controller} from "./lib/decorators/ControllerDecorator";
10-
export {Inject, Value, Autowire} from "./lib/decorators/InjectionDecorators";
10+
export {Inject, Value, Autowired} from "./lib/decorators/InjectionDecorators";
1111
export {PostConstruct, PreDestroy} from "./lib/decorators/LifeCycleHooksDecorators"
1212
export {Profile} from "./lib/decorators/ComponentDecorator";
1313
export {PropertySource} from "./lib/decorators/PropertySourceDecorator";

src/lib/decorators/ComponentDecorator.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { InjectUtil, InjectionData } from "./InjectionDecorators";
22
import { CONTROLLER_DECORATOR_TOKEN } from "./ControllerDecorator";
33
import { INTERCEPTOR_DECORATOR_TOKEN } from "../interceptors/InterceptorDecorator";
44
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
5+
import { DecoratorUtil, DecoratorType } from "../helpers/DecoratorUtils";
56

67
export class ComponentData {
78
classToken: Symbol;
@@ -20,6 +21,11 @@ const COMPONENT_DECORATOR_TOKEN = Symbol('component_decorator_token');
2021

2122
export function Component() {
2223
return function (target) {
24+
let args = Array.prototype.slice.call(arguments);
25+
if (!DecoratorUtil.isType(DecoratorType.CLASS, args)) {
26+
let subjectName = DecoratorUtil.getSubjectName(args);
27+
throw new DecoratorUsageError(`@Component can be set only on a class! (${subjectName})`);
28+
}
2329
let componentData = new ComponentData();
2430
componentData.injectionData = InjectUtil.initIfDoesntExist(target.prototype);
2531
target[COMPONENT_DECORATOR_TOKEN] = componentData;
@@ -29,7 +35,8 @@ export function Component() {
2935
export function Profile(profile: string) {
3036
return function (target) {
3137
if (!ComponentUtil.isComponent(target)) {
32-
throw new DecoratorUsageError(`@Profile can be set only on @Component! (${target.name})`);
38+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
39+
throw new DecoratorUsageError(`@Profile can be set only on @Component! (${subjectName})`);
3340
}
3441
ComponentUtil.getComponentData(target).profile = profile;
3542
};

src/lib/decorators/ComponentScanDecorator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ConfigurationData, ConfigurationUtil } from "./ConfigurationDecorator";
55
import { ComponentUtil } from "./ComponentDecorator";
66
import { RequireUtils } from "../helpers/RequireUtils";
77
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
8+
import { DecoratorUtil } from "../helpers/DecoratorUtils";
89

910
/**
1011
*A decorator for setting up project files to be component-scanned.
@@ -14,7 +15,8 @@ import { DecoratorUsageError } from "../errors/DecoratorUsageError";
1415
export function ComponentScan(path) {
1516
return function (target) {
1617
if (!ConfigurationUtil.isConfigurationClass(target)) {
17-
throw new DecoratorUsageError(`@ComponentScan is allowed on @Configuration classes only! (${target.name})`);
18+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
19+
throw new DecoratorUsageError(`@ComponentScan is allowed on @Configuration classes only! (${subjectName})`);
1820
}
1921
ConfigurationUtil.addComponentScanPath(target, path);
2022
};

src/lib/decorators/ConfigurationDecorator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ComponentFactory } from "../di/ComponentFactory";
22
import { PropertySourceUtil } from "./PropertySourceDecorator";
33
import { ComponentScanUtil } from "./ComponentScanDecorator";
44
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
5+
import { DecoratorUtil, DecoratorType } from "../helpers/DecoratorUtils";
56

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

@@ -38,6 +39,10 @@ export class ConfigurationData {
3839

3940
export function Configuration() {
4041
return function (target) {
42+
if (!DecoratorUtil.isType(DecoratorType.CLASS, Array.prototype.slice.call(arguments))) {
43+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
44+
throw new DecoratorUsageError(`@Configuration can be set only on classes! (${subjectName})`);
45+
}
4146
if (target[CONFIGURATION_HOLDER_TOKEN]) {
4247
throw new DecoratorUsageError(`Duplicate @Configuration decorator' (${target.name})`);
4348
}
@@ -51,7 +56,8 @@ export class ConfigurationUtil {
5156

5257
static getConfigurationData(target): ConfigurationData {
5358
if (!this.isConfigurationClass(target)) {
54-
throw new Error(`${target.name} is not a @Configuration class`);
59+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
60+
throw new Error(`${subjectName} is not a @Configuration class`);
5561
}
5662
return target[CONFIGURATION_HOLDER_TOKEN];
5763
}

src/lib/decorators/ControllerDecorator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { Component } from "./ComponentDecorator";
2+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
3+
import { DecoratorUtil, DecoratorType } from "../helpers/DecoratorUtils";
24
export const CONTROLLER_DECORATOR_TOKEN = Symbol('controller_decorator_token');
35

46
export function Controller() {
57
return function (target) {
8+
if (!DecoratorUtil.isType(DecoratorType.CLASS, Array.prototype.slice.call(arguments))) {
9+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
10+
throw new DecoratorUsageError(`@Configuration can be set only on classes! (${subjectName})`);
11+
}
612
Component()(target);
713
target[CONTROLLER_DECORATOR_TOKEN] = true;
814
};

src/lib/decorators/ImportDecorator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ConfigurationUtil } from "./ConfigurationDecorator";
22
import { BadArgumentError } from "../errors/BadArgumentError";
33
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
4+
import { DecoratorUtil } from "../helpers/DecoratorUtils";
45

56
/**
67
* Decorator used for composing configuration classes by importing other configuration classes.
@@ -11,8 +12,8 @@ import { DecoratorUsageError } from "../errors/DecoratorUsageError";
1112
export function Import(...configurationClasses) {
1213
return function (targetConfigurationClass) {
1314
if (!ConfigurationUtil.isConfigurationClass(targetConfigurationClass)) {
14-
// tslint:disable-next-line
15-
throw new DecoratorUsageError(`@Import is allowed on @Configuration classes only! (${targetConfigurationClass.name})`);
15+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
16+
throw new DecoratorUsageError(`@Import is allowed on @Configuration classes only! (${subjectName})`);
1617
}
1718
let targetConfigurationData = ConfigurationUtil.getConfigurationData(targetConfigurationClass);
1819
for (let configurationClass of configurationClasses) {

src/lib/decorators/InjectionDecorators.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ComponentUtil } from "./ComponentDecorator";
22
import { TypeUtils } from "../helpers/TypeUtils";
33
import { InjectionError } from "../errors/InjectionError";
4+
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
5+
import { DecoratorType, DecoratorUtil } from "../helpers/DecoratorUtils";
46

57
const INJECT_DECORATOR_TOKEN = Symbol('injector_decorator_token');
68

@@ -24,15 +26,22 @@ export class InjectionData {
2426
}
2527

2628
export function Inject(dependencyToken?: Symbol) {
27-
return function (target: any, fieldName: string) {
29+
return function (...args) {
30+
if (!DecoratorUtil.isType(DecoratorType.PROPERTY, args)) {
31+
let subject = DecoratorUtil.getSubjectName(args);
32+
throw new DecoratorUsageError(`@Inject can be set only on properties of a @Component class! (${subject})`);
33+
}
34+
let target = args[0];
35+
let fieldName = args[1];
2836
let token = dependencyToken;
2937
let type = (<any> Reflect).getMetadata('design:type', target, fieldName);
3038
if (!token) {
3139
// fallback to field type
3240
if (ComponentUtil.isComponent(type)) {
3341
token = ComponentUtil.getClassToken(type);
3442
} else {
35-
throw new InjectionError(`Cannot inject dependency which is not a @Component! (${type.name})`);
43+
let sub = DecoratorUtil.getSubjectName(args);
44+
throw new InjectionError(`Cannot inject dependency (${type.name}) which is not a @Component! (${sub})`);
3645
}
3746
}
3847
// NOTE assumption: if type not declared or any then type is Object and isArray is false
@@ -41,12 +50,23 @@ export function Inject(dependencyToken?: Symbol) {
4150
};
4251
}
4352

44-
export function Autowire() {
45-
return Inject();
53+
export function Autowired() {
54+
return function (...args) {
55+
if (!DecoratorUtil.isType(DecoratorType.PROPERTY, args)) {
56+
let subj = DecoratorUtil.getSubjectName(args);
57+
throw new DecoratorUsageError(`@Autowired can be set only on properties of a @Component class! (${subj})`);
58+
}
59+
return Inject()(...args);
60+
};
4661
}
4762

4863
export function Value(preopertyKey) {
4964
return function (target: any, fieldName: string) {
65+
let args = Array.prototype.slice.call(arguments);
66+
if (!DecoratorUtil.isType(DecoratorType.PROPERTY, args)) {
67+
let subject = DecoratorUtil.getSubjectName(args);
68+
throw new DecoratorUsageError(`@Value can be set only on properties of a @Component class! (${subject})`);
69+
}
5070
InjectUtil.initIfDoesntExist(target).properties.set(fieldName, preopertyKey);
5171
};
5272
}

src/lib/decorators/LifeCycleHooksDecorators.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
2+
import { DecoratorUtil, DecoratorType } from "../helpers/DecoratorUtils";
23

34
const LIFE_CYCLE_HOOKS_TOKEN = Symbol('life_cycle_hooks_token');
45

@@ -12,11 +13,17 @@ export class LifeCycleHooksConfig {
1213
*/
1314
export function PostConstruct() {
1415
return function (target, methodName, descriptor: PropertyDescriptor) {
16+
let args = Array.prototype.slice.call(arguments);
17+
if (!DecoratorUtil.isType(DecoratorType.METHOD, args)) {
18+
let sub = DecoratorUtil.getSubjectName(args);
19+
throw new DecoratorUsageError(`@PostConstruct can be set only on methods of a @Component class! (${sub})`);
20+
}
1521
let conf = LifeCycleHooksUtil.initIfDoesntExist(target);
1622
if (conf.postConstructMethod) {
1723
let errorParams = [conf.postConstructMethod, methodName].join(', ');
24+
let subjectName = DecoratorUtil.getSubjectName(args);
1825
// tslint:disable-next-line
19-
throw new DecoratorUsageError(`@PostConstruct used on multiple methods (${errorParams}) within a component`);
26+
throw new DecoratorUsageError(`@PostConstruct used on multiple methods (${errorParams}) within a @Component (${subjectName})`);
2027
}
2128
conf.postConstructMethod = methodName;
2229
};
@@ -27,10 +34,17 @@ export function PostConstruct() {
2734
*/
2835
export function PreDestroy() {
2936
return function (target, methodName, descriptor: PropertyDescriptor) {
37+
let args = Array.prototype.slice.call(arguments);
38+
if (!DecoratorUtil.isType(DecoratorType.METHOD, args)) {
39+
let subject = DecoratorUtil.getSubjectName(args);
40+
throw new DecoratorUsageError(`@PreDestroy can be set only on methods of a @Component class! (${subject})`);
41+
}
3042
let conf = LifeCycleHooksUtil.initIfDoesntExist(target);
3143
if (conf.preDestroyMethod) {
3244
let errorParams = [conf.preDestroyMethod, methodName].join(', ');
33-
throw new DecoratorUsageError(`@PreDestroy used on multiple methods within a component (${errorParams})`);
45+
let subjectName = DecoratorUtil.getSubjectName(args);
46+
// tslint:disable-next-line
47+
throw new DecoratorUsageError(`@PreDestroy used on multiple methods (${errorParams}) within a @Component (${subjectName})`);
3448
}
3549
conf.preDestroyMethod = methodName;
3650
};

src/lib/decorators/PropertySourceDecorator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ConfigurationUtil } from "./ConfigurationDecorator";
33
import { GeneralUtils } from "../helpers/GeneralUtils";
44
import {RequireUtils} from "../helpers/RequireUtils";
55
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
6+
import { DecoratorUtil } from "../helpers/DecoratorUtils";
67

78
/**
89
* A decorator for defining a JSON property source for the configuration properties.
@@ -12,8 +13,8 @@ import { DecoratorUsageError } from "../errors/DecoratorUsageError";
1213
export function PropertySource(path: string) {
1314
return function (target) {
1415
if (!ConfigurationUtil.isConfigurationClass(target)) {
15-
// tslint:disable-next-line
16-
throw new DecoratorUsageError(`@PropertySource can be used only on @Configuration classes! (${target.name})`);
16+
let subject = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
17+
throw new DecoratorUsageError(`@PropertySource is allowed on @Configuration classes only! (${subject})`);
1718
}
1819
ConfigurationUtil.addPropertySourcePath(target, path);
1920
};

src/lib/decorators/QualifierDecorator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { ComponentUtil } from "./ComponentDecorator";
22
import { DecoratorUsageError } from "../errors/DecoratorUsageError";
3+
import { DecoratorUtil } from "../helpers/DecoratorUtils";
34

45
export function Qualifier(token: Symbol) {
56
return function (target) {
67
if (!ComponentUtil.isComponent(target)) {
7-
throw new DecoratorUsageError(`@Qualifier can be used only on @Component classes! (${target.name})`);
8+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
9+
throw new DecoratorUsageError(`@Qualifier can be used only on @Component classes! (${subjectName})`);
810
}
911
ComponentUtil.getAliasTokens(target).push(token);
1012
};

src/lib/decorators/RequestMappingDecorator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export function RequestMapping(config: RequestMappingConfig) {
6363
// TODO: refactor when new options are added on @RequestMapping for classes
6464
target[CLASS_ROUTER_CONFIG] = config.path;
6565
} else {
66-
throw new DecoratorUsageError(`@RequestMapping can only be used on classes and methods! (${target.name})`);
66+
let subjectName = DecoratorUtil.getSubjectName(Array.prototype.slice.call(arguments));
67+
throw new DecoratorUsageError(`@RequestMapping can only be used on classes and methods! (${subjectName})`);
6768
}
6869
};
6970
}

src/lib/decorators/ViewDecorator.ts

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

46
export function View(name?: string) {
57
return function (target, methodName) {
8+
let args = Array.prototype.slice.call(arguments);
9+
if (!DecoratorUtil.isType(DecoratorType.METHOD, args)) {
10+
let subject = DecoratorUtil.getSubjectName(args);
11+
throw new DecoratorUsageError(`@View can be set only on methods of a @Component class! (${subject})`);
12+
}
613
let viewName = name || methodName;
714
let routerConfig = RequestMappingUtil.initRouterConfigIfDoesntExist(target);
815
let routeConfig = _.find(routerConfig.routes, {methodHandler: methodName});

src/lib/helpers/DecoratorUtils.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class DecoratorType {
1212

1313
export class DecoratorUtil {
1414

15-
static getType(args): string {
15+
static getType(args: Array<any>): string {
1616
if (args.length === 1) {
1717
return DecoratorType.CLASS;
1818
} else if (args.length === 2) {
@@ -29,7 +29,17 @@ export class DecoratorUtil {
2929
}
3030
}
3131

32-
static isType(decoratorType, args): boolean {
32+
static isType(decoratorType: DecoratorType, args: Array<any>): boolean {
3333
return this.getType(args) === decoratorType;
3434
}
35+
36+
static getSubjectName (args: Array<any>) {
37+
if (this.isType(DecoratorType.CLASS, args)) {
38+
return args[0].name;
39+
}
40+
if (this.isType(DecoratorType.METHOD, args)) {
41+
return `${args[0].constructor.name}.${args[1]}()`;
42+
}
43+
return [args[0].constructor.name, args[1]].join('.');
44+
}
3545
}

src/lib/helpers/ProcessHandler.ts

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

34
export class ProcessHandler {
45

@@ -23,7 +24,7 @@ export class ProcessHandler {
2324

2425
registerOnExitListener(callback: Function) {
2526
if (!_.isFunction(callback)) {
26-
throw new Error('Passed callback must be a function!');
27+
throw new BadArgumentError('Passed callback must be a function!');
2728
}
2829

2930
this.onExitListeners.push(callback);

test/lib/decorators/ComponentDecorator.spec.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import {Controller} from "../../../src/lib/decorators/ControllerDecorator";
88
import {Interceptor} from "../../../src/lib/interceptors/InterceptorDecorator";
99
import { DecoratorUsageError } from "../../../src/lib/errors/DecoratorUsageError";
1010

11+
class MyClass {
12+
myProperty: string;
13+
myFunction() {} // tslint:disable-line
14+
}
15+
1116
describe('ComponentDecorator', function () {
1217

1318
it('should add metadata', function () {
@@ -25,6 +30,13 @@ describe('ComponentDecorator', function () {
2530
expect(componentData.injectionData).to.be.instanceOf(InjectionData);
2631
expect(componentData.profile).to.be.undefined;
2732
});
33+
34+
it('should throw when not on a class', function () {
35+
// given / when / then
36+
expect(Component().bind(undefined, MyClass, 'myFunction', MyClass.prototype.myFunction))
37+
.to.throw(DecoratorUsageError);
38+
expect(Component().bind(undefined, MyClass, 'myProperty')).to.throw(DecoratorUsageError);
39+
});
2840
});
2941

3042
describe('ProfileDecorator', function () {
@@ -43,11 +55,11 @@ describe('ProfileDecorator', function () {
4355
});
4456

4557
it('should throw error when @Profile is used on non Component', function () {
46-
// given
47-
class B {}
48-
49-
// when / then
50-
expect(Profile('dev').bind(this, B)).to.throw(DecoratorUsageError);
58+
// given / when / then
59+
expect(Profile('dev').bind(undefined, MyClass)).to.throw(DecoratorUsageError);
60+
expect(Profile('dev').bind(undefined, MyClass, 'myFunction', MyClass.prototype.myFunction))
61+
.to.throw(DecoratorUsageError);
62+
expect(Profile('dev').bind(undefined, MyClass, 'myProperty')).to.throw(DecoratorUsageError);
5163
});
5264
});
5365

test/lib/decorators/ComponentScanDecorator.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ describe('ComponentScanDecorator', function () {
2929

3030
it('should throw when not on @Configuration', function () {
3131
// given
32-
class A {}
32+
class MyClass {
33+
myProperty: string;
34+
myFunction() {} // tslint:disable-line
35+
}
3336

3437
// when / then
35-
expect(ComponentScan('somePath').bind(null, A)).to.throw(DecoratorUsageError);
38+
expect(ComponentScan('somePath').bind(undefined, MyClass)).to.throw(DecoratorUsageError);
39+
expect(ComponentScan('somePath').bind(undefined, MyClass, 'myFunction', MyClass.prototype.myFunction))
40+
.to.throw(DecoratorUsageError);
41+
expect(ComponentScan('somePath').bind(undefined, MyClass, 'myProperty')).to.throw(DecoratorUsageError);
3642
});
3743
});
3844

0 commit comments

Comments
 (0)