From 99060781c3e583e7ef7cfb898816b53e97a2f5ed Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Thu, 20 Aug 2020 17:13:09 +0800 Subject: [PATCH] feat(rest): add msgpack body parser Signed-off-by: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> --- .gitignore | 1 + CODEOWNERS | 8 + bodyparsers/rest-msgpack/.npmrc | 2 + bodyparsers/rest-msgpack/LICENSE | 27 +++ bodyparsers/rest-msgpack/README.md | 163 ++++++++++++++++++ bodyparsers/rest-msgpack/package-lock.json | 101 +++++++++++ bodyparsers/rest-msgpack/package.json | 53 ++++++ .../acceptance/bodyparser.acceptance.ts | 132 ++++++++++++++ .../acceptance/component.acceptance.ts | 32 ++++ .../src/__tests__/unit/bodyparser.unit.ts | 29 ++++ bodyparsers/rest-msgpack/src/bodyparser.ts | 44 +++++ bodyparsers/rest-msgpack/src/component.ts | 30 ++++ bodyparsers/rest-msgpack/src/index.ts | 10 ++ bodyparsers/rest-msgpack/src/keys.ts | 13 ++ bodyparsers/rest-msgpack/tsconfig.json | 23 +++ docs/site/Accepting-MessagePack-over-HTTP.md | 10 ++ docs/site/MONOREPO.md | 1 + docs/site/sidebars/lb4_sidebar.yml | 4 + lerna.json | 1 + tsconfig.json | 3 + 20 files changed, 687 insertions(+) create mode 100644 bodyparsers/rest-msgpack/.npmrc create mode 100644 bodyparsers/rest-msgpack/LICENSE create mode 100644 bodyparsers/rest-msgpack/README.md create mode 100644 bodyparsers/rest-msgpack/package-lock.json create mode 100644 bodyparsers/rest-msgpack/package.json create mode 100644 bodyparsers/rest-msgpack/src/__tests__/acceptance/bodyparser.acceptance.ts create mode 100644 bodyparsers/rest-msgpack/src/__tests__/acceptance/component.acceptance.ts create mode 100644 bodyparsers/rest-msgpack/src/__tests__/unit/bodyparser.unit.ts create mode 100644 bodyparsers/rest-msgpack/src/bodyparser.ts create mode 100644 bodyparsers/rest-msgpack/src/component.ts create mode 100644 bodyparsers/rest-msgpack/src/index.ts create mode 100644 bodyparsers/rest-msgpack/src/keys.ts create mode 100644 bodyparsers/rest-msgpack/tsconfig.json create mode 100644 docs/site/Accepting-MessagePack-over-HTTP.md diff --git a/.gitignore b/.gitignore index 6e3414170261..15ce1ccf8060 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ fixtures/tsdocs-monorepo/packages/pkg1/docs acceptance/*/dist packages/*/dist extensions/*/dist +bodyparsers/*/dist examples/*/dist benchmark/dist **/package diff --git a/CODEOWNERS b/CODEOWNERS index af0a3e511459..b79baaa89d83 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -324,6 +324,14 @@ /extensions/graphql @raymondfeng /examples/graphql @raymondfeng +# +# MessagePack body parser +# +# - Issue label: n/a +# - Primary owner(s): @achrinza +# - Standby owner(s): n/a +/bodyparsers/rest-msgpack @achrinza + # # Build & internal tooling # diff --git a/bodyparsers/rest-msgpack/.npmrc b/bodyparsers/rest-msgpack/.npmrc new file mode 100644 index 000000000000..34fbbbb3f3e4 --- /dev/null +++ b/bodyparsers/rest-msgpack/.npmrc @@ -0,0 +1,2 @@ +package-lock=true +scripts-prepend-node-path=true diff --git a/bodyparsers/rest-msgpack/LICENSE b/bodyparsers/rest-msgpack/LICENSE new file mode 100644 index 000000000000..1519a3403681 --- /dev/null +++ b/bodyparsers/rest-msgpack/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/bodyparser-msgpack +This project is licensed under the MIT License, full text below. + +-------- + +MIT License + +MIT License Copyright (c) IBM Corp. 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bodyparsers/rest-msgpack/README.md b/bodyparsers/rest-msgpack/README.md new file mode 100644 index 000000000000..b222c76488ca --- /dev/null +++ b/bodyparsers/rest-msgpack/README.md @@ -0,0 +1,163 @@ +# @loopback/rest-msgpack + +This module extends LoopBack with the ability to receive +[MessagePack](https://msgpack.org/) requests and transparently convert it to a +regular JavaScript object. It provides a BodyParser implementation and a +component to register it. + +## Stability: ⚠️Experimental⚠️ + +> Experimental packages provide early access to advanced or experimental +> functionality to get community feedback. Such modules are published to npm +> using `0.x.y` versions. Their APIs and functionality may be subject to +> breaking changes in future releases. + +## Installation + +```sh +npm i @loopback/rest-msgpack --save +``` + +## Usage + +The component should be loaded in the constructor of your custom Application +class. + +Start by importing the component class: + +```ts +import {MsgPackBodyParserComponent} from '@loopback/rest-msgpack'; +``` + +In the constructor, add the component to your application: + +```ts +this.component(MsgPackBodyParserComponent); +``` + +The body parser will accept requests with the following MIME type +(`Content-Type`) blobs: + +- `application/msgpack` +- `application/x-msgpack` +- `application/*+msgpack` + +### Accepting MessagePack Requests + +To accept MessagePack requests in a controller, amend the OpenAPI decorator to +include the MIME type as a possible request body. + +For example, to update the Todo controller to accept MessagePack: + +```typescript +import {post, getModelSchemaRef, requestBody} from '@loopback/rest'; + +class TodoController { + // Omitted constructor for bevity + + @post('/todos') + async create( + @requestBody({ + content: { + // Change existing or append a new request body accepted MIME type + 'application/msgpack': { + schema: getModelSchemaRef(Todo, { + title: 'NewTodo', + exclude: ['id'], + }), + }, + }, + }) + todo: // Keep the request body object type, since the body parser transparently + // converts it into a JavaScript object. + Omit, + + // For bevity, the function does not return anything. See + // 'Returning MessagePack Requests' below. + ): void { + this.todoRepository.create(todo); + } +} +``` + +The MessagePack request payload will be transparently converted into a +JavaScript object and validated against the JSON Schema. + +### Returning MessagePack Requests + +{% include note.html content="The body parser will not convert responses into `application/msgpack` automatically. This feature is being tracked by [#6275](https://github.com/strongloop/loopback-next/issues/6275)" %} + +To return MessagePack requests in a controller, amend the requestBody decorator +to include the MIME type as a possible response and use a parser library. + +For example, to update the Todo controller to return in MessagePack: + +```ts +// `msgpack-lite` is re-exported by `@loopback/rest-msgpack` for convenience. +// It is recommended to bind it to context the inject it to benefit from +// dependency injection. +import {MsgPackBodyParserBindings, msgpack} from '@loopback/rest-msgpack'; +import {inject} from '@loopback/core'; +import {getModelSchemaRef, post, Response, RestBindings} from '@loopback/rest'; + +class TodoController { + private readonly _response: Response; + + constructor( + // Omitted other dependency injections (e.g. repository) for bevity. + + // Inject the Response object to the controller + @inject(RestBindings.Http.RESPONSE) + private readonly _res: Response, + ) {} + + @get('/todos', { + responses: { + '200': { + description: 'Array of Todo model instances', + content: { + // Update existing or amend new possible response + 'application/msgpack': { + schema: {type: 'array', items: getModelSchemaRef(Todo)}, + }, + }, + }, + }, + }) + async findTodos( + @param.filter(Todo) + filter?: Filter, + + // Change function return type to Promise. + ): Promise { + // Internally, LoopBack 4 will try to guess and override the `Content-Type` + // header, even after manually setting the headers. + // Buffers are automatically detected as `application/octet-stream`. + // We can use `Response.end()` to bypass that. + // + // See: https://github.com/strongloop/loopback-next/issues/5168 + // + this._res + .type('application/msgpack') + .end(msgpack.encode(this.todoRepository.find(filter))); + } +} +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/bodyparsers/rest-msgpack/package-lock.json b/bodyparsers/rest-msgpack/package-lock.json new file mode 100644 index 000000000000..4a223147180e --- /dev/null +++ b/bodyparsers/rest-msgpack/package-lock.json @@ -0,0 +1,101 @@ +{ + "name": "@loopback/bodyparser-msgpack", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/msgpack-lite": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@types/msgpack-lite/-/msgpack-lite-0.1.7.tgz", + "integrity": "sha512-OYPf2ExHl0TKuDn7M/356RDyCiqOQEs8f+SfePpbfHJJ7C8mqgp4ShgeUTuE8W7Tui2txbyVPiRiDPeK3D9PPA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==", + "dev": true + }, + "@types/type-is": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.3.tgz", + "integrity": "sha512-PNs5wHaNcBgCQG5nAeeZ7OvosrEsI9O4W2jAOO9BCCg4ux9ZZvH2+0iSCOIDBiKuQsiNS8CBlmfX9f5YBQ22cA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "event-lite": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.2.tgz", + "integrity": "sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g==" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "int64-buffer": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz", + "integrity": "sha1-J3siiofZWtd30HwTgyAiQGpHNCM=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "msgpack-lite": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz", + "integrity": "sha1-3TxQsm8FnyXn7e42REGDWOKprYk=", + "requires": { + "event-lite": "^0.1.1", + "ieee754": "^1.1.8", + "int64-buffer": "^0.1.9", + "isarray": "^1.0.0" + } + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + } + } +} diff --git a/bodyparsers/rest-msgpack/package.json b/bodyparsers/rest-msgpack/package.json new file mode 100644 index 000000000000..19f1ece6c39f --- /dev/null +++ b/bodyparsers/rest-msgpack/package.json @@ -0,0 +1,53 @@ +{ + "name": "@loopback/rest-msgpack", + "version": "0.1.0", + "description": "Body parser to handle MessagePack requests in LoopBack 4.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=10.16" + }, + "scripts": { + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "clean": "lb-clean dist *.tsbuildinfo .eslintcache" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "bodyparsers/msgpack" + }, + "author": "IBM Corp.", + "license": "MIT", + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "peerDependencies": { + "@loopback/core": "^2.9.4", + "@loopback/rest": "^6.1.0" + }, + "dependencies": { + "msgpack-lite": "0.1.26", + "tslib": "^2.0.0", + "type-is": "^1.6.18" + }, + "devDependencies": { + "@loopback/build": "^6.2.1", + "@loopback/core": "^2.9.4", + "@loopback/rest": "^6.1.0", + "@loopback/testlab": "^3.2.3", + "@types/msgpack-lite": "0.1.7", + "@types/node": "^10.17.28", + "@types/type-is": "^1.6.3", + "typescript": "~4.0.2" + }, + "copyright.owner": "IBM Corp.", + "publishConfig": { + "access": "public" + } +} diff --git a/bodyparsers/rest-msgpack/src/__tests__/acceptance/bodyparser.acceptance.ts b/bodyparsers/rest-msgpack/src/__tests__/acceptance/bodyparser.acceptance.ts new file mode 100644 index 000000000000..dfd13d0a3724 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/__tests__/acceptance/bodyparser.acceptance.ts @@ -0,0 +1,132 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Context} from '@loopback/core'; +import { + createResolvedRoute, + HttpErrors, + OperationObject, + parseOperationArgs, + PathParameterValues, + Request, + RequestBodyObject, + RequestBodyParser, + Route, +} from '@loopback/rest'; +import { + expect, + ShotRequestOptions, + stubExpressContext, +} from '@loopback/testlab'; +import {encode} from 'msgpack-lite'; +import {MsgPackBodyParser} from '../..'; + +describe('MessagePack body parser', () => { + let requestBodyParser: RequestBodyParser; + beforeEach(givenRequestBodyParser); + + const contentTypes = [ + 'application/msgpack', + 'application/x-msgpack', + 'application/subtype+msgpack', + ]; + + for (const contentType of contentTypes) { + it(`parses ${contentType} body as MessagePack data`, async () => { + const spec = givenOperationWithRequestBody({ + description: 'data', + content: { + [contentType]: { + schema: { + type: 'object', + properties: { + data: { + type: 'string', + }, + }, + }, + }, + }, + }); + + const req = givenRequest({ + url: '/', + headers: { + 'Content-Type': contentType, + }, + payload: encode({ + data: 'hello world', + }), + }); + + const route = givenResolvedRoute(spec); + const args = await parseOperationArgs(req, route, requestBodyParser); + + expect(args).to.eql([{data: 'hello world'}]); + }); + } + + it(`rejects MessagePack data that do not conform to OAS 3 Schema Object`, async () => { + const spec = givenOperationWithRequestBody({ + description: 'data', + content: { + 'application/msgpack': { + schema: { + type: 'object', + properties: { + data: { + type: 'number', + }, + }, + }, + }, + }, + }); + + const req = givenRequest({ + url: '/', + headers: { + 'Content-Type': 'application/msgpack', + }, + payload: encode({ + data: 'does not conform to OAS 3 Schema Object', + }), + }); + + const route = givenResolvedRoute(spec); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + expect( + parseOperationArgs(req, route, requestBodyParser), + ).to.be.rejectedWith(HttpErrors.UnprocessableEntity); + }); + + function givenOperationWithRequestBody(requestBody?: RequestBodyObject) { + return { + 'x-operation-name': 'testOp', + requestBody: requestBody, + responses: {}, + }; + } + + function givenRequest(options?: ShotRequestOptions): Request { + return stubExpressContext(options).request; + } + + function givenResolvedRoute( + spec: OperationObject, + pathParams: PathParameterValues = {}, + ) { + const route = new Route('get', '/', spec, () => {}); + return createResolvedRoute(route, pathParams); + } + + function givenRequestBodyParser() { + const options = {}; + const parsers = [new MsgPackBodyParser(options)]; + const context = new Context(); + requestBodyParser = new RequestBodyParser(parsers, context); + } +}); diff --git a/bodyparsers/rest-msgpack/src/__tests__/acceptance/component.acceptance.ts b/bodyparsers/rest-msgpack/src/__tests__/acceptance/component.acceptance.ts new file mode 100644 index 000000000000..8f9365e4ea88 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/__tests__/acceptance/component.acceptance.ts @@ -0,0 +1,32 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {RestApplication, RestBindings} from '@loopback/rest'; +import {expect} from '@loopback/testlab'; +import { + MsgPackBodyParser, + MsgPackBodyParserBindings, + MsgPackBodyParserComponent, +} from '../..'; + +describe('MessagePack body parser component', () => { + it('binds MessagePack body parser', async () => { + const restApplication = new RestApplication(); + restApplication.component(MsgPackBodyParserComponent); + + expect( + await restApplication.get(MsgPackBodyParserBindings.BODY_PARSER), + ).to.be.instanceOf(MsgPackBodyParser); + }); + + it('throws error without raw body parser', async () => { + const restApplication = new RestApplication(); + restApplication.unbind(RestBindings.REQUEST_BODY_PARSER_RAW); + + expect(() => + restApplication.component(MsgPackBodyParserComponent), + ).to.throw(); + }); +}); diff --git a/bodyparsers/rest-msgpack/src/__tests__/unit/bodyparser.unit.ts b/bodyparsers/rest-msgpack/src/__tests__/unit/bodyparser.unit.ts new file mode 100644 index 000000000000..92ad8a4f6525 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/__tests__/unit/bodyparser.unit.ts @@ -0,0 +1,29 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import {MsgPackBodyParser} from '../..'; + +describe('MessagePack body parser', () => { + const contentTypes = [ + 'application/msgpack', + 'application/x-msgpack', + 'application/subtype+msgpack', + ]; + + let bodyParser: MsgPackBodyParser; + + beforeEach(givenBodyParser); + + for (const contentType of contentTypes) { + it(`accepts ${contentType}`, () => { + expect(bodyParser.supports(contentType)).to.be.true(); + }); + } + + function givenBodyParser() { + bodyParser = new MsgPackBodyParser(); + } +}); diff --git a/bodyparsers/rest-msgpack/src/bodyparser.ts b/bodyparsers/rest-msgpack/src/bodyparser.ts new file mode 100644 index 000000000000..4048ed85c205 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/bodyparser.ts @@ -0,0 +1,44 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/core'; +import { + RawBodyParser, + Request, + RequestBody, + RequestBodyParserOptions, + RestBindings, +} from '@loopback/rest'; +import {is} from 'type-is'; +import {msgpack} from '.'; + +export class MsgPackBodyParser extends RawBodyParser { + name = Symbol('msgpack'); + + constructor( + @inject(RestBindings.REQUEST_BODY_PARSER_OPTIONS, {optional: true}) + options: RequestBodyParserOptions = {}, + ) { + super(options); + } + + supports(mediaType: string) { + return !!is( + mediaType, + 'application/msgpack', + 'application/x-msgpack', + 'application/*+msgpack', + ); + } + + async parse(request: Request): Promise { + const result = await super.parse(request); + const body = msgpack.decode(result.value); + + return { + value: body, + }; + } +} diff --git a/bodyparsers/rest-msgpack/src/component.ts b/bodyparsers/rest-msgpack/src/component.ts new file mode 100644 index 000000000000..29ed06d62549 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/component.ts @@ -0,0 +1,30 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Binding, Component, inject} from '@loopback/core'; +import { + createBodyParserBinding, + RawBodyParser, + RestBindings, +} from '@loopback/rest'; +import {MsgPackBodyParser, MsgPackBodyParserBindings} from '.'; + +export class MsgPackBodyParserComponent implements Component { + bindings: Binding[] = [ + createBodyParserBinding( + MsgPackBodyParser, + MsgPackBodyParserBindings.BODY_PARSER, + ), + ]; + + constructor( + @inject(RestBindings.REQUEST_BODY_PARSER_RAW, {optional: true}) + rawBodyParser?: RawBodyParser, + ) { + if (rawBodyParser == null) { + throw new Error('MessagePack body parser requires raw body parser.'); + } + } +} diff --git a/bodyparsers/rest-msgpack/src/index.ts b/bodyparsers/rest-msgpack/src/index.ts new file mode 100644 index 000000000000..c86d1caac5b1 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/index.ts @@ -0,0 +1,10 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import msgpack from 'msgpack-lite'; +export * from './bodyparser'; +export * from './component'; +export * from './keys'; +export {msgpack}; diff --git a/bodyparsers/rest-msgpack/src/keys.ts b/bodyparsers/rest-msgpack/src/keys.ts new file mode 100644 index 000000000000..edc7314a9693 --- /dev/null +++ b/bodyparsers/rest-msgpack/src/keys.ts @@ -0,0 +1,13 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/bodyparser-msgpack +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {BodyParser, RestBindings} from '@loopback/rest'; + +export namespace MsgPackBodyParserBindings { + export const BODY_PARSER = BindingKey.create( + `${RestBindings.REQUEST_BODY_PARSER}.msgpack`, + ); +} diff --git a/bodyparsers/rest-msgpack/tsconfig.json b/bodyparsers/rest-msgpack/tsconfig.json new file mode 100644 index 000000000000..bdd4f3eb6b2a --- /dev/null +++ b/bodyparsers/rest-msgpack/tsconfig.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../packages/core/tsconfig.json" + }, + { + "path": "../../packages/rest/tsconfig.json" + }, + { + "path": "../../packages/testlab/tsconfig.json" + } + ] +} diff --git a/docs/site/Accepting-MessagePack-over-HTTP.md b/docs/site/Accepting-MessagePack-over-HTTP.md new file mode 100644 index 000000000000..e4b74290637b --- /dev/null +++ b/docs/site/Accepting-MessagePack-over-HTTP.md @@ -0,0 +1,10 @@ +--- +lang: en +title: 'How to accept MessagePack over HTTP' +keywords: LoopBack 4.0, LoopBack 4, MessagePack +sidebar: lb4_sidebar +layout: readme +source: loopback-next +file: bodyparsers/msgpack/README.md +permalink: /doc/en/lb4/Accepting-messagepack-over-http.html +--- diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index e11de5dcff96..ce5829f87801 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -16,6 +16,7 @@ one in the monorepo: `npm run update-monorepo-file` | [acceptance/repository-mysql](https://github.com/strongloop/loopback-next/tree/master/acceptance/repository-mysql) | _(private)_ | Acceptance tests for `@loopback/repository` + `loopback-connector-mysql` | | [acceptance/repository-postgresql](https://github.com/strongloop/loopback-next/tree/master/acceptance/repository-postgresql) | _(private)_ | Acceptance tests for `@loopback/repository` + `loopback-connector-postgresql` | | [benchmark](https://github.com/strongloop/loopback-next/tree/master/benchmark) | _(private)_ | Benchmarks measuring performance of our framework. | +| [bodyparsers/rest-msgpack](https://github.com/strongloop/loopback-next/tree/master/bodyparsers/rest-msgpack) | @loopback/rest-msgpack | Body parser to handle MessagePack requests in LoopBack 4. | | [docs](https://github.com/strongloop/loopback-next/tree/master/docs) | @loopback/docs | Documentation files rendered at [https://loopback.io](https://loopback.io) | | [examples/access-control-migration](https://github.com/strongloop/loopback-next/tree/master/examples/access-control-migration) | @loopback/example-access-control-migration | Tutorial example on how to migrate the access control example with LoopBack 4. | | [examples/context](https://github.com/strongloop/loopback-next/tree/master/examples/context) | @loopback/example-context | Standalone examples to illustrate features provided by @loopback/context | diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index 18d3b8017f2d..61d6baa01154 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -232,6 +232,10 @@ children: url: Integrating-with-api-connect.html output: 'web, pdf' + - title: 'Accepting MessagePack over HTTP' + url: Accepting-messagepack-over-http.html + output: 'web, pdf' + - title: 'Creating Other Forms of APIs' output: 'web, pdf' children: diff --git a/lerna.json b/lerna.json index 0133255c1bed..105d5c232e99 100644 --- a/lerna.json +++ b/lerna.json @@ -7,6 +7,7 @@ "examples/*", "packages/*", "extensions/*", + "bodyparsers/*", "sandbox/*" ], "command": { diff --git a/tsconfig.json b/tsconfig.json index de0b30cf1401..703e7a373c87 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,9 @@ { "path": "benchmark/tsconfig.json" }, + { + "path": "bodyparsers/rest-msgpack/tsconfig.json" + }, { "path": "examples/access-control-migration/tsconfig.json" },