Skip to content

Commit

Permalink
Merge add5a3b into 85c3c5a
Browse files Browse the repository at this point in the history
  • Loading branch information
bajtos committed Jun 26, 2017
2 parents 85c3c5a + add5a3b commit 62b9ff8
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 22 deletions.
37 changes: 30 additions & 7 deletions packages/core/src/application.ts
Expand Up @@ -3,9 +3,10 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Binding, Context, Constructor} from '@loopback/context';
import {Component, OpenApiSpec} from '.';
import {Binding, Context, Constructor, Provider} from '@loopback/context';
import {OpenApiSpec} from '.';
import {ServerRequest, ServerResponse} from 'http';
import {Component, mountComponent} from './component';
import {getApiSpec} from './router/metadata';
import {HttpHandler} from './http-handler';
import {Sequence} from './sequence';
Expand Down Expand Up @@ -39,11 +40,7 @@ export class Application extends Context {

if (options && options.components) {
for (const component of options.components) {
// TODO(superkhau): Need to figure a way around this hack,
// `componentClassName.constructor.name` + `componentClassName.name`
// doesn't work
const componentClassName = component.toString().split(' ')[1];
this.bind(`component.${componentClassName}`).toClass(component);
this.component(component);
}
}

Expand Down Expand Up @@ -124,6 +121,32 @@ export class Application extends Context {
err.stack || err,
);
}

/**
* Add a component to this application.
*
* @param component The component to add.
*
* ```ts
*
* export class ProductComponent {
* controllers = [ProductController];
* repositories = [ProductRepo, UserRepo];
* providers = {
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
* [AUTHORIZATION_ROLE]: Role,
* };
* };
*
* app.component(ProductComponent);
* ```
*/
public component(component: Constructor<Component>) {
const componentKey = `components.${component.name}`;
this.bind(componentKey).toClass(component);
const instance = this.getSync(componentKey);
mountComponent(this, instance);
}
}

export interface ApplicationOptions {
Expand Down
29 changes: 28 additions & 1 deletion packages/core/src/component.ts
Expand Up @@ -3,4 +3,31 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export interface Component {}
import {Constructor, Provider} from '@loopback/context';
import {Application} from '.';

// tslint:disable:no-any

export interface Component {
controllers?: Constructor<any>[];
providers?: {
[key: string]: Constructor<Provider<any>>;
};
}

export function mountComponent(
app: Application,
component: Component,
) {
if (component.controllers) {
for (const controllerCtor of component.controllers) {
app.controller(controllerCtor);
}
}

if (component.providers) {
for (const providerKey in component.providers) {
app.bind(providerKey).toProvider(component.providers[providerKey]);
}
}
}
Expand Up @@ -4,12 +4,12 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Constructor, Provider, inject} from '@loopback/context';
import {Application, Sequence} from '../../..';

describe('Bootstrapping the application', () => {
let app: Application;

context('with a user-defined sequence', () => {
let app: Application;
before(givenAppWithUserDefinedSequence);

it('binds the `sequence` key to the user-defined sequence', async () => {
Expand All @@ -26,20 +26,88 @@ describe('Bootstrapping the application', () => {
});

context('with user-defined components', () => {
before(givenAppWithUserDefinedComponents);

it('binds all user-defined components to the application context', () => {
expect(app.find('component.*')).to.be.instanceOf(Array).with.lengthOf(4);
class AuditComponent {}

const app = new Application({components: [AuditComponent]});
const componentKeys = app.find('component.*').map(b => b.key);
expect(componentKeys).to.containEql('components.AuditComponent');

const componentInstance = app.getSync('components.AuditComponent');
expect(componentInstance).to.be.instanceOf(AuditComponent);
});

function givenAppWithUserDefinedComponents() {
class Todo {}
class Authentication {}
class Authorization {}
class Rejection {}
app = new Application({
components: [Todo, Authentication, Authorization, Rejection],
it('registers all providers from components', () => {
class FooProvider {
value() { return 'bar'; }
}

class FooComponent {
providers = {foo: FooProvider};
}

const app = new Application({components: [FooComponent]});

const value = app.getSync('foo');
expect(value).to.equal('bar');
});

it('registers all controllers from components', () => {
// TODO(bajtos) Beef up this test. Create a real controller with
// a public API endpoint and verify that this endpoint can be invoked
// via HTTP/REST API.

class MyController {
}

class MyComponent {
controllers = [MyController];
}

const app = new Application({components: [MyComponent]});

expect(app.find('controllers.*').map(b => b.key))
.to.eql(['controllers.MyController']);
});

it('injects component dependencies', () => {
class ConfigComponent {
providers = {
greetBriefly: class HelloProvider {
value() { return true; }
},
};
}

class BriefGreetingProvider {
value() { return 'Hi!'; }
}

class LongGreetingProvider {
value() { return 'Hello!'; }
}

class GreetingComponent {
providers: {
greeting: Constructor<Provider<string>>;
};

constructor(
@inject('greetBriefly') greetBriefly: boolean,
) {
this.providers = {
greeting: greetBriefly ?
BriefGreetingProvider :
LongGreetingProvider,
};
}
}

const app = new Application({
components: [ConfigComponent, GreetingComponent],
});
}

expect(app.getSync('greeting')).to.equal('Hi!');
});
});
});
Expand Up @@ -23,6 +23,5 @@ const app = new Application({
});

// get metadata about the registered components
console.log(app.find('component.*')); // [Bindings] should match the 4 components registered above
console.log(app.find('sequence.*')); // [Bindings] should match the 1 sequence registered above
```

0 comments on commit 62b9ff8

Please sign in to comment.