Skip to content

Commit

Permalink
feat(express): updated ExpressProvider.ts to make it much easier to e…
Browse files Browse the repository at this point in the history
…xtend.
  • Loading branch information
aahoogendoorn committed Mar 15, 2021
1 parent bf33938 commit 4bc9fc0
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 23 deletions.
56 changes: 36 additions & 20 deletions src/express/ExpressProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import express, { Express, NextFunction, Request, RequestHandler, Response } fro
import { checkScope, checkToken, checkUseCase } from './SecurityHandler';
import { choose } from '../utils';
import { isDefined, toList } from '../types';
import { ContentType, HttpStatus, rest, RestResult, toOriginatedError } from '../http';
import { HttpStatus, rest, RestResult, toOriginatedError } from '../http';
import { AppProvider, Endpoint, Handler, Resource, Route, routes, Service, toReq, VerbOptions } from '../resources';

export type ExpressVerb = 'get' | 'post' | 'put' | 'patch' | 'delete';
Expand All @@ -12,34 +12,29 @@ const toBody = (status: HttpStatus, outcome?: unknown): RestResult =>
.case(() => HttpStatus.NoContent.equals(status), undefined)
.case(
o => !isDefined(o),
() => rest.toData(status, [])
() => rest.toData(status, []),
)
.else(o => rest.toData(status, toList(o)));

const toResponse = (res: Response, result?: unknown, options?: VerbOptions) => {
res.status(options.onOk.status).type(options.type.id.toString());

choose<void, ContentType>(options.type)
.case(
ct => ContentType.Json.equals(ct),
() => res.json(toBody(options.onOk, result))
)
.case(
ct => ContentType.Stream.equals(ct),
() => res.end(result)
);
};
// const toResponse = (res: Response, result?: unknown, options?: VerbOptions) => {
// res.status(options.onOk.status).type(options.type.id.toString());
//
// choose<void, ContentType>(options.type)
// .case(
// ct => ContentType.Json.equals(ct),
// () => res.json(toBody(options.onOk, result))
// )
// .case(
// ct => ContentType.Stream.equals(ct),
// () => res.end(result)
// );
// };

export class ExpressProvider implements AppProvider {
constructor(private app: Express = express()) {
this.app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
}

protected handle = (endpoint: Endpoint, options: VerbOptions): RequestHandler => (req: Request, res: Response, next: NextFunction) =>
endpoint(toReq(req))
.then((r: any) => toResponse(res, r, options))
.catch(error => next(toOriginatedError(error, options)));

use = (handler: Handler): void => {
this.app.use(handler);
};
Expand Down Expand Up @@ -67,6 +62,27 @@ export class ExpressProvider implements AppProvider {
console.log(message);
});
};

protected toResponse = (res: Response, result: unknown, options: VerbOptions): void => {
res.status(options.onOk.status).type(options.type.code);
const f = (this as any)[options.type.name] ?? this.json;
f(res, result, options);
};

protected handle = (endpoint: Endpoint, options: VerbOptions): RequestHandler => (req: Request, res: Response, next: NextFunction) =>
endpoint(toReq(req))
.then((r: any) => this.toResponse(res, r, options))
.catch(error => next(toOriginatedError(error, options)));

// Handling responses depending on content type

protected json(res: Response, result: unknown, options: VerbOptions): void {
res.json(toBody(options.onOk, result));
}

protected stream(res: Response, result: unknown): void {
res.end(result);
}
}

export const service = (name: string): Service => new Service(name, new ExpressProvider());
4 changes: 2 additions & 2 deletions test/express/ErrorHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ describe('ErrorHandler', () => {
test('error with AuthenticationError Forbidden', () => {
error(authError(HttpStatus.Forbidden), req, res, next);
expect(res.status).toHaveBeenCalledWith(HttpStatus.Forbidden.status);
expect(res.json).toHaveBeenCalledWith(withErrorAndMessage(HttpStatus.Forbidden, 1, "Forbidden"));
expect(res.json).toHaveBeenCalledWith(withErrorAndMessage(HttpStatus.Forbidden, 1, 'Forbidden'));
});

test('error with AuthenticationError NotAuthorized', () => {
error(authError(HttpStatus.NotAuthorized), req, res, next);
expect(res.status).toHaveBeenCalledWith(HttpStatus.NotAuthorized.status);
expect(res.json).toHaveBeenCalledWith(withErrorAndMessage(HttpStatus.NotAuthorized, 1, "Not authorized"));
expect(res.json).toHaveBeenCalledWith(withErrorAndMessage(HttpStatus.NotAuthorized, 1, 'Not authorized'));
});
});
7 changes: 6 additions & 1 deletion test/express/ExpressProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ describe('ExpressProvider', () => {
});

function mockRouterMethodOnce(router: express.Router, method: ExpressVerb, cb: (endpoint: Endpoint) => any) {
jest.spyOn(router, method).mockImplementationOnce((path: string, handler: RequestHandler) => cb({ path, handler: handler as AsyncHandler }));
jest.spyOn(router, method).mockImplementationOnce((path: string, handler: RequestHandler) =>
cb({
path,
handler: handler as AsyncHandler,
})
);
}

test('route', () => {
Expand Down

0 comments on commit 4bc9fc0

Please sign in to comment.