Skip to content

Commit

Permalink
feat(rest): allow both styles of sequence of actions
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed May 31, 2019
1 parent f596297 commit 7e9bd28
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
SendAction,
SequenceHandler,
} from '../../..';
import {RestAction} from '../../../types';
import {FindRoute, InvokeMethod, ParseParams, RestAction} from '../../../types';

const SequenceActions = RestBindings.SequenceActions;

Expand Down Expand Up @@ -77,20 +77,20 @@ describe('Sequence', () => {
class MySequence implements SequenceHandler {
constructor(
@inject(SequenceActions.FIND_ROUTE)
protected findRouteAction: FindRouteAction,
protected findRoute: FindRoute,
@inject(SequenceActions.PARSE_PARAMS)
protected parseParamsAction: ParseParamsAction,
protected parseParams: ParseParams,
@inject(SequenceActions.INVOKE_METHOD)
protected invokeMethodAction: InvokeMethodAction,
@inject(SequenceActions.SEND) protected sendAction: SendAction,
protected invokeMethod: InvokeMethod,
@inject(SequenceActions.SEND) protected send: Send,
) {}

async handle(context: RequestContext) {
const {request, response} = context;
const route = this.findRouteAction.findRoute(request);
const args = await this.parseParamsAction.parseParams(request, route);
const result = await this.invokeMethodAction.invokeMethod(route, args);
this.sendAction.send(response, `MySequence ${result}`);
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
const result = await this.invokeMethod(route, args);
this.send(response, `MySequence ${result}`);
}
}

Expand All @@ -103,12 +103,10 @@ describe('Sequence', () => {

it('allows users to bind a custom sequence class via app.sequence()', async () => {
class MySequence implements SequenceHandler {
constructor(
@inject(SequenceActions.SEND) protected sendAction: SendAction,
) {}
constructor(@inject(SequenceActions.SEND) protected send: Send) {}

async handle({response}: RequestContext) {
this.sendAction.send(response, 'MySequence was invoked.');
this.send(response, 'MySequence was invoked.');
}
}

Expand All @@ -127,7 +125,7 @@ describe('Sequence', () => {
response.end(`CUSTOM FORMAT: ${result}`);
}
}
server.bind(SequenceActions.SEND).toClass(MySendAction);
server.bind(SequenceActions.SEND_ACTION).toClass(MySendAction);

return whenIRequest()
.get('/name')
Expand All @@ -141,7 +139,7 @@ describe('Sequence', () => {
response.end();
}
}
server.bind(SequenceActions.REJECT).toClass(MyRejectAction);
server.bind(SequenceActions.REJECT_ACTION).toClass(MyRejectAction);

return whenIRequest()
.get('/unknown-url')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,27 +626,36 @@ describe('HttpHandler', () => {

async function givenHandler() {
rootContext = new Context();
rootContext
.bind(SequenceActions.LOG_ERROR)
.to(createUnexpectedHttpErrorLogger());

/**
* Use dynamic import to avoid circular Node.js require
*/
const actions = await import('../../actions');

bindAction(
rootContext,
SequenceActions.FIND_ROUTE,
SequenceActions.FIND_ROUTE_ACTION,
actions.FindRouteAction,
);
bindAction(
rootContext,
SequenceActions.PARSE_PARAMS,
SequenceActions.PARSE_PARAMS_ACTION,
actions.ParseParamsAction,
);
bindAction(
rootContext,
SequenceActions.INVOKE_METHOD,
SequenceActions.INVOKE_METHOD_ACTION,
actions.InvokeMethodAction,
);
rootContext
.bind(SequenceActions.LOG_ERROR)
.to(createUnexpectedHttpErrorLogger());
bindAction(rootContext, SequenceActions.SEND, actions.SendAction);
bindAction(rootContext, SequenceActions.REJECT, actions.RejectAction);
bindAction(rootContext, SequenceActions.SEND_ACTION, actions.SendAction);
bindAction(
rootContext,
SequenceActions.REJECT_ACTION,
actions.RejectAction,
);

rootContext.bind(RestBindings.SEQUENCE).toClass(DefaultSequence);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
RestServerConfig,
Route,
} from '../../..';
import {InvokeMethodAction} from '../../../actions';

describe('RestServer', () => {
describe('"invokeMethod" binding', () => {
Expand All @@ -32,11 +31,11 @@ describe('RestServer', () => {
);

const ctx = await givenRequestContext();
const invokeMethodAction = (await ctx.get(
const invokeMethod = await ctx.get(
RestBindings.SequenceActions.INVOKE_METHOD,
)) as InvokeMethodAction;
);

const result = await invokeMethodAction.invokeMethod(route, []);
const result = await invokeMethod(route, []);
expect(result).to.equal('Hello world');
});
});
Expand Down
30 changes: 21 additions & 9 deletions packages/rest/src/actions/find-route.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,43 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Binding, Context, inject, Next} from '@loopback/context';
import {Context, inject, Next} from '@loopback/context';
import {HttpHandler} from '../http-handler';
import {RestBindings} from '../keys';
import {RouteEntry} from '../router';
import {HandlerContext, Request, RestAction, restAction} from '../types';
import {
FindRoute,
HttpContext,
Request,
RestAction,
restAction,
} from '../types';

/**
* Find a route matching the incoming REST API request.
* Throw an error when no route was found.
*/
@restAction('route')
export class FindRouteAction implements RestAction {
constructor(
@inject.context()
private context: Context,
@inject.binding(RestBindings.RESOLVED_ROUTE)
protected resolvedRouteBinding: Binding<RouteEntry>,
@inject(RestBindings.HANDLER) protected handler: HttpHandler,
@inject(RestBindings.HANDLER) protected httpHandler: HttpHandler,
) {}

action(ctx: HandlerContext, next: Next) {
action(ctx: HttpContext, next: Next) {
const found = this.findRoute(ctx.request);
this.resolvedRouteBinding.to(found);
// Bind resolved route
ctx.bind(RestBindings.RESOLVED_ROUTE).to(found);
return next();
}

findRoute(request: Request) {
const found = this.handler.findRoute(request);
const found = this.httpHandler.findRoute(request);
found.updateBindings(this.context);
return found;
}

get handler(): FindRoute {
return this.findRoute.bind(this);
}
}
16 changes: 9 additions & 7 deletions packages/rest/src/actions/invoke-method.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Binding, Context, Getter, inject, Next} from '@loopback/context';
import {Context, Getter, inject, Next} from '@loopback/context';
import {RestBindings} from '../keys';
import {RouteEntry} from '../router';
import {
HandlerContext,
HttpContext,
InvokeMethod,
OperationArgs,
OperationRetval,
RestAction,
restAction,
} from '../types';
Expand All @@ -23,20 +23,22 @@ export class InvokeMethodAction implements RestAction {
protected getRoute: Getter<RouteEntry>,
@inject.getter(RestBindings.OPERATION_ARGS)
protected getArgs: Getter<OperationArgs>,
@inject.binding(RestBindings.OPERATION_RESULT)
private binding: Binding<OperationRetval>,
) {}

async action(ctx: HandlerContext, next: Next) {
async action(ctx: HttpContext, next: Next) {
const result = await this.invokeMethod(
await this.getRoute(),
await this.getArgs(),
);
this.binding.to(result);
ctx.bind(RestBindings.OPERATION_RESULT).to(result);
return result;
}

async invokeMethod(route: RouteEntry, args: OperationArgs) {
return await route.invokeHandler(this.context, args);
}

get handler(): InvokeMethod {
return this.invokeMethod.bind(this);
}
}
16 changes: 9 additions & 7 deletions packages/rest/src/actions/parse-params.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Binding, Getter, inject, Next} from '@loopback/context';
import {Getter, inject, Next} from '@loopback/context';
import {RequestBodyParser} from '../body-parsers';
import {RestBindings} from '../keys';
import {parseOperationArgs} from '../parser';
import {ResolvedRoute} from '../router';
import {
HandlerContext,
OperationArgs,
HttpContext,
ParseParams,
Request,
RestAction,
restAction,
Expand All @@ -28,13 +28,11 @@ export class ParseParamsAction implements RestAction {
private getRoute: Getter<ResolvedRoute>,
@inject(RestBindings.REQUEST_BODY_PARSER)
private requestBodyParser: RequestBodyParser,
@inject.binding(RestBindings.OPERATION_ARGS)
private binding: Binding<OperationArgs>,
) {}

async action(ctx: HandlerContext, next: Next) {
async action(ctx: HttpContext, next: Next) {
const args = await this.parseParams(ctx.request, await this.getRoute());
this.binding.to(args);
ctx.bind(RestBindings.OPERATION_ARGS).to(args);
return await next();
}

Expand All @@ -45,4 +43,8 @@ export class ParseParamsAction implements RestAction {
this.requestBodyParser,
);
}

get handler(): ParseParams {
return this.parseParams.bind(this);
}
}
15 changes: 13 additions & 2 deletions packages/rest/src/actions/reject.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import {inject, Next} from '@loopback/context';
import {HttpError} from 'http-errors';
import {ErrorWriterOptions, writeErrorToResponse} from 'strong-error-handler';
import {RestBindings} from '../keys';
import {HandlerContext, LogError, RestAction, restAction} from '../types';
import {
HandlerContext,
HttpContext,
LogError,
Reject,
RestAction,
restAction,
} from '../types';

// TODO(bajtos) Make this mapping configurable at RestServer level,
// allow apps and extensions to contribute additional mappings.
Expand All @@ -24,7 +31,7 @@ export class RejectAction implements RestAction {
protected errorWriterOptions?: ErrorWriterOptions,
) {}

async action(ctx: HandlerContext, next: Next) {
async action(ctx: HttpContext, next: Next) {
try {
return await next();
} catch (error) {
Expand All @@ -44,4 +51,8 @@ export class RejectAction implements RestAction {
writeErrorToResponse(err, request, response, this.errorWriterOptions);
this.logError(err, statusCode, request);
}

get handler(): Reject {
return this.reject.bind(this);
}
}
9 changes: 7 additions & 2 deletions packages/rest/src/actions/send.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
import {Getter, inject, Next} from '@loopback/context';
import {RestBindings} from '../keys';
import {
HandlerContext,
HttpContext,
OperationRetval,
Response,
RestAction,
restAction,
Send,
} from '../types';
import {writeResultToResponse} from '../writer';

Expand All @@ -28,7 +29,7 @@ export class SendAction implements RestAction {
private getReturnValue: Getter<OperationRetval>,
) {}

async action(ctx: HandlerContext, next: Next) {
async action(ctx: HttpContext, next: Next) {
const result = await next();
const returnVal = await this.getReturnValue();
this.send(ctx.response, returnVal || result);
Expand All @@ -37,4 +38,8 @@ export class SendAction implements RestAction {
send(response: Response, result: OperationRetval) {
writeResultToResponse(response, result);
}

get handler(): Send {
return this.send.bind(this);
}
}
Loading

0 comments on commit 7e9bd28

Please # to comment.