Skip to content

Commit

Permalink
feat(context): allow @config.* to specify the target binding key
Browse files Browse the repository at this point in the history
Sometimes we want to inject configuration from another binding instead of
the current one.
  • Loading branch information
raymondfeng committed Jul 9, 2019
1 parent 97fcc64 commit f2af6aa
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 9 deletions.
Expand Up @@ -78,6 +78,47 @@ describe('Context bindings - injecting configuration for bound artifacts', () =>
expect(server1.port).to.eql(3000);
});

it('allows configPath for injection metadata', async () => {
class RestServerWithPort {
constructor(@config({configPath: 'port'}) public port: number) {}
}

// Bind configuration
ctx
.configure('servers.rest.server1')
.toDynamicValue(() => Promise.resolve({port: 3000}));

// Bind RestServer
ctx.bind('servers.rest.server1').toClass(RestServerWithPort);

// Resolve an instance of RestServer
// Expect server1.config to be `{port: 3000}
const server1 = await ctx.get<RestServerWithPort>('servers.rest.server1');
expect(server1.port).to.eql(3000);
});

it('allows targetBindingKey for injection metadata', async () => {
class RestServerWithPort {
constructor(
@config({configPath: 'port', targetBindingKey: 'restServer'})
public port: number,
) {}
}

// Bind configuration
ctx
.configure('restServer')
.toDynamicValue(() => Promise.resolve({port: 3000}));

// Bind RestServer
ctx.bind('servers.rest.server1').toClass(RestServerWithPort);

// Resolve an instance of RestServer
// Expect server1.config to be `{port: 3000}
const server1 = await ctx.get<RestServerWithPort>('servers.rest.server1');
expect(server1.port).to.eql(3000);
});

const LOGGER_KEY = 'loggers.Logger';
it('injects a getter function to access config', async () => {
class Logger {
Expand Down
47 changes: 38 additions & 9 deletions packages/context/src/inject-config.ts
Expand Up @@ -4,13 +4,27 @@
// License text available at https://opensource.org/licenses/MIT

import {BindingFilter} from './binding-filter';
import {BindingKey} from './binding-key';
import {BindingAddress, BindingKey} from './binding-key';
import {Context} from './context';
import {ContextView} from './context-view';
import {assertTargetType, inject, Injection, InjectionMetadata} from './inject';
import {ResolutionSession} from './resolution-session';
import {getDeepProperty, ValueOrPromise} from './value-promise';

/**
* Injection metadata for `@config.*`
*/
export interface ConfigInjectionMetadata extends InjectionMetadata {
/**
* Path to retrieve the configuration of the target binding
*/
configPath?: string;
/**
* Target binding key to override the current binding
*/
targetBindingKey?: BindingAddress;
}

/**
* Inject a property from `config` of the current binding. If no corresponding
* config value is present, `undefined` will be injected as the configuration
Expand Down Expand Up @@ -44,8 +58,14 @@ import {getDeepProperty, ValueOrPromise} from './value-promise';
* present, the `config` object will be returned.
* @param metadata - Optional metadata to help the injection
*/
export function config(configPath?: string, metadata?: InjectionMetadata) {
export function config(
configPath?: string | ConfigInjectionMetadata,
metadata?: ConfigInjectionMetadata,
) {
configPath = configPath || '';
if (typeof configPath === 'object') {
metadata = configPath;
}
metadata = Object.assign(
{configPath, decorator: '@config', optional: true},
metadata,
Expand All @@ -60,10 +80,13 @@ export namespace config {
* @param metadata - Injection metadata
*/
export const getter = function injectConfigGetter(
configPath?: string,
metadata?: InjectionMetadata,
configPath?: string | ConfigInjectionMetadata,
metadata?: ConfigInjectionMetadata,
) {
configPath = configPath || '';
if (typeof configPath === 'object') {
metadata = configPath;
}
metadata = Object.assign(
{configPath, decorator: '@config.getter', optional: true},
metadata,
Expand All @@ -78,10 +101,13 @@ export namespace config {
* @param metadata - Injection metadata
*/
export const view = function injectConfigView(
configPath?: string,
metadata?: InjectionMetadata,
configPath?: string | ConfigInjectionMetadata,
metadata?: ConfigInjectionMetadata,
) {
configPath = configPath || '';
if (typeof configPath === 'object') {
metadata = configPath;
}
metadata = Object.assign(
{configPath, decorator: '@config.view', optional: true},
metadata,
Expand Down Expand Up @@ -111,7 +137,8 @@ function resolveFromConfig(
injection: Injection,
session: ResolutionSession,
): ValueOrPromise<unknown> {
const bindingKey = getCurrentBindingKey(session);
const bindingKey =
injection.metadata.targetBindingKey || getCurrentBindingKey(session);
// Return `undefined` if no current binding is present
if (!bindingKey) return undefined;
const meta = injection.metadata;
Expand All @@ -133,7 +160,8 @@ function resolveAsGetterFromConfig(
session: ResolutionSession,
) {
assertTargetType(injection, Function, 'Getter function');
const bindingKey = getCurrentBindingKey(session);
const bindingKey =
injection.metadata.targetBindingKey || getCurrentBindingKey(session);
// We need to clone the session for the getter as it will be resolved later
const forkedSession = ResolutionSession.fork(session);
const meta = injection.metadata;
Expand All @@ -159,7 +187,8 @@ function resolveAsViewFromConfig(
session: ResolutionSession,
) {
assertTargetType(injection, ContextView);
const bindingKey = getCurrentBindingKey(session);
const bindingKey =
injection.metadata.targetBindingKey || getCurrentBindingKey(session);
// Return `undefined` if no current binding is present
if (!bindingKey) return undefined;
const view = new ConfigView(
Expand Down

0 comments on commit f2af6aa

Please sign in to comment.