Skip to content

Commit

Permalink
chore(context): review - 6
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Jan 15, 2019
1 parent f91d3ee commit b25d990
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 76 deletions.
10 changes: 5 additions & 5 deletions docs/site/Decorators_inject.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ from bindings that match a filter function.

```ts
class MyControllerWithGetter {
@inject.getter(bindingTagFilter('prime'))
@inject.getter(filterByTag('prime'))
getter: Getter<number[]>;
}
```
Expand Down Expand Up @@ -179,7 +179,7 @@ import {DataSource} from '@loopback/repository';

export class DataSourceTracker {
constructor(
@inject.view(bindingTagFilter('datasource'))
@inject.view(filterByTag('datasource'))
private dataSources: ContextView<DataSource[]>,
) {}

Expand All @@ -190,9 +190,9 @@ export class DataSourceTracker {
}
```

In the example above, `bindingTagFilter` is a helper function that creates a
filter function that matches a given tag. You can define your own filter
functions, such as:
In the example above, `filterByTag` is a helper function that creates a filter
function that matches a given tag. You can define your own filter functions,
such as:

```ts
export class DataSourceTracker {
Expand Down
25 changes: 13 additions & 12 deletions packages/context/src/binding-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ export type BindingFilter<ValueType = unknown> = (

/**
* Create a binding filter for the tag pattern
* @param tagPattern
* @param tagPattern Binding tag name, regexp, or object
*/
export function bindingTagFilter(tagPattern: BindingTag | RegExp) {
let bindingFilter: BindingFilter;
export function filterByTag(tagPattern: BindingTag | RegExp): BindingFilter {
if (typeof tagPattern === 'string' || tagPattern instanceof RegExp) {
const regexp =
typeof tagPattern === 'string'
? wildcardToRegExp(tagPattern)
: tagPattern;
bindingFilter = b => Array.from(b.tagNames).some(t => regexp!.test(t));
return b => Array.from(b.tagNames).some(t => regexp!.test(t));
} else {
bindingFilter = b => {
return b => {
for (const t in tagPattern) {
// One tag name/value does not match
if (b.tagMap[t] !== tagPattern[t]) return false;
Expand All @@ -35,22 +34,24 @@ export function bindingTagFilter(tagPattern: BindingTag | RegExp) {
return true;
};
}
return bindingFilter;
}

/**
* Create a binding filter from key pattern
* @param keyPattern Binding key, wildcard, or regexp
* @param keyPattern Binding key/wildcard, regexp, or a filter function
*/
export function bindingKeyFilter(keyPattern?: string | RegExp) {
let filter: BindingFilter = binding => true;
export function filterByKey(
keyPattern?: string | RegExp | BindingFilter,
): BindingFilter {
if (typeof keyPattern === 'string') {
const regex = wildcardToRegExp(keyPattern);
filter = binding => regex.test(binding.key);
return binding => regex.test(binding.key);
} else if (keyPattern instanceof RegExp) {
filter = binding => keyPattern.test(binding.key);
return binding => keyPattern.test(binding.key);
} else if (typeof keyPattern === 'function') {
return keyPattern;
}
return filter;
return () => true;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/context/src/binding-inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export type BindingFromClassOptions = {
/**
* Binding key
*/
key?: BindingAddress<unknown>;
key?: BindingAddress;
/**
* Artifact type, such as `server`, `controller`, `repository` or `service`
*/
Expand Down
23 changes: 7 additions & 16 deletions packages/context/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import * as debugModule from 'debug';
import {v1 as uuidv1} from 'uuid';
import {ValueOrPromise} from '.';
import {Binding, BindingTag} from './binding';
import {
BindingFilter,
bindingKeyFilter,
bindingTagFilter,
} from './binding-filter';
import {BindingFilter, filterByKey, filterByTag} from './binding-filter';
import {BindingAddress, BindingKey} from './binding-key';
import {
ContextEventListener,
Expand Down Expand Up @@ -130,7 +126,7 @@ export class Context {
* @param key Binding key
* @returns true if the binding key is found and removed from this context
*/
unbind(key: BindingAddress<unknown>): boolean {
unbind(key: BindingAddress): boolean {
key = BindingKey.validate(key);
const binding = this.registry.get(key);
if (binding == null) return false;
Expand Down Expand Up @@ -218,7 +214,7 @@ export class Context {
* delegating to the parent context
* @param key Binding key
*/
contains(key: BindingAddress<unknown>): boolean {
contains(key: BindingAddress): boolean {
key = BindingKey.validate(key);
return this.registry.has(key);
}
Expand All @@ -227,7 +223,7 @@ export class Context {
* Check if a key is bound in the context or its ancestors
* @param key Binding key
*/
isBound(key: BindingAddress<unknown>): boolean {
isBound(key: BindingAddress): boolean {
if (this.contains(key)) return true;
if (this._parent) {
return this._parent.isBound(key);
Expand All @@ -239,7 +235,7 @@ export class Context {
* Get the owning context for a binding key
* @param key Binding key
*/
getOwnerContext(key: BindingAddress<unknown>): Context | undefined {
getOwnerContext(key: BindingAddress): Context | undefined {
if (this.contains(key)) return this;
if (this._parent) {
return this._parent.getOwnerContext(key);
Expand Down Expand Up @@ -271,12 +267,7 @@ export class Context {
pattern?: string | RegExp | BindingFilter,
): Readonly<Binding<ValueType>>[] {
const bindings: Readonly<Binding>[] = [];
const filter: BindingFilter =
pattern == null ||
typeof pattern === 'string' ||
pattern instanceof RegExp
? bindingKeyFilter(pattern)
: pattern;
const filter = filterByKey(pattern);

for (const b of this.registry.values()) {
if (filter(b)) bindings.push(b);
Expand All @@ -303,7 +294,7 @@ export class Context {
findByTag<ValueType = BoundValue>(
tagFilter: BindingTag | RegExp,
): Readonly<Binding<ValueType>>[] {
return this.find(bindingTagFilter(tagFilter));
return this.find(filterByTag(tagFilter));
}

protected _mergeWithParent<ValueType>(
Expand Down
26 changes: 18 additions & 8 deletions packages/context/src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PropertyDecoratorFactory,
} from '@loopback/metadata';
import {BindingTag} from './binding';
import {BindingFilter, bindingTagFilter} from './binding-filter';
import {BindingFilter, filterByTag} from './binding-filter';
import {BindingAddress} from './binding-key';
import {Context} from './context';
import {ContextView} from './context-view';
Expand Down Expand Up @@ -273,15 +273,15 @@ export namespace inject {
{decorator: '@inject.tag', tag: bindingTag},
metadata,
);
return inject(bindingTagFilter(bindingTag), metadata);
return inject(filterByTag(bindingTag), metadata);
};

/**
* Inject matching bound values by the filter function
*
* ```ts
* class MyControllerWithView {
* @inject.view(Context.bindingTagFilter('foo'))
* @inject.view(filterByTag('foo'))
* view: ContextView<string[]>;
* }
* ```
Expand Down Expand Up @@ -318,8 +318,10 @@ function resolveAsGetter(
) {
const targetType = inspectTargetType(injection);
if (targetType && targetType !== Function) {
const targetName = ResolutionSession.describeInjection(injection)!
.targetName;
throw new Error(
`The target type ${targetType.name} is not a Getter function`,
`The type of ${targetName} (${targetType.name}) is not a Getter function`,
);
}
if (typeof injection.bindingSelector === 'function') {
Expand All @@ -328,7 +330,8 @@ function resolveAsGetter(
// We need to clone the session for the getter as it will be resolved later
session = ResolutionSession.fork(session);
return function getter() {
return ctx.get(injection.bindingSelector as BindingAddress, {
const key = injection.bindingSelector as BindingAddress;
return ctx.get(key, {
session,
optional: injection.metadata && injection.metadata.optional,
});
Expand All @@ -338,13 +341,16 @@ function resolveAsGetter(
function resolveAsSetter(ctx: Context, injection: Injection) {
const targetType = inspectTargetType(injection);
if (targetType && targetType !== Function) {
const targetName = ResolutionSession.describeInjection(injection)!
.targetName;
throw new Error(
`The target type ${targetType.name} is not a Setter function`,
`The type of ${targetName} (${targetType.name}) is not a Setter function`,
);
}
// No resolution session should be propagated into the setter
return function setter(value: unknown) {
ctx.bind(injection.bindingSelector as BindingAddress).to(value);
const key = injection.bindingSelector as BindingAddress;
ctx.bind(key).to(value);
};
}

Expand Down Expand Up @@ -417,7 +423,11 @@ function resolveByFilter(
const targetType = inspectTargetType(injection);
if (injection.metadata && injection.metadata.decorator === '@inject.view') {
if (targetType && targetType !== ContextView) {
throw new Error(`The target type ${targetType.name} is not ContextView`);
const targetName = ResolutionSession.describeInjection(injection)!
.targetName;
throw new Error(
`The type of ${targetName} (${targetType.name}) is not ContextView`,
);
}
}
const bindingFilter = injection.bindingSelector as BindingFilter;
Expand Down
16 changes: 7 additions & 9 deletions packages/context/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,13 @@ function resolve<T>(
return injection.resolve(ctx, injection, s);
} else {
// Default to resolve the value from the context by binding key
return ctx.getValueOrPromise(
injection.bindingSelector as BindingAddress<unknown>,
{
session: s,
// If the `optional` flag is set for the injection, the resolution
// will return `undefined` instead of throwing an error
optional: injection.metadata && injection.metadata.optional,
},
);
const key = injection.bindingSelector as BindingAddress;
return ctx.getValueOrPromise(key, {
session: s,
// If the `optional` flag is set for the injection, the resolution
// will return `undefined` instead of throwing an error
optional: injection.metadata && injection.metadata.optional,
});
}
},
injection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('Context bindings - Injecting dependencies of classes', () => {

ctx.bind('store').toClass(Store);
expect(() => ctx.getSync<Store>('store')).to.throw(
/The target type String is not a Getter function/,
'The type of Store.constructor[0] (String) is not a Getter function',
);
});

Expand All @@ -203,7 +203,7 @@ describe('Context bindings - Injecting dependencies of classes', () => {

ctx.bind('store').toClass(Store);
expect(() => ctx.getSync<Store>('store')).to.throw(
/The target type Object is not a Setter function/,
'The type of Store.constructor[0] (Object) is not a Setter function',
);
});

Expand Down
12 changes: 6 additions & 6 deletions packages/context/test/acceptance/context-view.acceptance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect} from '@loopback/testlab';
import {
Binding,
bindingTagFilter,
filterByTag,
Context,
ContextEventListener,
ContextEventType,
Expand Down Expand Up @@ -31,7 +31,7 @@ describe('ContextView - watches matching bindings', () => {

function givenControllerWatcher() {
server = givenServerWithinAnApp();
contextWatcher = server.createView(bindingTagFilter('controller'));
contextWatcher = server.createView(filterByTag('controller'));
givenController(server, '1');
givenController(server.parent!, '2');
}
Expand All @@ -57,19 +57,19 @@ describe('@inject.* - injects a live collection of matching bindings', async ()
beforeEach(givenPrimeNumbers);

class MyControllerWithGetter {
@inject.getter(bindingTagFilter('prime'), {watch: true})
@inject.getter(filterByTag('prime'), {watch: true})
getter: Getter<number[]>;
}

class MyControllerWithValues {
constructor(
@inject(bindingTagFilter('prime'))
@inject(filterByTag('prime'))
public values: number[],
) {}
}

class MyControllerWithView {
@inject.view(bindingTagFilter('prime'))
@inject.view(filterByTag('prime'))
view: ContextView<number[]>;
}

Expand Down Expand Up @@ -139,7 +139,7 @@ describe('ContextEventListener - listens on matching bindings', () => {

class MyListenerForControllers implements ContextEventListener {
controllers: Set<string> = new Set();
filter = bindingTagFilter('controller');
filter = filterByTag('controller');
listen(event: ContextEventType, binding: Readonly<Binding<unknown>>) {
if (event === 'bind') {
this.controllers.add(binding.tagMap.name);
Expand Down
10 changes: 2 additions & 8 deletions packages/context/test/unit/context-view.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {
Binding,
BindingScope,
bindingTagFilter,
Context,
ContextView,
} from '../..';
import {Binding, BindingScope, filterByTag, Context, ContextView} from '../..';

describe('ContextView', () => {
let ctx: Context;
Expand Down Expand Up @@ -97,7 +91,7 @@ describe('ContextView', () => {
function givenContextView() {
bindings = [];
ctx = givenContext(bindings);
contextView = ctx.createView(bindingTagFilter('foo'));
contextView = ctx.createView(filterByTag('foo'));
}
});

Expand Down
Loading

0 comments on commit b25d990

Please sign in to comment.