Skip to content

Commit

Permalink
Bind a meta value provider to a context
Browse files Browse the repository at this point in the history
* Create a MetaValueProvider interface

* Add Binding.toProvider() to bind a MetaValueProvider

* return provider.value() asynchronously thru Binding.getValue()
  • Loading branch information
deepakrkris committed Jun 7, 2017
1 parent 0185602 commit 214cf8f
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 2 deletions.
40 changes: 40 additions & 0 deletions packages/context/src/binding.ts
Expand Up @@ -5,10 +5,14 @@

import {Context} from './context';
import {Constructor, instantiateClass} from './resolver';
import {isPromise} from './isPromise';
import {Provider} from './provider';

// tslint:disable-next-line:no-any
export type BoundValue = any;

export type ValueOrPromise<T> = T | Promise<T>;

// FIXME(bajtos) The binding class should be parameterized by the value type stored
export class Binding {
private _tagName: string;
Expand Down Expand Up @@ -94,6 +98,42 @@ export class Binding {
return this;
}

/**
* Bind the key to a BindingProvider
*/
public toProvider<T>(providerClass: Constructor<Provider<T>>): this {
this.getProviderInstance = async (ctx: Context) => {
const providerOrPromise: ValueOrPromise<Provider<T>> = instantiateClass<Provider<T>>(providerClass, ctx);
return this.resolveClassInstance<Provider<T>>(providerOrPromise);
};
this.getValue = async (ctx): Promise<BoundValue> => {
const providerInstance: Provider<T> = await this.getProviderInstance<T>(ctx);
return providerInstance.value();
};
return this;
}

/**
* get an instance of the provider
*/
public async getProviderInstance<T>(ctx: Context): Promise<Provider<T>> {
return Promise.reject(new Error(`No provider is attached to binding ${this._key}.`));
}

/**
* get an instance of the provider
*/
public async resolveClassInstance<T>(instanceOrPromise: ValueOrPromise<T>): Promise<ValueOrPromise<T>> {
if (isPromise(instanceOrPromise)) {
const providerPromise = instanceOrPromise as Promise<T>;
instanceOrPromise = await instanceOrPromise;
return instanceOrPromise;
} else {
instanceOrPromise = instanceOrPromise as T;
return instanceOrPromise;
}
}

/**
* Bind the key to an instance of the given class.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/context/src/index.ts
Expand Up @@ -8,10 +8,10 @@ export {Context} from './context';
export {Constructor} from './resolver';
export {inject} from './inject';
export {NamespacedReflect} from './reflect';
export {Provider} from './provider';
export const isPromise = require('is-promise');

// internals for testing
export {instantiateClass} from './resolver';
export {describeInjectedArguments, describeInjectedProperties} from './inject';
export {Reflector} from './reflect';

35 changes: 35 additions & 0 deletions packages/context/src/provider.ts
@@ -0,0 +1,35 @@
// Copyright IBM Corp. 2013,2017. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {ValueOrPromise} from './binding';

/**
* @exports Provider<T> : interface definition for a provider of a value of type T
* @summary Providers allow binding of a value provider class instead of the value itself
* @example:
* ```ts
* export class DateProvider implements Provider<Date> {
* constructor(@inject('stringDate') private param: String){}
* value(): Date {
* return new Date(param);
* }
* }
* ```
* @example: Binding a context
* ```ts
* ctx.bind('provider_key').toProvider(MyProvider);
* ```
* @example: getting a value dynamically
* ```ts
* ctx.get('provider_key');
* ctx.getBinding('provider_key').getValue();
* ```
*/
export interface Provider<T> {
/**
* @returns a value or a promise
*/
value(): ValueOrPromise<T>;
}
57 changes: 56 additions & 1 deletion packages/context/test/unit/binding.ts
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Binding, Context} from '../..';
import {Binding, Context, inject, Provider} from '../..';

const key = 'foo';

Expand Down Expand Up @@ -38,8 +38,63 @@ describe('Binding', () => {
});
});

describe('resolveClassInstance(promiseOfInstance)', () => {
it('resolves both a promise and an instance', async () => {
const provider: MyProvider = new MyProvider('hello');
const promise = new Promise<MyProvider>((resolve) => {
resolve(provider);
});
const result1 = await binding.resolveClassInstance<MyProvider>(promise);
const result2 = await binding.resolveClassInstance<MyProvider>(provider);
expect(result1).to.be.equal(result2);
});
});

describe('getProviderInstance(ctx)', () => {
it('returns error when no provider is attached', async () => {
let err;
try {
await binding.getProviderInstance<String>(ctx);
} catch (exception) {
err = exception;
expect(exception.message).to.equal('No provider is attached to binding foo.');
}
expect(err).to.not.to.be.undefined();
});
});

describe('toProvider(provider)', () => {
it('attaches a provider class to the binding', async () => {
ctx.bind('msg').to('hello');
binding.toProvider(MyProvider);
const providerInstance: Provider<String> = await binding.getProviderInstance<String>(ctx);
expect(providerInstance).to.be.instanceOf(MyProvider);
});

it('provider instance is injected with constructor arguments', async () => {
ctx.bind('msg').to('hello');
binding.toProvider(MyProvider);
const providerInstance: Provider<String> = await binding.getProviderInstance<String>(ctx);
expect(providerInstance.value()).to.equal('hello world');
});

it('binding returns the expected value', async () => {
ctx.bind('msg').to('hello');
ctx.bind('provider_key').toProvider(MyProvider);
const value: String = await ctx.get('provider_key');
expect(value).to.equal('hello world');
});
});

function givenBinding() {
ctx = new Context();
binding = new Binding(key);
}

class MyProvider implements Provider<String> {
constructor(@inject('msg') private _msg: string) {}
value(): String {
return this._msg + ' world';
}
}
});
30 changes: 30 additions & 0 deletions packages/context/test/unit/provider.ts
@@ -0,0 +1,30 @@
// Copyright IBM Corp. 2013,2017. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Provider, Context, Binding} from '../../src';

describe('Provider', () => {
let provider: Provider<String>;

beforeEach(givenProvider);

describe('value()', () => {
it('returns the value of the binding', () => {
expect(provider.value()).to.equal('hello world');
});
});

function givenProvider() {
provider = new MyProvider('hello');
}
});

class MyProvider implements Provider<String> {
constructor(private _msg: string) {}
value(): String {
return this._msg + ' world';
}
}

0 comments on commit 214cf8f

Please sign in to comment.