Skip to content

Commit

Permalink
feat: Refactor REST decorators to use factories
Browse files Browse the repository at this point in the history
  • Loading branch information
Raymond Feng authored and raymondfeng committed Dec 15, 2017
1 parent c683019 commit d03adf7
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 181 deletions.
92 changes: 79 additions & 13 deletions packages/metadata/src/decorator-factory.ts
Expand Up @@ -10,17 +10,6 @@ const debug = debugModule('loopback:metadata:decorator');

// tslint:disable:no-any

function cloneDeep<T>(val: T): T {
if (val === undefined) {
return {} as T;
}
return _.cloneDeepWith(val, v => {
// Do not clone functions
if (typeof v === 'function') return v;
return undefined;
});
}

/**
* An object mapping keys to corresponding metadata
*/
Expand Down Expand Up @@ -197,6 +186,8 @@ export class DecoratorFactory<
if (typeof spec === 'object' && spec != null) {
const specWithTarget = spec as any;
return specWithTarget[DecoratorFactory.TARGET];
} else {
return undefined;
}
}

Expand Down Expand Up @@ -256,7 +247,9 @@ export class DecoratorFactory<
if (meta == null && this.allowInheritance()) {
// Clone the base metadata so that it won't be accidentally
// mutated by sub classes
meta = cloneDeep(Reflector.getMetadata(this.key, target));
meta = DecoratorFactory.cloneDeep(
Reflector.getMetadata(this.key, target),
);
meta = this.processInherited(meta, target, member, descriptorOrIndex);
if (debug.enabled) {
debug('%s: %j', targetName, meta);
Expand Down Expand Up @@ -285,6 +278,17 @@ export class DecoratorFactory<
const inst = new this<T, M, D>(key, spec, options);
return inst.create();
}

static cloneDeep<T>(val: T): T {
if (val === undefined) {
return {} as T;
}
return _.cloneDeepWith(val, v => {
// Do not clone functions
if (typeof v === 'function') return v;
return undefined;
});
}
}

/**
Expand Down Expand Up @@ -422,7 +426,8 @@ export class MethodDecoratorFactory<T> extends DecoratorFactory<
methodDescriptor?: TypedPropertyDescriptor<any> | number,
) {
if (localMeta == null) localMeta = {};
if (localMeta[methodName!] != null) {
const methodMeta = localMeta[methodName!];
if (this.getTarget(methodMeta) === target) {
throw new Error(
'Decorator cannot be applied more than once on ' +
this.getTargetName(target, methodName, methodDescriptor),
Expand Down Expand Up @@ -542,3 +547,64 @@ export class ParameterDecoratorFactory<T> extends DecoratorFactory<
);
}
}

/**
* Factory for method level parameter decorator. For example, the following
* code uses `@param` to declare two parameters for `greet()`.
* ```ts
* class MyController {
* @param('name') // Parameter 0
* @param('msg') // Parameter 1
* greet() {}
* }
* ```
*/
export class MethodParameterDecoratorFactory<T> extends DecoratorFactory<
T,
MetadataMap<T[]>,
MethodDecorator
> {
protected processInherited(
baseMeta: MetadataMap<T[]>,
target: Object,
methodName?: string | symbol,
methodDescriptor?: TypedPropertyDescriptor<any> | number,
) {
return {[methodName!]: [this.spec]};
}

protected processLocal(
localMeta: MetadataMap<T[]>,
target: Object,
methodName?: string | symbol,
methodDescriptor?: TypedPropertyDescriptor<any> | number,
) {
if (localMeta == null) localMeta = {};
let params = localMeta[methodName!];
params = [this.spec].concat(params);
localMeta[methodName!] = params;
return localMeta;
}

create(): MethodDecorator {
return (
target: Object,
methodName: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => this.decorate(target, methodName, descriptor);
}

/**
* Create a method decorator function
* @param key Metadata key
* @param spec Metadata object from the decorator function
* @param options Options for the decorator
*/
static createDecorator<T>(key: string, spec: T, options?: DecoratorOptions) {
return super._createDecorator<T, MetadataMap<T[]>, MethodDecorator>(
key,
spec,
options,
);
}
}
40 changes: 32 additions & 8 deletions packages/metadata/src/inspector.ts
Expand Up @@ -26,8 +26,14 @@ export class MetadataInspector {
* @param key Metadata key
* @param target Class that contains the metadata
*/
static getClassMetadata<T>(key: string, target: Function): T | undefined {
return Reflector.getMetadata(key, target);
static getClassMetadata<T>(
key: string,
target: Function,
ownOnly?: boolean,
): T | undefined {
return ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
}

/**
Expand All @@ -39,8 +45,11 @@ export class MetadataInspector {
static getAllMethodMetadata<T>(
key: string,
target: Object,
ownOnly?: boolean,
): MetadataMap<T> | undefined {
return Reflector.getMetadata(key, target);
return ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
}

/**
Expand All @@ -55,9 +64,12 @@ export class MetadataInspector {
key: string,
target: Object,
methodName?: string | symbol,
ownOnly?: boolean,
): T | undefined {
methodName = methodName || '';
const meta: MetadataMap<T> = Reflector.getMetadata(key, target);
const meta: MetadataMap<T> = ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
return meta && meta[methodName];
}

Expand All @@ -70,8 +82,11 @@ export class MetadataInspector {
static getAllPropertyMetadata<T>(
key: string,
target: Object,
ownOnly?: boolean,
): MetadataMap<T> | undefined {
return Reflector.getMetadata(key, target);
return ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
}

/**
Expand All @@ -86,8 +101,11 @@ export class MetadataInspector {
key: string,
target: Object,
propertyName: string | symbol,
ownOnly?: boolean,
): T | undefined {
const meta: MetadataMap<T> = Reflector.getMetadata(key, target);
const meta: MetadataMap<T> = ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
return meta && meta[propertyName];
}

Expand All @@ -103,9 +121,12 @@ export class MetadataInspector {
key: string,
target: Object,
methodName?: string | symbol,
ownOnly?: boolean,
): T[] | undefined {
methodName = methodName || '';
const meta: MetadataMap<T[]> = Reflector.getMetadata(key, target);
const meta: MetadataMap<T[]> = ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
return meta && meta[methodName];
}

Expand All @@ -123,9 +144,12 @@ export class MetadataInspector {
target: Object,
methodName: string | symbol,
index: number,
ownOnly?: boolean,
): T | undefined {
methodName = methodName || '';
const meta: MetadataMap<T[]> = Reflector.getMetadata(key, target);
const meta: MetadataMap<T[]> = ownOnly
? Reflector.getOwnMetadata(key, target)
: Reflector.getMetadata(key, target);
const params = meta && meta[methodName];
return params && params[index];
}
Expand Down

0 comments on commit d03adf7

Please sign in to comment.