@loopback/rest
Version:
Expose controllers as REST endpoints and route REST API requests to controller methods
149 lines (135 loc) • 3.61 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/rest
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {
Options,
OptionsJson,
OptionsText,
OptionsUrlencoded,
} from 'body-parser';
import debugModule from 'debug';
import {HttpError} from 'http-errors';
import {Request, RequestBodyParserOptions, Response} from '../types';
const debug = debugModule('loopback:rest:body-parser');
/**
* Get the content-type header value from the request
* @param req - Http request
*/
export function getContentType(req: Request): string | undefined {
return req.get('content-type');
}
/**
* Express body parser function type
*/
export type BodyParserMiddleware = (
request: Request,
response: Response,
next: (err: HttpError) => void,
) => void;
/**
* Normalize parsing errors as `4xx`
* @param err
*/
export function normalizeParsingError(err: HttpError) {
debug('Cannot parse request body %j', err);
if (!err.statusCode || err.statusCode >= 500) {
err.statusCode = 400;
}
return err;
}
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Parse the request body asynchronously
* @param handle - The express middleware handler
* @param request - Http request
*/
export function invokeBodyParserMiddleware(
handle: BodyParserMiddleware,
request: Request,
): Promise<any> {
// A hack to fool TypeScript as we don't need `response`
const response = {} as any as Response;
return new Promise<void>((resolve, reject) => {
handle(request, response, err => {
if (err) {
reject(err);
return;
}
resolve(request.body);
});
});
}
// Default limit of the body length
export const DEFAULT_LIMIT = '1mb';
/**
* Extract parser options based on the parser type
* @param type - json|urlencoded|text
* @param options
*/
export function getParserOptions(
type: 'json',
options: RequestBodyParserOptions,
): OptionsJson;
export function getParserOptions(
type: 'urlencoded',
options: RequestBodyParserOptions,
): OptionsUrlencoded;
export function getParserOptions(
type: 'text',
options: RequestBodyParserOptions,
): OptionsText;
export function getParserOptions(
type: 'raw',
options: RequestBodyParserOptions,
): Options;
export function getParserOptions(
type: 'json' | 'urlencoded' | 'text' | 'raw',
options: RequestBodyParserOptions,
) {
const opts: {[name: string]: any} = {limit: DEFAULT_LIMIT};
switch (type) {
case 'json':
// Allow */json and */*+json
opts.type = ['*/json', '*/*+json'];
opts.strict = false;
break;
case 'urlencoded':
opts.type = type;
opts.extended = true;
break;
case 'text':
// Set media type to `text/*` to match `text/plain` or `text/html`
opts.type = 'text/*';
break;
case 'raw':
opts.type = ['application/octet-stream', '*/*'];
break;
}
Object.assign(opts, options[type], options);
for (const k of ['json', 'urlencoded', 'text', 'raw']) {
delete opts[k];
}
return opts;
}
export namespace builtinParsers {
export const json = Symbol('json');
export const urlencoded = Symbol('urlencoded');
export const text = Symbol('text');
export const raw = Symbol('raw');
export const stream = Symbol('stream');
export const names: (string | symbol)[] = [
json,
urlencoded,
text,
raw,
stream,
];
export const mapping: {[name: string]: symbol} = {
json,
urlencoded,
text,
raw,
stream,
};
}