Skip to content

Commit

Permalink
fix(context): relax checking on instances of BindingKey class
Browse files Browse the repository at this point in the history
Fixes #4570


Please note that `bindingSelector instanceof BindingKey` is not always
reliable as the `@loopback/context` module might be loaded from multiple
locations if `npm install` does not dedupe or there are mixed versions in
the tree.
  • Loading branch information
raymondfeng committed Feb 6, 2020
1 parent 7dc1c70 commit 8668eb6
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
37 changes: 36 additions & 1 deletion packages/context/src/__tests__/unit/binding-filter.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Binding, filterByKey, filterByTag, isBindingTagFilter} from '../..';
import {
Binding,
BindingFilter,
BindingKey,
filterByKey,
filterByTag,
isBindingAddress,
isBindingTagFilter,
} from '../..';

const key = 'foo';

Expand Down Expand Up @@ -72,6 +80,33 @@ describe('BindingFilter', () => {
});
});

describe('isBindingAddress', () => {
it('allows binding selector to be a string', () => {
expect(isBindingAddress('controllers.MyController')).to.be.true();
});

it('allows binding selector to be a BindingKey', () => {
expect(
isBindingAddress(BindingKey.create('controllers.MyController')),
).to.be.true();
});

it('does not allow binding selector to be an object', () => {
const filter: BindingFilter = () => true;
expect(isBindingAddress(filter)).to.be.false();
});

it('allows binding selector to be a BindingKey by duck-typing', () => {
// Please note that TypeScript checks types by duck-typing
// See https://www.typescriptlang.org/docs/handbook/interfaces.html#introduction
const selector: BindingKey<unknown> = {
key: 'x',
deepProperty: () => BindingKey.create('y'),
};
expect(isBindingAddress(selector)).to.be.true();
});
});

describe('BindingTagFilter', () => {
it('allows tag name as string', () => {
const filter = filterByTag('controller');
Expand Down
22 changes: 20 additions & 2 deletions packages/context/src/binding-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {Binding, BindingTag} from './binding';
import {BindingAddress, BindingKey} from './binding-key';
import {BindingAddress} from './binding-key';
import {MapObject} from './value-promise';

/**
Expand Down Expand Up @@ -50,6 +50,18 @@ export type BindingSelector<ValueType = unknown> =
| BindingAddress<ValueType>
| BindingFilter<ValueType>;

/**
* Check if an object is a `BindingKey` by duck typing
* @param selector Binding selector
*/
function isBindingKey(selector: BindingSelector) {
if (selector == null || typeof selector !== 'object') return false;
return (
typeof selector.key === 'string' &&
typeof selector.deepProperty === 'function'
);
}

/**
* Type guard for binding address
* @param bindingSelector - Binding key or filter function
Expand All @@ -58,7 +70,13 @@ export function isBindingAddress(
bindingSelector: BindingSelector,
): bindingSelector is BindingAddress {
return (
typeof bindingSelector === 'string' || bindingSelector instanceof BindingKey
typeof bindingSelector !== 'function' &&
(typeof bindingSelector === 'string' ||
// See https://github.com/strongloop/loopback-next/issues/4570
// `bindingSelector instanceof BindingKey` is not always reliable as the
// `@loopback/context` module might be loaded from multiple locations if
// `npm install` does not dedupe or there are mixed versions in the tree
isBindingKey(bindingSelector))
);
}

Expand Down

0 comments on commit 8668eb6

Please sign in to comment.