Skip to content

Commit

Permalink
Tidy up @Inject for various cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Raymond Feng committed Jun 13, 2017
1 parent 6259e4b commit 4a1311a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 20 deletions.
44 changes: 27 additions & 17 deletions packages/context/src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { Reflector } from './reflect';
import { BoundValue, ValueOrPromise } from './binding';
import { Context } from './context';

const REFLECTION_CDI_KEY = 'loopback:inject:constructor';
const REFLECTION_PDI_KEY = 'loopback:inject:properties';
const PARAMETERS_KEY = 'inject:parameters';
const PROPERTIES_KEY = 'inject:properties';

/**
* A function to provide resolution of injected values
Expand Down Expand Up @@ -55,41 +55,51 @@ export interface Injection {
*/
export function inject(bindingKey: string, metadata?: Object, resolve?: ResolverFunction) {
// tslint:disable-next-line:no-any
return function markArgumentAsInjected(target: any, propertyKey?: string | symbol,
return function markParameterOrPropertyAsInjected(target: any, propertyKey?: string | symbol,
propertyDescriptorOrParameterIndex?: TypedPropertyDescriptor<BoundValue> | number) {

if (typeof propertyDescriptorOrParameterIndex === 'number') {
// The decorator is applied to a method parameter
// Please note propertyKey is `undefined` for constructor
const injectedArgs: Injection[] =
Reflector.getOwnMetadata(REFLECTION_CDI_KEY, target, propertyKey!) || [];
Reflector.getOwnMetadata(PARAMETERS_KEY, target, propertyKey!) || [];
injectedArgs[propertyDescriptorOrParameterIndex] = {bindingKey, metadata, resolve};
Reflector.defineMetadata(REFLECTION_CDI_KEY, injectedArgs, target, propertyKey!);
Reflector.defineMetadata(PARAMETERS_KEY, injectedArgs, target, propertyKey!);
} else if (propertyKey) {
if (typeof Object.getPrototypeOf(target) === 'function') {
const prop = target.name + '.' + propertyKey.toString();
throw new Error('@inject is not supported for a static property: ' + prop);
}
// The decorator is applied to a property
const injections: { [p: string]: Injection } =
Reflector.getOwnMetadata(REFLECTION_PDI_KEY, target) || {};
Reflector.getOwnMetadata(PROPERTIES_KEY, target) || {};
injections[propertyKey] = {bindingKey, metadata, resolve};
Reflector.defineMetadata(REFLECTION_PDI_KEY, injections, target);
Reflector.defineMetadata(PROPERTIES_KEY, injections, target);
} else {
throw new Error('@inject can be used on properties or method parameters.');
throw new Error('@inject can only be used on properties or method parameters.');
}
};
}

/**
* Return an array of injection objects for constructor parameters
* @param target The target class
* Return an array of injection objects for parameters
* @param target The target class for constructor or static methods, or the prototype
* for instance methods
* @param methodName Method name, undefined for constructor
*/
export function describeInjectedArguments(target: Function): Injection[] {
return Reflector.getOwnMetadata(REFLECTION_CDI_KEY, target) || [];
// tslint:disable-next-line:no-any
export function describeInjectedArguments(target: any, method?: string | symbol): Injection[] {
if (method) {
return Reflector.getOwnMetadata(PARAMETERS_KEY, target, method) || [];
} else {
return Reflector.getOwnMetadata(PARAMETERS_KEY, target) || [];
}
}

/**
* Return a map of injection objects for properties
* @param target The target class. Please note a property decorator function receives
* the target.prototype
* @param target The target class for static properties or prototype for instance properties.
*/
export function describeInjectedProperties(target: Function): { [p: string]: Injection } {
return Reflector.getOwnMetadata(REFLECTION_PDI_KEY, target.prototype) || {};
// tslint:disable-next-line:no-any
export function describeInjectedProperties(target: any): { [p: string]: Injection } {
return Reflector.getOwnMetadata(PROPERTIES_KEY, target) || {};
}
2 changes: 1 addition & 1 deletion packages/context/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function resolveInjectedArguments(fn: Function, ctx: Context): BoundValue
export type KV = { [p: string]: BoundValue };

export function resolveInjectedProperties(fn: Function, ctx: Context): KV | Promise<KV> {
const injectedProperties = describeInjectedProperties(fn);
const injectedProperties = describeInjectedProperties(fn.prototype);

const properties: KV = {};
let asyncResolvers: Promise<void>[] | undefined = undefined;
Expand Down
33 changes: 31 additions & 2 deletions packages/context/test/unit/inject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ describe('function argument injection', () => {
expect(meta.map(m => m.bindingKey)).to.deepEqual(['foo']);
});

it('can retrieve information about injected method arguments', () => {
class TestClass {
test(@inject('foo') foo: string) {
}
}

const meta = describeInjectedArguments(TestClass.prototype, 'test');
expect(meta.map(m => m.bindingKey)).to.deepEqual(['foo']);
});

it('can retrieve information about injected static method arguments', () => {
class TestClass {
static test(@inject('foo') foo: string) {
}
}

const meta = describeInjectedArguments(TestClass, 'test');
expect(meta.map(m => m.bindingKey)).to.deepEqual(['foo']);
});

it('returns an empty array when no ctor arguments are decorated', () => {
class TestClass {
constructor(foo: string) {
Expand All @@ -49,7 +69,7 @@ describe('property injection', () => {
@inject('foo') foo: string;
}

const meta = describeInjectedProperties(TestClass);
const meta = describeInjectedProperties(TestClass.prototype);
expect(meta.foo.bindingKey).to.eql('foo');
});

Expand All @@ -58,7 +78,16 @@ describe('property injection', () => {
foo: string;
}

const meta = describeInjectedProperties(TestClass);
const meta = describeInjectedProperties(TestClass.prototype);
expect(meta).to.deepEqual({});
});

it('cannot decorate static properties', () => {
expect(() => {
class TestClass {
@inject('foo') static foo: string;
}
}).to.throw(/@inject is not supported for a static property/);
});

});

0 comments on commit 4a1311a

Please sign in to comment.