Skip to content

Commit

Permalink
fixup! introduce ControllerSpec
Browse files Browse the repository at this point in the history
  • Loading branch information
bajtos committed Aug 1, 2017
1 parent 3af38f1 commit 6ea00e4
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 96 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/http-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
BoundValue,
Constructor,
} from '@loopback/context';
import {OpenApiSpec, PathsObject} from '@loopback/openapi-spec';
import {PathsObject} from '@loopback/openapi-spec';
import {ServerRequest, ServerResponse} from 'http';
import {getApiSpec} from './router/metadata';
import {getApiSpec, ControllerSpec} from './router/metadata';

import {SequenceHandler} from './sequence';
import {
Expand Down Expand Up @@ -43,7 +43,7 @@ export class HttpHandler {
this.handleRequest = (req, res) => this._handleRequest(req, res);
}

registerController(name: ControllerClass, spec: OpenApiSpec) {
registerController(name: ControllerClass, spec: ControllerSpec) {
this._routes.registerController(name, spec);
}

Expand Down
32 changes: 23 additions & 9 deletions packages/core/src/router/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,43 @@
import * as assert from 'assert';
import {Reflector} from '@loopback/context';
import {
OpenApiSpec,
OperationObject,
ParameterLocation,
ParameterObject,
SchemaObject,
ParameterTypeValue,
createEmptyApiSpec,
PathsObject,
} from '@loopback/openapi-spec';

const debug = require('debug')('loopback:core:router:metadata');

// tslint:disable:no-any

export interface ControllerSpec {
/**
* The base path on which the Controller API is served.
* If it is not included, the API is served directly under the host.
* The value MUST start with a leading slash (/).
*/
basePath?: string;

/**
* The available paths and operations for the API.
*/
paths: PathsObject;
}

/**
* Decorate the given Controller constructor with metadata describing
* the HTTP/REST API the Controller implements/provides.
*
* @param {OpenApiSpec} spec OpenAPI specification describing the endpoints
* @param spec OpenAPI specification describing the endpoints
* handled by this controller
*
* @decorator
*/
export function api(spec: OpenApiSpec) {
export function api(spec: ControllerSpec) {
return function(constructor: Function) {
assert(
typeof constructor === 'function',
Expand All @@ -43,10 +57,10 @@ interface RestEndpoint {
path: string;
}

export function getApiSpec(constructor: Function): OpenApiSpec {
export function getApiSpec(constructor: Function): ControllerSpec {
debug(`Retrieving OpenAPI specification for controller ${constructor.name}`);

let spec: OpenApiSpec = Reflector.getMetadata(
let spec: ControllerSpec = Reflector.getMetadata(
'loopback:api-spec',
constructor,
);
Expand All @@ -56,7 +70,7 @@ export function getApiSpec(constructor: Function): OpenApiSpec {
return spec;
}

spec = createEmptyApiSpec();
spec = {paths: {}};
for (
let proto = constructor.prototype;
proto && proto !== Object.prototype;
Expand All @@ -67,7 +81,7 @@ export function getApiSpec(constructor: Function): OpenApiSpec {
return spec;
}

function addPrototypeMethodsToSpec(spec: OpenApiSpec, proto: any) {
function addPrototypeMethodsToSpec(spec: ControllerSpec, proto: any) {
const controllerMethods = Object.getOwnPropertyNames(proto).filter(
key => key !== 'constructor' && typeof proto[key] === 'function',
);
Expand All @@ -77,7 +91,7 @@ function addPrototypeMethodsToSpec(spec: OpenApiSpec, proto: any) {
}

function addControllerMethodToSpec(
spec: OpenApiSpec,
spec: ControllerSpec,
proto: any,
methodName: string,
) {
Expand Down Expand Up @@ -182,7 +196,7 @@ export function patch(path: string, spec?: OperationObject) {
* of this operation.
*/
export function del(path: string, spec?: OperationObject) {
return operation('del', path, spec);
return operation('delete', path, spec);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/router/routing-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// License text available at https://opensource.org/licenses/MIT

import {
OpenApiSpec,
OperationObject,
ParameterObject,
PathsObject,
Expand All @@ -20,6 +19,8 @@ import {
OperationRetval,
} from '../internal-types';

import {ControllerSpec} from './metadata';

import * as assert from 'assert';
import * as url from 'url';
const debug = require('debug')('loopback:core:routing-table');
Expand Down Expand Up @@ -54,7 +55,7 @@ export type ControllerClass = Constructor<any>;
export class RoutingTable {
private readonly _routes: RouteEntry[] = [];

registerController(controller: ControllerClass, spec: OpenApiSpec) {
registerController(controller: ControllerClass, spec: ControllerSpec) {
assert(
typeof spec === 'object' && !!spec,
'API specification must be a non-null object',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Application,
api,
get,
OpenApiSpec,
ParameterObject,
OperationObject,
ServerRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import {
Application,
api,
OpenApiSpec,
ParameterObject,
ServerRequest,
ServerResponse,
Expand Down
1 change: 0 additions & 1 deletion packages/core/test/integration/application.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {expect, createClientForApp} from '@loopback/testlab';
import {
Application,
Route,
OpenApiSpec,
ResponseObject,
SchemaObject,
ReferenceObject,
Expand Down
5 changes: 3 additions & 2 deletions packages/core/test/integration/http-handler.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
ServerRequest,
writeResultToResponse,
RejectProvider,
ControllerSpec,
} from '../..';
import {Context} from '@loopback/context';
import {expect, Client, createClientForHandler} from '@loopback/testlab';
import {OpenApiSpec, ParameterObject} from '@loopback/openapi-spec';
import {ParameterObject} from '@loopback/openapi-spec';
import {anOpenApiSpec} from '@loopback/openapi-spec-builder';

describe('HttpHandler', () => {
Expand Down Expand Up @@ -403,7 +404,7 @@ describe('HttpHandler', () => {
function givenControllerClass(
// tslint:disable-next-line:no-any
ctor: new (...args: any[]) => Object,
spec: OpenApiSpec,
spec: ControllerSpec,
) {
handler.registerController(ctor, spec);
}
Expand Down
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 {expect} from '@loopback/testlab';
import {Application, api, OpenApiSpec, get, Route} from '../../..';
import {Application, api, get, Route} from '../../..';
import {anOpenApiSpec} from '@loopback/openapi-spec-builder';

describe('Application.getApiSpec()', () => {
Expand Down
138 changes: 76 additions & 62 deletions packages/core/test/unit/router/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'get',
'/greet',
Object.assign({'x-operation-name': 'greet'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greet': {
get: {
'x-operation-name': 'greet',
...operationSpec,
},
},
},
});
});

it('returns spec defined via @post decorator', () => {
Expand All @@ -67,14 +69,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'post',
'/greeting',
Object.assign({'x-operation-name': 'createGreeting'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greeting': {
post: {
'x-operation-name': 'createGreeting',
...operationSpec,
},
},
},
});
});

it('returns spec defined via @put decorator', () => {
Expand All @@ -87,14 +91,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'put',
'/greeting',
Object.assign({'x-operation-name': 'updateGreeting'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greeting': {
put: {
'x-operation-name': 'updateGreeting',
...operationSpec,
},
},
},
});
});

it('returns spec defined via @patch decorator', () => {
Expand All @@ -107,14 +113,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'patch',
'/greeting',
Object.assign({'x-operation-name': 'patchGreeting'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greeting': {
patch: {
'x-operation-name': 'patchGreeting',
...operationSpec,
},
},
},
});
});

it('returns spec defined via @del decorator', () => {
Expand All @@ -127,14 +135,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'del',
'/greeting',
Object.assign({'x-operation-name': 'deleteGreeting'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greeting': {
delete: {
'x-operation-name': 'deleteGreeting',
...operationSpec,
},
},
},
});
});

it('returns spec defined via @operation decorator', () => {
Expand All @@ -147,14 +157,16 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(MyController);

const expectedSpec = anOpenApiSpec()
.withOperation(
'post',
'/greeting',
Object.assign({'x-operation-name': 'createGreeting'}, operationSpec),
)
.build();
expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/greeting': {
post: {
'x-operation-name': 'createGreeting',
...operationSpec,
},
},
},
});
});

it('returns default spec for @get with no spec', () => {
Expand Down Expand Up @@ -204,20 +216,22 @@ describe('Routing metadata', () => {

const actualSpec = getApiSpec(Child);

const expectedSpec = anOpenApiSpec()
.withOperation(
'get',
'/parent',
Object.assign({'x-operation-name': 'getParentName'}, operationSpec),
)
.withOperation(
'get',
'/child',
Object.assign({'x-operation-name': 'getChildName'}, operationSpec),
)
.build();

expect(actualSpec).to.eql(expectedSpec);
expect(actualSpec).to.eql({
paths: {
'/parent': {
get: {
'x-operation-name': 'getParentName',
...operationSpec,
},
},
'/child': {
get: {
'x-operation-name': 'getChildName',
...operationSpec,
},
},
},
});
});

it('allows children to override parent REST endpoints', () => {
Expand Down

0 comments on commit 6ea00e4

Please sign in to comment.