Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/context/src/__tests__/unit/binding-inspector.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ describe('createBindingFromClass()', () => {
expect(binding.key).to.eql('services.MyService');
});

it('includes class name in error messages', () => {
expect(() => {
// Reproduce a problem that @bajtos encountered when the project
// was not built correctly and somehow `@bind` was called with `undefined`
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@bind(undefined as any)
class MyClass {}

return createBindingFromClass(MyClass);
}).to.throw(/(while building binding for class MyClass)/);
});

function givenBindingFromClass(
cls: Constructor<unknown>,
ctx: Context = new Context(),
Expand Down
39 changes: 28 additions & 11 deletions packages/context/src/binding-inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
// License text available at https://opensource.org/licenses/MIT

import {MetadataAccessor, MetadataInspector} from '@loopback/metadata';
import * as debugFactory from 'debug';
import {Binding, BindingScope, BindingTag, BindingTemplate} from './binding';
import {BindingAddress} from './binding-key';
import {ContextTags} from './keys';
import {Provider} from './provider';
import {Constructor} from './value-promise';

const debug = debugFactory('loopback:context:binding-inspector');

/**
* Binding metadata from `@bind`
*/
Expand Down Expand Up @@ -63,10 +66,11 @@ export function isProviderClass(
export function asProvider(
target: Constructor<Provider<unknown>>,
): BindingTemplate {
return binding =>
return function bindAsProvider(binding) {
binding.toProvider(target).tag(ContextTags.PROVIDER, {
[ContextTags.TYPE]: ContextTags.PROVIDER,
});
};
}

/**
Expand All @@ -78,7 +82,7 @@ export function asClassOrProvider(
target: Constructor<unknown>,
): BindingTemplate {
// Add a template to bind to a class or provider
return binding => {
return function bindAsClassOrProvider(binding) {
if (isProviderClass(target)) {
asProvider(target)(binding);
} else {
Expand All @@ -94,7 +98,7 @@ export function asClassOrProvider(
export function asBindingTemplate(
scopeAndTags: BindingScopeAndTags,
): BindingTemplate {
return binding => {
return function applyBindingScopeAndTag(binding) {
if (scopeAndTags.scope) {
binding.inScope(scopeAndTags.scope);
}
Expand Down Expand Up @@ -140,10 +144,11 @@ export function bindingTemplateFor<T = unknown>(
cls: Constructor<T | Provider<T>>,
): BindingTemplate<T> {
const spec = getBindingMetadata(cls);
debug('class %s has binding metadata', cls.name, spec);
const templateFunctions = (spec && spec.templates) || [
asClassOrProvider(cls),
];
return binding => {
return function applyBindingTemplatesFromMetadata(binding) {
for (const t of templateFunctions) {
binding.apply(t);
}
Expand Down Expand Up @@ -215,12 +220,23 @@ export function createBindingFromClass<T = unknown>(
cls: Constructor<T | Provider<T>>,
options: BindingFromClassOptions = {},
): Binding<T> {
const templateFn = bindingTemplateFor(cls);
let key = options.key;
if (!key) {
key = buildBindingKey(cls, options);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to check options.key here, it's already handled by buildBindingKey.

debug('create binding from class %s with options', cls.name, options);
try {
const templateFn = bindingTemplateFor(cls);
const key = buildBindingKey(cls, options);
const binding = Binding.bind<T>(key).apply(templateFn);
applyClassBindingOptions(binding, options);
return binding;
} catch (err) {
err.message += ` (while building binding for class ${cls.name})`;
throw err;
}
const binding = Binding.bind<T>(key).apply(templateFn);
}

function applyClassBindingOptions<T>(
binding: Binding<T>,
options: BindingFromClassOptions,
) {
if (options.name) {
binding.tag({name: options.name});
}
Expand All @@ -230,7 +246,6 @@ export function createBindingFromClass<T = unknown>(
if (options.defaultScope) {
binding.applyDefaultScope(options.defaultScope);
}
return binding;
}

/**
Expand Down Expand Up @@ -272,11 +287,13 @@ function buildBindingKey(
cls: Constructor<unknown>,
options: BindingFromClassOptions = {},
) {
if (options.key) return options.key;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small optimization - we don't need to call buildingTemplateFor and create a new Binding when options.key is set. Return as early as possible in that case.


const templateFn = bindingTemplateFor(cls);
// Create a temporary binding
const bindingTemplate = new Binding('template').apply(templateFn);
// Is there a `key` tag?
let key: string = options.key || bindingTemplate.tagMap[ContextTags.KEY];
let key: string = bindingTemplate.tagMap[ContextTags.KEY];
if (key) return key;

let namespace =
Expand Down