@loopback/rest 4.0.0-alpha.8 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1822 -0
- package/LICENSE +1 -1
- package/README.md +30 -58
- package/dist/body-parsers/body-parser.d.ts +25 -0
- package/dist/body-parsers/body-parser.helpers.d.ts +44 -0
- package/dist/body-parsers/body-parser.helpers.js +102 -0
- package/dist/body-parsers/body-parser.helpers.js.map +1 -0
- package/dist/body-parsers/body-parser.js +159 -0
- package/dist/body-parsers/body-parser.js.map +1 -0
- package/dist/body-parsers/body-parser.json.d.ts +9 -0
- package/dist/body-parsers/body-parser.json.js +43 -0
- package/dist/body-parsers/body-parser.json.js.map +1 -0
- package/dist/body-parsers/body-parser.raw.d.ts +12 -0
- package/dist/body-parsers/body-parser.raw.js +39 -0
- package/dist/body-parsers/body-parser.raw.js.map +1 -0
- package/dist/body-parsers/body-parser.stream.d.ts +12 -0
- package/dist/body-parsers/body-parser.stream.js +28 -0
- package/dist/body-parsers/body-parser.stream.js.map +1 -0
- package/dist/body-parsers/body-parser.text.d.ts +9 -0
- package/dist/body-parsers/body-parser.text.js +38 -0
- package/dist/body-parsers/body-parser.text.js.map +1 -0
- package/dist/body-parsers/body-parser.urlencoded.d.ts +9 -0
- package/dist/body-parsers/body-parser.urlencoded.js +36 -0
- package/dist/body-parsers/body-parser.urlencoded.js.map +1 -0
- package/dist/body-parsers/index.d.ts +8 -0
- package/dist/body-parsers/index.js +16 -0
- package/dist/body-parsers/index.js.map +1 -0
- package/dist/body-parsers/types.d.ts +51 -0
- package/dist/body-parsers/types.js +12 -0
- package/dist/body-parsers/types.js.map +1 -0
- package/dist/coercion/coerce-parameter.d.ts +9 -0
- package/dist/coercion/coerce-parameter.js +166 -0
- package/dist/coercion/coerce-parameter.js.map +1 -0
- package/dist/coercion/utils.d.ts +43 -0
- package/dist/coercion/utils.js +96 -0
- package/dist/coercion/utils.js.map +1 -0
- package/dist/coercion/validator.d.ts +49 -0
- package/dist/coercion/validator.js +85 -0
- package/dist/coercion/validator.js.map +1 -0
- package/dist/http-handler.d.ts +38 -0
- package/dist/http-handler.js +68 -0
- package/dist/http-handler.js.map +1 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +40 -6
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +198 -0
- package/dist/keys.js +202 -0
- package/dist/keys.js.map +1 -0
- package/dist/parse-json.d.ts +11 -0
- package/dist/parse-json.js +42 -0
- package/dist/parse-json.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +76 -0
- package/dist/parser.js.map +1 -0
- package/{dist6/src/providers/find-route.d.ts → dist/providers/find-route.provider.d.ts} +3 -1
- package/dist/providers/find-route.provider.js +36 -0
- package/dist/providers/find-route.provider.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +14 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/{src/providers/invoke-method.d.ts → providers/invoke-method.provider.d.ts} +3 -1
- package/dist/providers/invoke-method.provider.js +30 -0
- package/dist/providers/invoke-method.provider.js.map +1 -0
- package/dist/providers/log-error.provider.d.ts +6 -0
- package/dist/providers/log-error.provider.js +21 -0
- package/dist/providers/log-error.provider.js.map +1 -0
- package/dist/providers/parse-params.provider.d.ts +15 -0
- package/dist/providers/parse-params.provider.js +41 -0
- package/dist/providers/parse-params.provider.js.map +1 -0
- package/dist/providers/reject.provider.d.ts +10 -0
- package/dist/providers/reject.provider.js +47 -0
- package/dist/providers/reject.provider.js.map +1 -0
- package/dist/{src/providers/send.d.ts → providers/send.provider.d.ts} +1 -4
- package/dist/{src/providers/send.js → providers/send.provider.js} +4 -6
- package/dist/providers/send.provider.js.map +1 -0
- package/dist/request-context.d.ts +36 -0
- package/dist/request-context.js +104 -0
- package/dist/request-context.js.map +1 -0
- package/dist/rest-http-error.d.ts +37 -0
- package/dist/rest-http-error.js +51 -0
- package/dist/rest-http-error.js.map +1 -0
- package/dist/rest.application.d.ts +232 -0
- package/dist/rest.application.js +174 -0
- package/dist/rest.application.js.map +1 -0
- package/dist/rest.component.d.ts +15 -0
- package/dist/rest.component.js +72 -0
- package/dist/rest.component.js.map +1 -0
- package/dist/rest.server.d.ts +443 -0
- package/dist/rest.server.js +748 -0
- package/dist/rest.server.js.map +1 -0
- package/dist/router/base-route.d.ts +29 -0
- package/dist/router/base-route.js +41 -0
- package/dist/router/base-route.js.map +1 -0
- package/dist/router/controller-route.d.ts +61 -0
- package/dist/router/controller-route.js +160 -0
- package/dist/router/controller-route.js.map +1 -0
- package/dist/router/external-express-routes.d.ts +24 -0
- package/dist/router/external-express-routes.js +90 -0
- package/dist/router/external-express-routes.js.map +1 -0
- package/dist/router/handler-route.d.ts +12 -0
- package/dist/router/handler-route.js +30 -0
- package/dist/router/handler-route.js.map +1 -0
- package/dist/router/index.d.ts +14 -0
- package/dist/router/index.js +25 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/openapi-path.d.ts +14 -0
- package/dist/router/openapi-path.js +64 -0
- package/dist/router/openapi-path.js.map +1 -0
- package/dist/router/redirect-route.d.ts +23 -0
- package/dist/router/redirect-route.js +50 -0
- package/dist/router/redirect-route.js.map +1 -0
- package/dist/router/regexp-router.d.ts +25 -0
- package/dist/router/regexp-router.js +84 -0
- package/dist/router/regexp-router.js.map +1 -0
- package/dist/router/rest-router.d.ts +35 -0
- package/dist/{src/internal-types.js → router/rest-router.js} +2 -2
- package/dist/router/rest-router.js.map +1 -0
- package/dist/router/route-entry.d.ts +46 -0
- package/dist/router/route-entry.js +20 -0
- package/dist/router/route-entry.js.map +1 -0
- package/dist/router/route-sort.d.ts +7 -0
- package/dist/router/route-sort.js +75 -0
- package/dist/router/route-sort.js.map +1 -0
- package/dist/router/router-base.d.ts +42 -0
- package/dist/router/router-base.js +101 -0
- package/dist/router/router-base.js.map +1 -0
- package/dist/router/router-spec.d.ts +3 -0
- package/dist/router/router-spec.js +40 -0
- package/dist/router/router-spec.js.map +1 -0
- package/dist/router/routing-table.d.ts +32 -0
- package/dist/router/routing-table.js +86 -0
- package/dist/router/routing-table.js.map +1 -0
- package/dist/router/trie-router.d.ts +13 -0
- package/dist/router/trie-router.js +55 -0
- package/dist/router/trie-router.js.map +1 -0
- package/dist/router/trie.d.ts +59 -0
- package/dist/router/trie.js +180 -0
- package/dist/router/trie.js.map +1 -0
- package/{dist6/src → dist}/sequence.d.ts +28 -23
- package/dist/sequence.js +112 -0
- package/dist/sequence.js.map +1 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.d.ts +68 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.js +145 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -0
- package/dist/spec-enhancers/info.spec-enhancer.d.ts +19 -0
- package/dist/spec-enhancers/info.spec-enhancer.js +89 -0
- package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/validation/ajv-factory.provider.d.ts +12 -0
- package/dist/validation/ajv-factory.provider.js +87 -0
- package/dist/validation/ajv-factory.provider.js.map +1 -0
- package/dist/validation/request-body.validator.d.ts +14 -0
- package/dist/validation/request-body.validator.js +161 -0
- package/dist/validation/request-body.validator.js.map +1 -0
- package/dist/writer.d.ts +9 -0
- package/dist/writer.js +62 -0
- package/dist/writer.js.map +1 -0
- package/package.json +66 -38
- package/src/body-parsers/body-parser.helpers.ts +148 -0
- package/src/body-parsers/body-parser.json.ts +46 -0
- package/src/body-parsers/body-parser.raw.ts +42 -0
- package/src/body-parsers/body-parser.stream.ts +27 -0
- package/src/body-parsers/body-parser.text.ts +44 -0
- package/src/body-parsers/body-parser.ts +208 -0
- package/src/body-parsers/body-parser.urlencoded.ts +42 -0
- package/src/body-parsers/index.ts +13 -0
- package/src/body-parsers/types.ts +60 -0
- package/src/coercion/coerce-parameter.ts +207 -0
- package/src/coercion/utils.ts +103 -0
- package/src/coercion/validator.ts +98 -0
- package/src/http-handler.ts +84 -41
- package/src/index.ts +37 -30
- package/src/keys.ts +273 -20
- package/src/parse-json.ts +42 -0
- package/src/parser.ts +89 -104
- package/src/providers/{find-route.ts → find-route.provider.ts} +10 -7
- package/src/providers/index.ts +7 -9
- package/src/providers/{invoke-method.ts → invoke-method.provider.ts} +8 -5
- package/src/providers/log-error.provider.ts +27 -0
- package/src/providers/parse-params.provider.ts +42 -0
- package/src/providers/reject.provider.ts +44 -0
- package/src/providers/{send.ts → send.provider.ts} +2 -5
- package/src/request-context.ts +123 -0
- package/src/rest-http-error.ts +87 -0
- package/src/rest.application.ts +390 -0
- package/src/rest.component.ts +111 -0
- package/src/rest.server.ts +1192 -0
- package/src/router/base-route.ts +53 -0
- package/src/router/controller-route.ts +241 -0
- package/src/router/external-express-routes.ts +139 -0
- package/src/router/handler-route.ts +44 -0
- package/src/router/index.ts +24 -0
- package/src/router/openapi-path.ts +67 -0
- package/src/router/redirect-route.ts +64 -0
- package/src/router/regexp-router.ts +104 -0
- package/src/router/rest-router.ts +48 -0
- package/src/router/route-entry.ts +74 -0
- package/src/router/route-sort.ts +74 -0
- package/src/router/router-base.ts +124 -0
- package/src/router/router-spec.ts +36 -0
- package/src/router/routing-table.ts +83 -279
- package/src/router/trie-router.ts +57 -0
- package/src/router/trie.ts +233 -0
- package/src/sequence.ts +44 -37
- package/src/spec-enhancers/consolidate.spec-enhancer.ts +182 -0
- package/src/spec-enhancers/info.spec-enhancer.ts +92 -0
- package/src/types.ts +216 -0
- package/src/validation/ajv-factory.provider.ts +94 -0
- package/src/validation/request-body.validator.ts +208 -0
- package/src/writer.ts +41 -68
- package/api-docs/.DS_Store +0 -0
- package/api-docs/apple-touch-icon-114x114-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-144x144-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-57x57-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-72x72-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-precomposed.png +0 -0
- package/api-docs/apple-touch-icon.png +0 -0
- package/api-docs/css/bootstrap.min.css +0 -9
- package/api-docs/css/code-themes/arta.css +0 -158
- package/api-docs/css/code-themes/ascetic.css +0 -50
- package/api-docs/css/code-themes/brown_paper.css +0 -104
- package/api-docs/css/code-themes/brown_papersq.png +0 -0
- package/api-docs/css/code-themes/dark.css +0 -103
- package/api-docs/css/code-themes/default.css +0 -135
- package/api-docs/css/code-themes/far.css +0 -111
- package/api-docs/css/code-themes/github.css +0 -127
- package/api-docs/css/code-themes/googlecode.css +0 -144
- package/api-docs/css/code-themes/idea.css +0 -121
- package/api-docs/css/code-themes/ir_black.css +0 -104
- package/api-docs/css/code-themes/magula.css +0 -121
- package/api-docs/css/code-themes/monokai.css +0 -114
- package/api-docs/css/code-themes/pojoaque.css +0 -104
- package/api-docs/css/code-themes/pojoaque.jpg +0 -0
- package/api-docs/css/code-themes/rainbow.css +0 -114
- package/api-docs/css/code-themes/school_book.css +0 -111
- package/api-docs/css/code-themes/school_book.png +0 -0
- package/api-docs/css/code-themes/sl-theme.css +0 -45
- package/api-docs/css/code-themes/solarized_dark.css +0 -88
- package/api-docs/css/code-themes/solarized_light.css +0 -88
- package/api-docs/css/code-themes/sunburst.css +0 -158
- package/api-docs/css/code-themes/tomorrow-night-blue.css +0 -52
- package/api-docs/css/code-themes/tomorrow-night-bright.css +0 -51
- package/api-docs/css/code-themes/tomorrow-night-eighties.css +0 -51
- package/api-docs/css/code-themes/tomorrow-night.css +0 -52
- package/api-docs/css/code-themes/tomorrow.css +0 -49
- package/api-docs/css/code-themes/vs.css +0 -86
- package/api-docs/css/code-themes/xcode.css +0 -154
- package/api-docs/css/code-themes/zenburn.css +0 -115
- package/api-docs/css/main.css +0 -139
- package/api-docs/favicon.ico +0 -0
- package/api-docs/fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/index.html +0 -7082
- package/api-docs/js/main.js +0 -19
- package/api-docs/js/vendor/bootstrap.min.js +0 -6
- package/api-docs/js/vendor/jquery-1.10.1.min.js +0 -6
- package/api-docs/js/vendor/jquery.scrollTo-1.4.3.1.js +0 -218
- package/api-docs/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js +0 -11
- package/dist/src/http-handler.d.ts +0 -19
- package/dist/src/http-handler.js +0 -43
- package/dist/src/http-handler.js.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -33
- package/dist/src/index.js.map +0 -1
- package/dist/src/internal-types.d.ts +0 -67
- package/dist/src/internal-types.js.map +0 -1
- package/dist/src/keys.d.ts +0 -22
- package/dist/src/keys.js +0 -35
- package/dist/src/keys.js.map +0 -1
- package/dist/src/parser.d.ts +0 -11
- package/dist/src/parser.js +0 -98
- package/dist/src/parser.js.map +0 -1
- package/dist/src/providers/bind-element.d.ts +0 -7
- package/dist/src/providers/bind-element.js +0 -34
- package/dist/src/providers/bind-element.js.map +0 -1
- package/dist/src/providers/find-route.d.ts +0 -9
- package/dist/src/providers/find-route.js +0 -42
- package/dist/src/providers/find-route.js.map +0 -1
- package/dist/src/providers/get-from-context.d.ts +0 -7
- package/dist/src/providers/get-from-context.js +0 -34
- package/dist/src/providers/get-from-context.js.map +0 -1
- package/dist/src/providers/index.d.ts +0 -8
- package/dist/src/providers/index.js +0 -18
- package/dist/src/providers/index.js.map +0 -1
- package/dist/src/providers/invoke-method.js +0 -36
- package/dist/src/providers/invoke-method.js.map +0 -1
- package/dist/src/providers/log-error-provider.d.ts +0 -6
- package/dist/src/providers/log-error-provider.js +0 -17
- package/dist/src/providers/log-error-provider.js.map +0 -1
- package/dist/src/providers/parse-params.d.ts +0 -13
- package/dist/src/providers/parse-params.js +0 -22
- package/dist/src/providers/parse-params.js.map +0 -1
- package/dist/src/providers/reject.d.ts +0 -6
- package/dist/src/providers/reject.js +0 -40
- package/dist/src/providers/reject.js.map +0 -1
- package/dist/src/providers/send.js.map +0 -1
- package/dist/src/rest-component.d.ts +0 -12
- package/dist/src/rest-component.js +0 -50
- package/dist/src/rest-component.js.map +0 -1
- package/dist/src/rest-server.d.ts +0 -211
- package/dist/src/rest-server.js +0 -426
- package/dist/src/rest-server.js.map +0 -1
- package/dist/src/router/metadata.d.ts +0 -150
- package/dist/src/router/metadata.js +0 -410
- package/dist/src/router/metadata.js.map +0 -1
- package/dist/src/router/routing-table.d.ts +0 -68
- package/dist/src/router/routing-table.js +0 -204
- package/dist/src/router/routing-table.js.map +0 -1
- package/dist/src/sequence.d.ts +0 -81
- package/dist/src/sequence.js +0 -104
- package/dist/src/sequence.js.map +0 -1
- package/dist/src/writer.d.ts +0 -17
- package/dist/src/writer.js +0 -87
- package/dist/src/writer.js.map +0 -1
- package/dist6/index.d.ts +0 -1
- package/dist6/index.js +0 -12
- package/dist6/src/http-handler.d.ts +0 -19
- package/dist6/src/http-handler.js +0 -53
- package/dist6/src/http-handler.js.map +0 -1
- package/dist6/src/index.d.ts +0 -14
- package/dist6/src/index.js +0 -33
- package/dist6/src/index.js.map +0 -1
- package/dist6/src/internal-types.d.ts +0 -67
- package/dist6/src/internal-types.js +0 -7
- package/dist6/src/internal-types.js.map +0 -1
- package/dist6/src/keys.d.ts +0 -22
- package/dist6/src/keys.js +0 -35
- package/dist6/src/keys.js.map +0 -1
- package/dist6/src/parser.d.ts +0 -11
- package/dist6/src/parser.js +0 -108
- package/dist6/src/parser.js.map +0 -1
- package/dist6/src/providers/bind-element.d.ts +0 -7
- package/dist6/src/providers/bind-element.js +0 -34
- package/dist6/src/providers/bind-element.js.map +0 -1
- package/dist6/src/providers/find-route.js +0 -42
- package/dist6/src/providers/find-route.js.map +0 -1
- package/dist6/src/providers/get-from-context.d.ts +0 -7
- package/dist6/src/providers/get-from-context.js +0 -34
- package/dist6/src/providers/get-from-context.js.map +0 -1
- package/dist6/src/providers/index.d.ts +0 -8
- package/dist6/src/providers/index.js +0 -18
- package/dist6/src/providers/index.js.map +0 -1
- package/dist6/src/providers/invoke-method.d.ts +0 -7
- package/dist6/src/providers/invoke-method.js +0 -44
- package/dist6/src/providers/invoke-method.js.map +0 -1
- package/dist6/src/providers/log-error-provider.d.ts +0 -6
- package/dist6/src/providers/log-error-provider.js +0 -17
- package/dist6/src/providers/log-error-provider.js.map +0 -1
- package/dist6/src/providers/parse-params.d.ts +0 -13
- package/dist6/src/providers/parse-params.js +0 -22
- package/dist6/src/providers/parse-params.js.map +0 -1
- package/dist6/src/providers/reject.d.ts +0 -6
- package/dist6/src/providers/reject.js +0 -40
- package/dist6/src/providers/reject.js.map +0 -1
- package/dist6/src/providers/send.d.ts +0 -15
- package/dist6/src/providers/send.js +0 -24
- package/dist6/src/providers/send.js.map +0 -1
- package/dist6/src/rest-component.d.ts +0 -12
- package/dist6/src/rest-component.js +0 -50
- package/dist6/src/rest-component.js.map +0 -1
- package/dist6/src/rest-server.d.ts +0 -211
- package/dist6/src/rest-server.js +0 -444
- package/dist6/src/rest-server.js.map +0 -1
- package/dist6/src/router/metadata.d.ts +0 -150
- package/dist6/src/router/metadata.js +0 -410
- package/dist6/src/router/metadata.js.map +0 -1
- package/dist6/src/router/routing-table.d.ts +0 -68
- package/dist6/src/router/routing-table.js +0 -218
- package/dist6/src/router/routing-table.js.map +0 -1
- package/dist6/src/sequence.js +0 -114
- package/dist6/src/sequence.js.map +0 -1
- package/dist6/src/writer.d.ts +0 -17
- package/dist6/src/writer.js +0 -87
- package/dist6/src/writer.js.map +0 -1
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/src/internal-types.ts +0 -96
- package/src/providers/bind-element.ts +0 -15
- package/src/providers/get-from-context.ts +0 -16
- package/src/providers/log-error-provider.ts +0 -23
- package/src/providers/parse-params.ts +0 -20
- package/src/providers/reject.ts +0 -27
- package/src/rest-component.ts +0 -54
- package/src/rest-server.ts +0 -584
- package/src/router/metadata.ts +0 -517
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {inject} from '@loopback/context';
|
|
7
|
+
import {text} from 'body-parser';
|
|
8
|
+
import {is} from 'type-is';
|
|
9
|
+
import {RestBindings} from '../keys';
|
|
10
|
+
import {Request, RequestBodyParserOptions} from '../types';
|
|
11
|
+
import {
|
|
12
|
+
BodyParserMiddleware,
|
|
13
|
+
getParserOptions,
|
|
14
|
+
invokeBodyParserMiddleware,
|
|
15
|
+
builtinParsers,
|
|
16
|
+
} from './body-parser.helpers';
|
|
17
|
+
import {BodyParser, RequestBody} from './types';
|
|
18
|
+
|
|
19
|
+
export class TextBodyParser implements BodyParser {
|
|
20
|
+
name = builtinParsers.text;
|
|
21
|
+
private textParser: BodyParserMiddleware;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
@inject(RestBindings.REQUEST_BODY_PARSER_OPTIONS, {optional: true})
|
|
25
|
+
options: RequestBodyParserOptions = {},
|
|
26
|
+
) {
|
|
27
|
+
const textOptions = Object.assign(
|
|
28
|
+
{type: 'text/*'},
|
|
29
|
+
getParserOptions('text', options),
|
|
30
|
+
);
|
|
31
|
+
this.textParser = text(textOptions);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
supports(mediaType: string) {
|
|
35
|
+
// Please note that `text/*` matches `text/plain` and `text/html` but`text`
|
|
36
|
+
// does not.
|
|
37
|
+
return !!is(mediaType, 'text/*');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async parse(request: Request): Promise<RequestBody> {
|
|
41
|
+
const body = await invokeBodyParserMiddleware(this.textParser, request);
|
|
42
|
+
return {value: body};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
compareByOrder,
|
|
8
|
+
Constructor,
|
|
9
|
+
Context,
|
|
10
|
+
filterByTag,
|
|
11
|
+
inject,
|
|
12
|
+
instantiateClass,
|
|
13
|
+
} from '@loopback/context';
|
|
14
|
+
import {isReferenceObject, OperationObject} from '@loopback/openapi-v3';
|
|
15
|
+
import debugModule from 'debug';
|
|
16
|
+
import {is} from 'type-is';
|
|
17
|
+
import {RestHttpErrors} from '../rest-http-error';
|
|
18
|
+
import {Request} from '../types';
|
|
19
|
+
import {
|
|
20
|
+
builtinParsers,
|
|
21
|
+
getContentType,
|
|
22
|
+
normalizeParsingError,
|
|
23
|
+
} from './body-parser.helpers';
|
|
24
|
+
import {
|
|
25
|
+
BodyParser,
|
|
26
|
+
BodyParserFunction,
|
|
27
|
+
RequestBody,
|
|
28
|
+
REQUEST_BODY_PARSER_TAG,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
const debug = debugModule('loopback:rest:body-parser');
|
|
32
|
+
|
|
33
|
+
export class RequestBodyParser {
|
|
34
|
+
readonly parsers: BodyParser[];
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
@inject(filterByTag(REQUEST_BODY_PARSER_TAG), {optional: true})
|
|
38
|
+
parsers?: BodyParser[],
|
|
39
|
+
@inject.context() private readonly ctx?: Context,
|
|
40
|
+
) {
|
|
41
|
+
this.parsers = sortParsers(parsers ?? []);
|
|
42
|
+
if (debug.enabled) {
|
|
43
|
+
debug(
|
|
44
|
+
'Body parsers: ',
|
|
45
|
+
this.parsers.map(p => p.name),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async loadRequestBodyIfNeeded(
|
|
51
|
+
operationSpec: OperationObject,
|
|
52
|
+
request: Request,
|
|
53
|
+
): Promise<RequestBody> {
|
|
54
|
+
const {requestBody, customParser} = await this._matchRequestBodySpec(
|
|
55
|
+
operationSpec,
|
|
56
|
+
request,
|
|
57
|
+
);
|
|
58
|
+
if (!operationSpec.requestBody) return requestBody;
|
|
59
|
+
const matchedMediaType = requestBody.mediaType!;
|
|
60
|
+
try {
|
|
61
|
+
if (customParser) {
|
|
62
|
+
// Invoke the custom parser
|
|
63
|
+
const body = await this._invokeCustomParser(customParser, request);
|
|
64
|
+
return Object.assign(requestBody, body);
|
|
65
|
+
} else {
|
|
66
|
+
const parser = this._findParser(matchedMediaType);
|
|
67
|
+
if (parser) {
|
|
68
|
+
const body = await parser.parse(request);
|
|
69
|
+
return Object.assign(requestBody, body);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
throw normalizeParsingError(err);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw RestHttpErrors.unsupportedMediaType(matchedMediaType);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Match the http request to a given media type of the request body spec
|
|
81
|
+
*/
|
|
82
|
+
private async _matchRequestBodySpec(
|
|
83
|
+
operationSpec: OperationObject,
|
|
84
|
+
request: Request,
|
|
85
|
+
) {
|
|
86
|
+
const requestBody: RequestBody = {
|
|
87
|
+
value: undefined,
|
|
88
|
+
};
|
|
89
|
+
if (!operationSpec.requestBody) return {requestBody};
|
|
90
|
+
|
|
91
|
+
const contentType = getContentType(request) ?? 'application/json';
|
|
92
|
+
debug('Loading request body with content type %j', contentType);
|
|
93
|
+
|
|
94
|
+
// the type of `operationSpec.requestBody` could be `RequestBodyObject`
|
|
95
|
+
// or `ReferenceObject`, resolving a `$ref` value is not supported yet.
|
|
96
|
+
if (isReferenceObject(operationSpec.requestBody)) {
|
|
97
|
+
throw new Error('$ref requestBody is not supported yet.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let content = operationSpec.requestBody.content || {};
|
|
101
|
+
if (!Object.keys(content).length) {
|
|
102
|
+
content = {
|
|
103
|
+
// default to allow json and urlencoded
|
|
104
|
+
'application/json': {schema: {type: 'object'}},
|
|
105
|
+
'application/x-www-form-urlencoded': {schema: {type: 'object'}},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check of the request content type matches one of the expected media
|
|
110
|
+
// types in the request body spec
|
|
111
|
+
let matchedMediaType: string | false = false;
|
|
112
|
+
let customParser = undefined;
|
|
113
|
+
for (const type in content) {
|
|
114
|
+
matchedMediaType = is(contentType, type);
|
|
115
|
+
if (matchedMediaType) {
|
|
116
|
+
debug('Matched media type: %s -> %s', type, contentType);
|
|
117
|
+
requestBody.mediaType = contentType;
|
|
118
|
+
requestBody.schema = content[type].schema;
|
|
119
|
+
customParser = content[type]['x-parser'];
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!matchedMediaType) {
|
|
125
|
+
// No matching media type found, fail fast
|
|
126
|
+
throw RestHttpErrors.unsupportedMediaType(
|
|
127
|
+
contentType,
|
|
128
|
+
Object.keys(content),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {requestBody, customParser};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Find a body parser that supports the media type
|
|
137
|
+
* @param matchedMediaType - Media type
|
|
138
|
+
*/
|
|
139
|
+
private _findParser(matchedMediaType: string) {
|
|
140
|
+
for (const parser of this.parsers) {
|
|
141
|
+
if (!parser.supports(matchedMediaType)) {
|
|
142
|
+
debug(
|
|
143
|
+
'Body parser %s does not support %s',
|
|
144
|
+
parser.name,
|
|
145
|
+
matchedMediaType,
|
|
146
|
+
);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
debug('Body parser %s found for %s', parser.name, matchedMediaType);
|
|
150
|
+
return parser;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve and invoke a custom parser
|
|
156
|
+
* @param customParser - The parser name, class or function
|
|
157
|
+
* @param request - Http request
|
|
158
|
+
*/
|
|
159
|
+
private async _invokeCustomParser(
|
|
160
|
+
customParser: string | Constructor<BodyParser> | BodyParserFunction,
|
|
161
|
+
request: Request,
|
|
162
|
+
) {
|
|
163
|
+
if (typeof customParser === 'string') {
|
|
164
|
+
const parser = this.parsers.find(
|
|
165
|
+
p =>
|
|
166
|
+
p.name === customParser ||
|
|
167
|
+
p.name === builtinParsers.mapping[customParser],
|
|
168
|
+
);
|
|
169
|
+
if (parser) {
|
|
170
|
+
debug('Using custom parser %s', customParser);
|
|
171
|
+
return parser.parse(request);
|
|
172
|
+
}
|
|
173
|
+
} else if (typeof customParser === 'function') {
|
|
174
|
+
if (isBodyParserClass(customParser)) {
|
|
175
|
+
debug('Using custom parser class %s', customParser.name);
|
|
176
|
+
const parser = await instantiateClass<BodyParser>(
|
|
177
|
+
customParser as Constructor<BodyParser>,
|
|
178
|
+
this.ctx!,
|
|
179
|
+
);
|
|
180
|
+
return parser.parse(request);
|
|
181
|
+
} else {
|
|
182
|
+
debug('Using custom parser function %s', customParser.name);
|
|
183
|
+
return customParser(request);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
throw new Error('Custom parser not found: ' + customParser);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Test if a function is a body parser class or plain function
|
|
192
|
+
* @param fn
|
|
193
|
+
*/
|
|
194
|
+
function isBodyParserClass(
|
|
195
|
+
fn: Constructor<BodyParser> | BodyParserFunction,
|
|
196
|
+
): fn is Constructor<BodyParser> {
|
|
197
|
+
return fn.toString().startsWith('class ');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Sort body parsers so that built-in ones are used after extensions
|
|
202
|
+
* @param parsers
|
|
203
|
+
*/
|
|
204
|
+
function sortParsers(parsers: BodyParser[]) {
|
|
205
|
+
return parsers.sort((a, b) =>
|
|
206
|
+
compareByOrder(a.name, b.name, builtinParsers.names),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {inject} from '@loopback/context';
|
|
7
|
+
import {urlencoded} from 'body-parser';
|
|
8
|
+
import {is} from 'type-is';
|
|
9
|
+
import {RestBindings} from '../keys';
|
|
10
|
+
import {Request, RequestBodyParserOptions} from '../types';
|
|
11
|
+
import {
|
|
12
|
+
BodyParserMiddleware,
|
|
13
|
+
getParserOptions,
|
|
14
|
+
invokeBodyParserMiddleware,
|
|
15
|
+
builtinParsers,
|
|
16
|
+
} from './body-parser.helpers';
|
|
17
|
+
import {BodyParser, RequestBody} from './types';
|
|
18
|
+
|
|
19
|
+
export class UrlEncodedBodyParser implements BodyParser {
|
|
20
|
+
name = builtinParsers.urlencoded;
|
|
21
|
+
private urlencodedParser: BodyParserMiddleware;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
@inject(RestBindings.REQUEST_BODY_PARSER_OPTIONS, {optional: true})
|
|
25
|
+
options: RequestBodyParserOptions = {},
|
|
26
|
+
) {
|
|
27
|
+
const urlencodedOptions = getParserOptions('urlencoded', options);
|
|
28
|
+
this.urlencodedParser = urlencoded(urlencodedOptions);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
supports(mediaType: string) {
|
|
32
|
+
return !!is(mediaType, 'urlencoded');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async parse(request: Request): Promise<RequestBody> {
|
|
36
|
+
const body = await invokeBodyParserMiddleware(
|
|
37
|
+
this.urlencodedParser,
|
|
38
|
+
request,
|
|
39
|
+
);
|
|
40
|
+
return {value: body, coercionRequired: true};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
export * from './body-parser';
|
|
7
|
+
export * from './body-parser.helpers';
|
|
8
|
+
export * from './body-parser.json';
|
|
9
|
+
export * from './body-parser.raw';
|
|
10
|
+
export * from './body-parser.stream';
|
|
11
|
+
export * from './body-parser.text';
|
|
12
|
+
export * from './body-parser.urlencoded';
|
|
13
|
+
export * from './types';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {ReferenceObject, SchemaObject} from '@loopback/openapi-v3';
|
|
7
|
+
import {Request} from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Request body with metadata
|
|
10
|
+
*/
|
|
11
|
+
export type RequestBody = {
|
|
12
|
+
/**
|
|
13
|
+
* Parsed value of the request body
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
value: any | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Is coercion required? Some forms of request such as urlencoded don't
|
|
19
|
+
* have rich types such as number or boolean.
|
|
20
|
+
*/
|
|
21
|
+
coercionRequired?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Resolved media type
|
|
24
|
+
*/
|
|
25
|
+
mediaType?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Corresponding schema for the request body
|
|
28
|
+
*/
|
|
29
|
+
schema?: SchemaObject | ReferenceObject;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Interface to be implemented by body parser extensions
|
|
34
|
+
*/
|
|
35
|
+
export interface BodyParser {
|
|
36
|
+
/**
|
|
37
|
+
* Name of the parser
|
|
38
|
+
*/
|
|
39
|
+
name: string | symbol;
|
|
40
|
+
/**
|
|
41
|
+
* Indicate if the given media type is supported
|
|
42
|
+
* @param mediaType - Media type
|
|
43
|
+
*/
|
|
44
|
+
supports(mediaType: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Parse the request body
|
|
47
|
+
* @param request - http request
|
|
48
|
+
*/
|
|
49
|
+
parse(request: Request): Promise<RequestBody>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Plain function for body parsing
|
|
54
|
+
*/
|
|
55
|
+
export type BodyParserFunction = (request: Request) => Promise<RequestBody>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Binding tag for request body parser extensions
|
|
59
|
+
*/
|
|
60
|
+
export const REQUEST_BODY_PARSER_TAG = 'rest.requestBodyParser';
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {isReferenceObject, ParameterObject} from '@loopback/openapi-v3';
|
|
7
|
+
import debugModule from 'debug';
|
|
8
|
+
import {RestHttpErrors} from '../';
|
|
9
|
+
import {parseJson} from '../parse-json';
|
|
10
|
+
import {
|
|
11
|
+
DateCoercionOptions,
|
|
12
|
+
getOAIPrimitiveType,
|
|
13
|
+
IntegerCoercionOptions,
|
|
14
|
+
isEmpty,
|
|
15
|
+
isFalse,
|
|
16
|
+
isTrue,
|
|
17
|
+
isValidDateTime,
|
|
18
|
+
matchDateFormat,
|
|
19
|
+
} from './utils';
|
|
20
|
+
import {Validator} from './validator';
|
|
21
|
+
const isRFC3339 = require('validator/lib/isRFC3339');
|
|
22
|
+
const debug = debugModule('loopback:rest:coercion');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Coerce the http raw data to a JavaScript type data of a parameter
|
|
26
|
+
* according to its OpenAPI schema specification.
|
|
27
|
+
*
|
|
28
|
+
* @param data - The raw data get from http request
|
|
29
|
+
* @param schema - The parameter's schema defined in OpenAPI specification
|
|
30
|
+
*/
|
|
31
|
+
export function coerceParameter(
|
|
32
|
+
data: string | undefined | object,
|
|
33
|
+
spec: ParameterObject,
|
|
34
|
+
) {
|
|
35
|
+
let schema = spec.schema;
|
|
36
|
+
|
|
37
|
+
// If a query parameter is a url encoded Json object, the schema is defined under content['application/json']
|
|
38
|
+
if (!schema && spec.in === 'query' && spec.content) {
|
|
39
|
+
const jsonSpec = spec.content['application/json'];
|
|
40
|
+
schema = jsonSpec.schema;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!schema || isReferenceObject(schema)) {
|
|
44
|
+
debug(
|
|
45
|
+
'The parameter with schema %s is not coerced since schema' +
|
|
46
|
+
'dereference is not supported yet.',
|
|
47
|
+
schema,
|
|
48
|
+
);
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
const OAIType = getOAIPrimitiveType(schema.type, schema.format);
|
|
52
|
+
const validator = new Validator({parameterSpec: spec});
|
|
53
|
+
|
|
54
|
+
validator.validateParamBeforeCoercion(data);
|
|
55
|
+
if (data === undefined) return data;
|
|
56
|
+
|
|
57
|
+
switch (OAIType) {
|
|
58
|
+
case 'byte':
|
|
59
|
+
return coerceBuffer(data, spec);
|
|
60
|
+
case 'date':
|
|
61
|
+
return coerceDatetime(data, spec, {dateOnly: true});
|
|
62
|
+
case 'date-time':
|
|
63
|
+
return coerceDatetime(data, spec);
|
|
64
|
+
case 'float':
|
|
65
|
+
case 'double':
|
|
66
|
+
case 'number':
|
|
67
|
+
return coerceNumber(data, spec);
|
|
68
|
+
case 'long':
|
|
69
|
+
return coerceInteger(data, spec, {isLong: true});
|
|
70
|
+
case 'integer':
|
|
71
|
+
return coerceInteger(data, spec);
|
|
72
|
+
case 'boolean':
|
|
73
|
+
return coerceBoolean(data, spec);
|
|
74
|
+
case 'object':
|
|
75
|
+
return coerceObject(data, spec);
|
|
76
|
+
case 'string':
|
|
77
|
+
case 'password':
|
|
78
|
+
return coerceString(data, spec);
|
|
79
|
+
default:
|
|
80
|
+
return data;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function coerceString(data: string | object, spec: ParameterObject) {
|
|
85
|
+
if (typeof data !== 'string')
|
|
86
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
87
|
+
|
|
88
|
+
debug('data of type string is coerced to %s', data);
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function coerceBuffer(data: string | object, spec: ParameterObject) {
|
|
93
|
+
if (typeof data === 'object')
|
|
94
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
95
|
+
return Buffer.from(data, 'base64');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function coerceDatetime(
|
|
99
|
+
data: string | object,
|
|
100
|
+
spec: ParameterObject,
|
|
101
|
+
options?: DateCoercionOptions,
|
|
102
|
+
) {
|
|
103
|
+
if (typeof data === 'object' || isEmpty(data))
|
|
104
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
105
|
+
|
|
106
|
+
if (options?.dateOnly) {
|
|
107
|
+
if (!matchDateFormat(data))
|
|
108
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
109
|
+
} else {
|
|
110
|
+
if (!isRFC3339(data)) throw RestHttpErrors.invalidData(data, spec.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const coercedDate = new Date(data);
|
|
114
|
+
if (!isValidDateTime(coercedDate))
|
|
115
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
116
|
+
return coercedDate;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function coerceNumber(data: string | object, spec: ParameterObject) {
|
|
120
|
+
if (typeof data === 'object' || isEmpty(data))
|
|
121
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
122
|
+
|
|
123
|
+
const coercedNum = Number(data);
|
|
124
|
+
if (isNaN(coercedNum)) throw RestHttpErrors.invalidData(data, spec.name);
|
|
125
|
+
|
|
126
|
+
debug('data of type number is coerced to %s', coercedNum);
|
|
127
|
+
return coercedNum;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function coerceInteger(
|
|
131
|
+
data: string | object,
|
|
132
|
+
spec: ParameterObject,
|
|
133
|
+
options?: IntegerCoercionOptions,
|
|
134
|
+
) {
|
|
135
|
+
if (typeof data === 'object' || isEmpty(data))
|
|
136
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
137
|
+
|
|
138
|
+
const coercedInt = Number(data);
|
|
139
|
+
if (isNaN(coercedInt!)) throw RestHttpErrors.invalidData(data, spec.name);
|
|
140
|
+
|
|
141
|
+
if (options?.isLong) {
|
|
142
|
+
if (!Number.isInteger(coercedInt))
|
|
143
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
144
|
+
} else {
|
|
145
|
+
if (!Number.isSafeInteger(coercedInt))
|
|
146
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
debug('data of type integer is coerced to %s', coercedInt);
|
|
150
|
+
return coercedInt;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function coerceBoolean(data: string | object, spec: ParameterObject) {
|
|
154
|
+
if (typeof data === 'object' || isEmpty(data))
|
|
155
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
156
|
+
if (isTrue(data)) return true;
|
|
157
|
+
if (isFalse(data)) return false;
|
|
158
|
+
throw RestHttpErrors.invalidData(data, spec.name);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function coerceObject(input: string | object, spec: ParameterObject) {
|
|
162
|
+
const data = parseJsonIfNeeded(input, spec);
|
|
163
|
+
|
|
164
|
+
if (data === undefined) {
|
|
165
|
+
// Skip any further checks and coercions, nothing we can do with `undefined`
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (typeof data !== 'object' || Array.isArray(data))
|
|
170
|
+
throw RestHttpErrors.invalidData(input, spec.name);
|
|
171
|
+
|
|
172
|
+
// TODO(bajtos) apply coercion based on properties defined by spec.schema
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseJsonIfNeeded(
|
|
177
|
+
data: string | object,
|
|
178
|
+
spec: ParameterObject,
|
|
179
|
+
): string | object | undefined {
|
|
180
|
+
if (typeof data !== 'string') return data;
|
|
181
|
+
|
|
182
|
+
if (spec.in !== 'query' || (spec.in === 'query' && !spec.content)) {
|
|
183
|
+
debug(
|
|
184
|
+
'Skipping JSON.parse, argument %s is not a url encoded json object query parameter (since content field is missing in parameter schema)',
|
|
185
|
+
spec.name,
|
|
186
|
+
);
|
|
187
|
+
return data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (data === '') {
|
|
191
|
+
debug('Converted empty string to object value `undefined`');
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const result = parseJson(data);
|
|
197
|
+
debug('Parsed parameter %s as %j', spec.name, result);
|
|
198
|
+
return result;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
debug('Cannot parse %s value %j as JSON: %s', spec.name, data, err.message);
|
|
201
|
+
throw RestHttpErrors.invalidData(data, spec.name, {
|
|
202
|
+
details: {
|
|
203
|
+
syntaxError: err.message,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import debugModule from 'debug';
|
|
7
|
+
const debug = debugModule('loopback:rest:coercion');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for function coerceDatetime
|
|
11
|
+
*/
|
|
12
|
+
export type DateCoercionOptions = {
|
|
13
|
+
dateOnly?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for function coerceInteger
|
|
18
|
+
*/
|
|
19
|
+
export type IntegerCoercionOptions = {
|
|
20
|
+
isLong?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function isEmpty(data: string) {
|
|
24
|
+
const result = data === '';
|
|
25
|
+
debug('isEmpty(%j) -> %s', data, result);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A set of truthy values. A data in this set will be coerced to `true`.
|
|
30
|
+
*
|
|
31
|
+
* @param data - The raw data get from http request
|
|
32
|
+
* @returns The corresponding coerced boolean type
|
|
33
|
+
*/
|
|
34
|
+
export function isTrue(data: string): boolean {
|
|
35
|
+
return ['TRUE', '1'].includes(data.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A set of falsy values. A data in this set will be coerced to `false`.
|
|
40
|
+
* @param data - The raw data get from http request
|
|
41
|
+
* @returns The corresponding coerced boolean type
|
|
42
|
+
*/
|
|
43
|
+
export function isFalse(data: string): boolean {
|
|
44
|
+
return ['FALSE', '0'].includes(data.toUpperCase());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Return false for invalid date
|
|
49
|
+
*/
|
|
50
|
+
export function isValidDateTime(data: Date) {
|
|
51
|
+
return isNaN(data.getTime()) ? false : true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const REGEX_RFC3339_DATE = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])$/;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Return true when a date follows the RFC3339 standard
|
|
58
|
+
*
|
|
59
|
+
* @param date - The date to verify
|
|
60
|
+
*/
|
|
61
|
+
export function matchDateFormat(date: string) {
|
|
62
|
+
const pattern = new RegExp(REGEX_RFC3339_DATE);
|
|
63
|
+
const result = pattern.test(date);
|
|
64
|
+
debug('matchDateFormat(%j) -> %s', date, result);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Return the corresponding OpenAPI data type given an OpenAPI schema
|
|
70
|
+
*
|
|
71
|
+
* @param type - The type in an OpenAPI schema specification
|
|
72
|
+
* @param format - The format in an OpenAPI schema specification
|
|
73
|
+
*/
|
|
74
|
+
export function getOAIPrimitiveType(type?: string, format?: string) {
|
|
75
|
+
if (type === 'object' || type === 'array') return type;
|
|
76
|
+
if (type === 'string') {
|
|
77
|
+
switch (format) {
|
|
78
|
+
case 'byte':
|
|
79
|
+
return 'byte';
|
|
80
|
+
case 'binary':
|
|
81
|
+
return 'binary';
|
|
82
|
+
case 'date':
|
|
83
|
+
return 'date';
|
|
84
|
+
case 'date-time':
|
|
85
|
+
return 'date-time';
|
|
86
|
+
case 'password':
|
|
87
|
+
return 'password';
|
|
88
|
+
default:
|
|
89
|
+
return 'string';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (type === 'boolean') return 'boolean';
|
|
93
|
+
if (type === 'number')
|
|
94
|
+
switch (format) {
|
|
95
|
+
case 'float':
|
|
96
|
+
return 'float';
|
|
97
|
+
case 'double':
|
|
98
|
+
return 'double';
|
|
99
|
+
default:
|
|
100
|
+
return 'number';
|
|
101
|
+
}
|
|
102
|
+
if (type === 'integer') return format === 'int64' ? 'long' : 'integer';
|
|
103
|
+
}
|