diff --git a/packages/rest/package-lock.json b/packages/rest/package-lock.json index d6b35a92e130..b300c492d0c1 100644 --- a/packages/rest/package-lock.json +++ b/packages/rest/package-lock.json @@ -22,9 +22,9 @@ }, "dependencies": { "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" } } }, @@ -37,9 +37,9 @@ }, "dependencies": { "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" } } }, @@ -79,9 +79,9 @@ }, "dependencies": { "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" } } }, @@ -122,6 +122,11 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, + "@types/msgpack": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/msgpack/-/msgpack-0.0.30.tgz", + "integrity": "sha512-ehT/a2KQeQhMwMxvzvoZ/MFjwmOa9km5mL6G8XQPwF+z6DG1mJYhOvZACys9+Rd0zh0TxIHs1K3lKSImJGl0dg==" + }, "@types/multer": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", @@ -146,9 +151,9 @@ }, "dependencies": { "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" } } }, @@ -180,9 +185,9 @@ }, "dependencies": { "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" } } }, @@ -517,9 +522,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz", - "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", "requires": { "jake": "^10.6.1" } @@ -962,6 +967,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "msgpack": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/msgpack/-/msgpack-1.0.3.tgz", + "integrity": "sha512-FX88+Pexuh+UItdqg68WhNPGPhqs57RlXAYyXNZ8eTQC/Tx5lzeIoxTxA2qAd7qU5a/crON5F0ADf4l6k2b7Jw==", + "requires": { + "nan": "^2.14.0" + } + }, "multer": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", @@ -989,6 +1002,11 @@ } } }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", diff --git a/packages/rest/package.json b/packages/rest/package.json index 24af6fa59d89..f589d65ae62a 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -48,6 +48,7 @@ "js-yaml": "^3.14.0", "json-schema-compare": "^0.2.2", "lodash": "^4.17.20", + "msgpack": "^1.0.3", "on-finished": "^2.3.0", "path-to-regexp": "^6.1.0", "qs": "^6.9.4", @@ -66,6 +67,7 @@ "@types/js-yaml": "^3.12.5", "@types/json-schema-compare": "^0.2.0", "@types/lodash": "^4.14.159", + "@types/msgpack": "0.0.30", "@types/multer": "^1.4.4", "@types/node": "^10.17.28", "@types/on-finished": "^2.3.1", diff --git a/packages/rest/src/__tests__/unit/body-parser.unit.ts b/packages/rest/src/__tests__/unit/body-parser.unit.ts index 7088d1993292..26831ddb2f19 100644 --- a/packages/rest/src/__tests__/unit/body-parser.unit.ts +++ b/packages/rest/src/__tests__/unit/body-parser.unit.ts @@ -22,6 +22,7 @@ import { UrlEncodedBodyParser, } from '../..'; import {builtinParsers} from '../../body-parsers/body-parser.helpers'; +import {MsgPackBodyParser} from '../../body-parsers/body-parser.msgpack'; describe('body parser', () => { const defaultSchema = { @@ -212,6 +213,7 @@ describe('body parser', () => { new JsonBodyParser(options), new UrlEncodedBodyParser(options), new RawBodyParser(options), + new MsgPackBodyParser(), { name: 'xml', supports: mediaType => true, diff --git a/packages/rest/src/__tests__/unit/parser.unit.ts b/packages/rest/src/__tests__/unit/parser.unit.ts index 7ce0bad615f7..7a0b6940bee5 100644 --- a/packages/rest/src/__tests__/unit/parser.unit.ts +++ b/packages/rest/src/__tests__/unit/parser.unit.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,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 @@ -15,6 +15,7 @@ import { ShotRequestOptions, stubExpressContext, } from '@loopback/testlab'; +import msgpack from 'msgpack'; import { createResolvedRoute, JsonBodyParser, @@ -256,6 +257,47 @@ describe('operationArgsParser', () => { expect(args).to.eql([{key1: ['value1', 'value2']}]); }); + it('parses body parameter for MessagePack data', async () => { + const contentTypes = [ + 'application/msgpack', + 'application/x-msgpack', + 'application/subtype+msgpack', + ]; + + for (const contentType of contentTypes) { + const req = givenRequest({ + url: '/', + headers: { + 'Content-Type': contentType, + }, + payload: msgpack.pack({ + data: 'hello world', + }), + }); + + const spec = givenOperationWithRequestBody({ + description: 'data', + content: { + [contentType]: { + schema: { + type: 'object', + properties: { + data: { + type: 'string', + }, + }, + }, + }, + }, + }); + const route = givenResolvedRoute(spec); + + const args = await parseOperationArgs(req, route, requestBodyParser); + + expect(args).to.eql({data: 'hello world'}); + } + }); + it('parses body parameter for text data', async () => { const req = givenRequest({ url: '/', diff --git a/packages/rest/src/body-parsers/body-parser.helpers.ts b/packages/rest/src/body-parsers/body-parser.helpers.ts index 3d39b33954ce..0157bcde0adf 100644 --- a/packages/rest/src/body-parsers/body-parser.helpers.ts +++ b/packages/rest/src/body-parsers/body-parser.helpers.ts @@ -125,6 +125,7 @@ export function getParserOptions( export namespace builtinParsers { export const json = Symbol('json'); + export const msgpack = Symbol('msgpack'); export const urlencoded = Symbol('urlencoded'); export const text = Symbol('text'); export const raw = Symbol('raw'); @@ -132,6 +133,7 @@ export namespace builtinParsers { export const names: (string | symbol)[] = [ json, + msgpack, urlencoded, text, raw, @@ -140,6 +142,7 @@ export namespace builtinParsers { export const mapping: {[name: string]: symbol} = { json, + msgpack, urlencoded, text, raw, diff --git a/packages/rest/src/body-parsers/body-parser.json.ts b/packages/rest/src/body-parsers/body-parser.json.ts index 0b81860c3c3d..8bb9a86d5089 100644 --- a/packages/rest/src/body-parsers/body-parser.json.ts +++ b/packages/rest/src/body-parsers/body-parser.json.ts @@ -7,15 +7,15 @@ import {inject} from '@loopback/core'; import {json} from 'body-parser'; import {is} from 'type-is'; import {RestBindings} from '../keys'; +import {sanitizeJsonParse} from '../parse-json'; import {Request, RequestBodyParserOptions} from '../types'; import { BodyParserMiddleware, + builtinParsers, getParserOptions, invokeBodyParserMiddleware, - builtinParsers, } from './body-parser.helpers'; import {BodyParser, RequestBody} from './types'; -import {sanitizeJsonParse} from '../parse-json'; export class JsonBodyParser implements BodyParser { name = builtinParsers.json; diff --git a/packages/rest/src/body-parsers/body-parser.msgpack.ts b/packages/rest/src/body-parsers/body-parser.msgpack.ts new file mode 100644 index 000000000000..142d7296c23a --- /dev/null +++ b/packages/rest/src/body-parsers/body-parser.msgpack.ts @@ -0,0 +1,30 @@ +// Copyright IBM Corp. 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 msgpack from 'msgpack'; +import {is} from 'type-is'; +import {Request} from '../types'; +import {builtinParsers} from './body-parser.helpers'; +import {BodyParser, RequestBody} from './types'; + +export class MsgPackBodyParser implements BodyParser { + name = builtinParsers.msgpack; + + constructor() {} + + supports(mediaType: string) { + return !!is( + mediaType, + 'application/msgpack', + 'application/x-msgpack', + 'application/*+msgpack', + ); + } + + async parse(request: Request): Promise { + const body = msgpack.unpack(request.body); + return {value: body}; + } +} diff --git a/packages/rest/src/keys.ts b/packages/rest/src/keys.ts index 3970f130363c..9719b6c87bbe 100644 --- a/packages/rest/src/keys.ts +++ b/packages/rest/src/keys.ts @@ -127,6 +127,13 @@ export namespace RestBindings { bodyParserBindingKey('JsonBodyParser'), ); + /** + * Binding key for request msgpack body parser + */ + export const REQUEST_BODY_PARSER_MSGPACK = BindingKey.create( + bodyParserBindingKey('MsgPackBodyParser'), + ); + /** * Binding key for request urlencoded body parser */ diff --git a/packages/rest/src/rest.component.ts b/packages/rest/src/rest.component.ts index 9bf46648300e..ebada2e902e3 100644 --- a/packages/rest/src/rest.component.ts +++ b/packages/rest/src/rest.component.ts @@ -24,6 +24,7 @@ import { TextBodyParser, UrlEncodedBodyParser, } from './body-parsers'; +import {MsgPackBodyParser} from './body-parsers/body-parser.msgpack'; import {RawBodyParser} from './body-parsers/body-parser.raw'; import {RestBindings, RestTags} from './keys'; import { @@ -68,6 +69,10 @@ export class RestComponent implements Component { JsonBodyParser, RestBindings.REQUEST_BODY_PARSER_JSON, ), + createBodyParserBinding( + MsgPackBodyParser, + RestBindings.REQUEST_BODY_PARSER_MSGPACK, + ), createBodyParserBinding( TextBodyParser, RestBindings.REQUEST_BODY_PARSER_TEXT,