Skip to content

Commit

Permalink
feat(rest): use method DI for actions
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Jun 11, 2019
1 parent e8c7ba6 commit 94214fa
Show file tree
Hide file tree
Showing 12 changed files with 59 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
Provider,
ValueOrPromise,
} from '../..';
import {invokeMethodWithoutInterceptors} from '../../invocation';

describe('Interceptor', () => {
let ctx: Context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('MiddlewareAction', () => {
httpCtx.request,
httpCtx.response,
ctx,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{} as any,
);
}
Expand Down
19 changes: 18 additions & 1 deletion packages/rest/src/actions/base-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Next} from '@loopback/context';
import {InvocationArgs, invokeMethod, Next} from '@loopback/context';
import {HttpContext, RestAction} from '../types';

export class BaseRestAction implements RestAction {
async run(ctx: HttpContext, next: Next) {
return await next();
}

/**
* Delegate the call to an instance method
* @param ctx - Http context
* @param methodName - Method to handle the processing with dependency
* injection
* @param nonInjectedArgs Non-injected arguments
*/
protected async delegate(
ctx: HttpContext,
methodName: string,
nonInjectedArgs?: InvocationArgs,
) {
return await invokeMethod(this, methodName, ctx, nonInjectedArgs, {
skipInterceptors: true,
});
}
}
11 changes: 5 additions & 6 deletions packages/rest/src/actions/find-route.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ import {BaseRestAction} from './base-action';
@restAction('route')
export class FindRouteAction extends BaseRestAction {
constructor(
@inject(RestBindings.Http.CONTEXT)
private context: Context,
@inject(RestBindings.Http.CONTEXT) protected context: Context,
@inject(RestBindings.HANDLER) protected httpHandler: HttpHandler,
) {
super();
}

run(ctx: HttpContext, next: Next) {
const found = this.findRoute(ctx.request);
async run(ctx: HttpContext, next: Next) {
const found = await this.delegate(ctx, 'findRoute');
// Bind resolved route
ctx.bind(RestBindings.RESOLVED_ROUTE).to(found);
return next();
return await next();
}

findRoute(request: Request) {
findRoute(@inject(RestBindings.Http.REQUEST) request: Request) {
const found = this.httpHandler.findRoute(request);
found.updateBindings(this.context);
return found;
Expand Down
18 changes: 7 additions & 11 deletions packages/rest/src/actions/invoke-method.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Context, Getter, inject, Next} from '@loopback/context';
import {Context, inject} from '@loopback/context';
import {RestBindings} from '../keys';
import {RouteEntry} from '../router';
import {HttpContext, InvokeMethod, OperationArgs, restAction} from '../types';
Expand All @@ -14,24 +14,20 @@ export class InvokeMethodAction extends BaseRestAction {
constructor(
@inject(RestBindings.Http.CONTEXT)
private context: Context,
@inject.getter(RestBindings.RESOLVED_ROUTE)
protected getRoute: Getter<RouteEntry>,
@inject.getter(RestBindings.OPERATION_ARGS)
protected getArgs: Getter<OperationArgs>,
) {
super();
}

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

async invokeMethod(route: RouteEntry, args: OperationArgs) {
async invokeMethod(
@inject(RestBindings.RESOLVED_ROUTE) route: RouteEntry,
@inject(RestBindings.OPERATION_ARGS) args: OperationArgs,
) {
return await route.invokeHandler(this.context, args);
}

Expand Down
1 change: 1 addition & 0 deletions packages/rest/src/actions/middleware-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class MiddlewareAction extends BaseRestAction {
resolve(next());
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.requestHandler(ctx.request, ctx.response, (err?: any) => {
if (err) reject(err);
else resolve(next());
Expand Down
16 changes: 5 additions & 11 deletions packages/rest/src/actions/parse-params.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Getter, inject, Next} from '@loopback/context';
import {inject, Next} from '@loopback/context';
import {RequestBodyParser} from '../body-parsers';
import {RestBindings} from '../keys';
import {parseOperationArgs} from '../parser';
Expand All @@ -12,7 +12,6 @@ import {
HttpContext,
ParseParams,
Request,
RequestBodyParserOptions,
RequestBodyValidationOptions,
restAction,
} from '../types';
Expand All @@ -26,29 +25,24 @@ import {BaseRestAction} from './base-action';
@restAction('parseParams')
export class ParseParamsAction extends BaseRestAction {
constructor(
@inject.getter(RestBindings.RESOLVED_ROUTE)
private getRoute: Getter<ResolvedRoute>,
@inject(RestBindings.REQUEST_BODY_PARSER)
private requestBodyParser: RequestBodyParser,
@inject(RestBindings.REQUEST_BODY_PARSER_OPTIONS, {optional: true})
private options: RequestBodyParserOptions = {},
) {
super();
}

async run(ctx: HttpContext, next: Next) {
const args = await this.parseParams(
ctx.request,
await this.getRoute(),
this.options.validation,
);
const args = await this.delegate(ctx, 'parseParams');
ctx.bind(RestBindings.OPERATION_ARGS).to(args);
return await next();
}

async parseParams(
@inject(RestBindings.Http.REQUEST)
request: Request,
@inject(RestBindings.RESOLVED_ROUTE)
resolvedRoute: ResolvedRoute,
@inject(RestBindings.REQUEST_BODY_VALIDATION_OPTIONS, {optional: true})
options?: RequestBodyValidationOptions,
) {
return await parseOperationArgs(
Expand Down
7 changes: 5 additions & 2 deletions packages/rest/src/actions/reject.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ export class RejectAction extends BaseRestAction {
try {
return await next();
} catch (error) {
this.reject(ctx, error);
await this.delegate(ctx, 'reject', [error]);
}
}

reject({request, response}: HandlerContext, error: Error) {
reject(
@inject(RestBindings.Http.CONTEXT) {request, response}: HandlerContext,
error: Error,
) {
const err = error as HttpError;
if (!err.status && !err.statusCode && err.code) {
const customStatus = codeToStatusCodeMap[err.code];
Expand Down
18 changes: 9 additions & 9 deletions packages/rest/src/actions/send.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Getter, inject, Next} from '@loopback/context';
import {inject, Next} from '@loopback/context';
import {RestBindings} from '../keys';
import {
HttpContext,
Expand All @@ -24,20 +24,20 @@ import {BaseRestAction} from './base-action';
*/
@restAction('send')
export class SendAction extends BaseRestAction {
constructor(
@inject.getter(RestBindings.OPERATION_RESULT, {optional: true})
private getReturnValue: Getter<OperationRetval>,
) {
constructor() {
super();
}

async run(ctx: HttpContext, next: Next) {
const result = await next();
const returnVal = await this.getReturnValue();
this.send(ctx.response, returnVal || result);
await next();
return this.delegate(ctx, 'send');
}

send(response: Response, result: OperationRetval) {
send(
@inject(RestBindings.Http.RESPONSE) response: Response,
@inject(RestBindings.OPERATION_RESULT, {optional: true})
result: OperationRetval,
) {
writeResultToResponse(response, result);
}

Expand Down
8 changes: 8 additions & 0 deletions packages/rest/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Reject,
Request,
RequestBodyParserOptions,
RequestBodyValidationOptions,
Response,
RestAction,
Send,
Expand Down Expand Up @@ -109,6 +110,13 @@ export namespace RestBindings {
RequestBodyParserOptions
>('rest.requestBodyParserOptions');

/**
* Binding key for request body validation options
*/
export const REQUEST_BODY_VALIDATION_OPTIONS = BindingKey.create<
RequestBodyValidationOptions
>('rest.requestBodyParserOptions#validation');

/**
* Binding key for request body parser
*/
Expand Down
2 changes: 0 additions & 2 deletions packages/rest/src/rest.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ export interface HttpServerLike {
requestHandler: HttpRequestListener;
}

const SequenceActions = RestBindings.SequenceActions;

// NOTE(bajtos) we cannot use `import * as cloneDeep from 'lodash/cloneDeep'
// because it produces the following TypeScript error:
// Module '"(...)/node_modules/@types/lodash/cloneDeep/index"' resolves to
Expand Down
2 changes: 0 additions & 2 deletions packages/rest/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ export interface RequestBodyValidationOptions extends ajv.Options {
>;
}

/* eslint-disable @typescript-eslint/no-explicit-any */

/**
* Options for request body parsing
* See https://github.com/expressjs/body-parser/#options
Expand Down

0 comments on commit 94214fa

Please # to comment.