@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
|
@@ -1,108 +1,88 @@
|
|
|
1
|
-
// Copyright IBM Corp. 2017. All Rights Reserved.
|
|
1
|
+
// Copyright IBM Corp. 2017,2020. All Rights Reserved.
|
|
2
2
|
// Node module: @loopback/rest
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
ControllerSpec,
|
|
7
8
|
OperationObject,
|
|
8
9
|
ParameterObject,
|
|
9
|
-
|
|
10
|
-
} from '@loopback/openapi-
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
10
|
+
PathObject,
|
|
11
|
+
} from '@loopback/openapi-v3';
|
|
12
|
+
import debugFactory from 'debug';
|
|
13
|
+
import HttpErrors from 'http-errors';
|
|
14
|
+
import {Request} from '../types';
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from '
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
const debug =
|
|
27
|
-
|
|
28
|
-
// TODO(bajtos) Refactor this code to use Trie-based lookup,
|
|
29
|
-
// e.g. via wayfarer/trie or find-my-way
|
|
30
|
-
// See https://github.com/strongloop/loopback-next/issues/98
|
|
31
|
-
import * as pathToRegexp from 'path-to-regexp';
|
|
16
|
+
ControllerClass,
|
|
17
|
+
ControllerFactory,
|
|
18
|
+
createRoutesForController,
|
|
19
|
+
} from './controller-route';
|
|
20
|
+
import {ExternalExpressRoutes} from './external-express-routes';
|
|
21
|
+
import {validateApiPath} from './openapi-path';
|
|
22
|
+
import {RestRouter} from './rest-router';
|
|
23
|
+
import {ResolvedRoute, RouteEntry} from './route-entry';
|
|
24
|
+
import {TrieRouter} from './trie-router';
|
|
25
|
+
|
|
26
|
+
const debug = debugFactory('loopback:rest:routing-table');
|
|
32
27
|
|
|
33
28
|
/**
|
|
34
|
-
*
|
|
35
|
-
* on this request object:
|
|
36
|
-
* - `path`
|
|
37
|
-
* - `query`
|
|
38
|
-
*
|
|
39
|
-
* @private
|
|
40
|
-
* @param request
|
|
29
|
+
* Routing table
|
|
41
30
|
*/
|
|
42
|
-
export function parseRequestUrl(request: ServerRequest): ParsedRequest {
|
|
43
|
-
// TODO(bajtos) The following parsing can be skipped when the router
|
|
44
|
-
// is mounted on an express app
|
|
45
|
-
const parsedRequest = request as ParsedRequest;
|
|
46
|
-
const parsedUrl = url.parse(parsedRequest.url, true);
|
|
47
|
-
parsedRequest.path = parsedUrl.pathname || '/';
|
|
48
|
-
parsedRequest.query = parsedUrl.query;
|
|
49
|
-
return parsedRequest;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// tslint:disable-next-line:no-any
|
|
53
|
-
export type ControllerClass = Constructor<any>;
|
|
54
|
-
|
|
55
31
|
export class RoutingTable {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
32
|
+
constructor(
|
|
33
|
+
private readonly _router: RestRouter = new TrieRouter(),
|
|
34
|
+
private _externalRoutes?: ExternalExpressRoutes,
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Register a controller as the route
|
|
39
|
+
* @param spec
|
|
40
|
+
* @param controllerCtor
|
|
41
|
+
* @param controllerFactory
|
|
42
|
+
*/
|
|
43
|
+
registerController<T>(
|
|
44
|
+
spec: ControllerSpec,
|
|
45
|
+
controllerCtor: ControllerClass<T>,
|
|
46
|
+
controllerFactory?: ControllerFactory<T>,
|
|
47
|
+
) {
|
|
48
|
+
const routes = createRoutesForController(
|
|
49
|
+
spec,
|
|
50
|
+
controllerCtor,
|
|
51
|
+
controllerFactory,
|
|
62
52
|
);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
debug('Registering Controller with API', spec);
|
|
68
|
-
|
|
69
|
-
const basePath = spec.basePath || '/';
|
|
70
|
-
for (const p in spec.paths) {
|
|
71
|
-
for (const verb in spec.paths[p]) {
|
|
72
|
-
const opSpec: OperationObject = spec.paths[p][verb];
|
|
73
|
-
const fullPath = RoutingTable.joinPath(basePath, p);
|
|
74
|
-
const route = new ControllerRoute(verb, fullPath, opSpec, controller);
|
|
75
|
-
this.registerRoute(route);
|
|
76
|
-
}
|
|
53
|
+
for (const route of routes) {
|
|
54
|
+
this.registerRoute(route);
|
|
77
55
|
}
|
|
78
56
|
}
|
|
79
57
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.replace(/\/$/, '') // Remove trailing /
|
|
85
|
-
.replace(/^(\/)?/, '/'); // Add leading /
|
|
86
|
-
return fullPath;
|
|
87
|
-
}
|
|
88
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Register a route
|
|
60
|
+
* @param route - A route entry
|
|
61
|
+
*/
|
|
89
62
|
registerRoute(route: RouteEntry) {
|
|
90
63
|
// TODO(bajtos) handle the case where opSpec.parameters contains $ref
|
|
91
64
|
// See https://github.com/strongloop/loopback-next/issues/435
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
/* istanbul ignore if */
|
|
66
|
+
if (debug.enabled) {
|
|
67
|
+
debug(
|
|
68
|
+
'Registering route %s %s -> %s(%s)',
|
|
69
|
+
route.verb.toUpperCase(),
|
|
70
|
+
route.path,
|
|
71
|
+
route.describe(),
|
|
72
|
+
describeOperationParameters(route.spec),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
validateApiPath(route.path);
|
|
77
|
+
|
|
78
|
+
this._router.add(route);
|
|
100
79
|
}
|
|
101
80
|
|
|
102
|
-
describeApiPaths():
|
|
103
|
-
const paths:
|
|
81
|
+
describeApiPaths(): PathObject {
|
|
82
|
+
const paths: PathObject = {};
|
|
104
83
|
|
|
105
|
-
for (const route of this.
|
|
84
|
+
for (const route of this._router.list()) {
|
|
85
|
+
if (route.spec['x-visibility'] === 'undocumented') continue;
|
|
106
86
|
if (!paths[route.path]) {
|
|
107
87
|
paths[route.path] = {};
|
|
108
88
|
}
|
|
@@ -113,215 +93,39 @@ export class RoutingTable {
|
|
|
113
93
|
return paths;
|
|
114
94
|
}
|
|
115
95
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Map a request to a route
|
|
98
|
+
* @param request
|
|
99
|
+
*/
|
|
100
|
+
find(request: Request): ResolvedRoute {
|
|
101
|
+
debug('Finding route for %s %s', request.method, request.path);
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
`Endpoint "${request.method} ${request.path}" not found.`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface RouteEntry {
|
|
129
|
-
readonly verb: string;
|
|
130
|
-
readonly path: string;
|
|
131
|
-
readonly spec: OperationObject;
|
|
103
|
+
const found = this._router.find(request);
|
|
132
104
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
invokeHandler(
|
|
137
|
-
requestContext: Context,
|
|
138
|
-
args: OperationArgs,
|
|
139
|
-
): Promise<OperationRetval>;
|
|
140
|
-
|
|
141
|
-
describe(): string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface ResolvedRoute extends RouteEntry {
|
|
145
|
-
readonly pathParams: PathParameterValues;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export abstract class BaseRoute implements RouteEntry {
|
|
149
|
-
public readonly verb: string;
|
|
150
|
-
private readonly _keys: pathToRegexp.Key[] = [];
|
|
151
|
-
private readonly _pathRegexp: RegExp;
|
|
152
|
-
|
|
153
|
-
constructor(
|
|
154
|
-
verb: string,
|
|
155
|
-
public readonly path: string,
|
|
156
|
-
public readonly spec: OperationObject,
|
|
157
|
-
) {
|
|
158
|
-
this.verb = verb.toLowerCase();
|
|
159
|
-
|
|
160
|
-
// In Swagger, path parameters are wrapped in `{}`.
|
|
161
|
-
// In Express.js, path parameters are prefixed with `:`
|
|
162
|
-
path = path.replace(/{([^}]*)}(\/|$)/g, ':$1$2');
|
|
163
|
-
this._pathRegexp = pathToRegexp(path, this._keys, {
|
|
164
|
-
strict: false,
|
|
165
|
-
end: true,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
match(request: ParsedRequest): ResolvedRoute | undefined {
|
|
170
|
-
debug('trying endpoint', this);
|
|
171
|
-
if (this.verb !== request.method!.toLowerCase()) {
|
|
172
|
-
debug(' -> verb mismatch');
|
|
173
|
-
return undefined;
|
|
105
|
+
if (found) {
|
|
106
|
+
debug('Route matched: %j', found);
|
|
107
|
+
return found;
|
|
174
108
|
}
|
|
175
109
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const pathParams = this._buildPathParams(match);
|
|
183
|
-
debug(' -> found with params: %j', pathParams);
|
|
184
|
-
|
|
185
|
-
return createResolvedRoute(this, pathParams);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
abstract updateBindings(requestContext: Context): void;
|
|
189
|
-
|
|
190
|
-
abstract invokeHandler(
|
|
191
|
-
requestContext: Context,
|
|
192
|
-
args: OperationArgs,
|
|
193
|
-
): Promise<OperationRetval>;
|
|
194
|
-
|
|
195
|
-
describe(): string {
|
|
196
|
-
return `"${this.verb} ${this.path}"`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private _buildPathParams(pathMatch: RegExpExecArray): PathParameterValues {
|
|
200
|
-
const pathParams = Object.create(null);
|
|
201
|
-
for (const ix in this._keys) {
|
|
202
|
-
const key = this._keys[ix];
|
|
203
|
-
const matchIndex = +ix + 1;
|
|
204
|
-
pathParams[key.name] = pathMatch[matchIndex];
|
|
205
|
-
}
|
|
206
|
-
return pathParams;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function createResolvedRoute(
|
|
211
|
-
route: RouteEntry,
|
|
212
|
-
pathParams: PathParameterValues,
|
|
213
|
-
): ResolvedRoute {
|
|
214
|
-
return Object.create(route, {
|
|
215
|
-
pathParams: {
|
|
216
|
-
writable: false,
|
|
217
|
-
value: pathParams,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export class Route extends BaseRoute {
|
|
223
|
-
constructor(
|
|
224
|
-
verb: string,
|
|
225
|
-
path: string,
|
|
226
|
-
public readonly spec: OperationObject,
|
|
227
|
-
protected readonly _handler: Function,
|
|
228
|
-
) {
|
|
229
|
-
super(verb, path, spec);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
describe(): string {
|
|
233
|
-
return this._handler.name || super.describe();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
updateBindings(requestContext: Context) {
|
|
237
|
-
// no-op
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async invokeHandler(
|
|
241
|
-
requestContext: Context,
|
|
242
|
-
args: OperationArgs,
|
|
243
|
-
): Promise<OperationRetval> {
|
|
244
|
-
return await this._handler(...args);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
type ControllerInstance = {[opName: string]: Function};
|
|
249
|
-
|
|
250
|
-
export class ControllerRoute extends BaseRoute {
|
|
251
|
-
protected readonly _methodName: string;
|
|
252
|
-
|
|
253
|
-
constructor(
|
|
254
|
-
verb: string,
|
|
255
|
-
path: string,
|
|
256
|
-
spec: OperationObject,
|
|
257
|
-
protected readonly _controllerCtor: ControllerClass,
|
|
258
|
-
methodName?: string,
|
|
259
|
-
) {
|
|
260
|
-
super(
|
|
261
|
-
verb,
|
|
262
|
-
path,
|
|
263
|
-
// Add x-controller-name and x-operation-name if not present
|
|
264
|
-
Object.assign(
|
|
265
|
-
{
|
|
266
|
-
'x-controller-name': _controllerCtor.name,
|
|
267
|
-
'x-operation-name': methodName,
|
|
268
|
-
},
|
|
269
|
-
spec,
|
|
270
|
-
),
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
if (!methodName) {
|
|
274
|
-
methodName = this.spec['x-operation-name'];
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (!methodName) {
|
|
278
|
-
throw new Error(
|
|
279
|
-
'methodName must be provided either via the ControllerRoute argument ' +
|
|
280
|
-
'or via "x-operation-name" extension field in OpenAPI spec. ' +
|
|
281
|
-
`Operation: "${verb} ${path}" ` +
|
|
282
|
-
`Controller: ${this._controllerCtor.name}.`,
|
|
110
|
+
if (this._externalRoutes) {
|
|
111
|
+
debug(
|
|
112
|
+
'No API route found for %s %s, trying to find an external Express route',
|
|
113
|
+
request.method,
|
|
114
|
+
request.path,
|
|
283
115
|
);
|
|
284
|
-
}
|
|
285
116
|
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
describe(): string {
|
|
290
|
-
return `${this._controllerCtor.name}.${this._methodName}`;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
updateBindings(requestContext: Context) {
|
|
294
|
-
const ctor = this._controllerCtor;
|
|
295
|
-
requestContext.bind('controller.current.ctor').to(ctor);
|
|
296
|
-
requestContext.bind('controller.current.operation').to(this._methodName);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async invokeHandler(
|
|
300
|
-
requestContext: Context,
|
|
301
|
-
args: OperationArgs,
|
|
302
|
-
): Promise<OperationRetval> {
|
|
303
|
-
const controller = await this._createControllerInstance(requestContext);
|
|
304
|
-
if (typeof controller[this._methodName] !== 'function') {
|
|
305
|
-
throw new HttpErrors.NotFound(
|
|
306
|
-
`Controller method not found: ${this.describe()}`,
|
|
307
|
-
);
|
|
117
|
+
return this._externalRoutes.find(request);
|
|
308
118
|
}
|
|
309
|
-
return await controller[this._methodName](...args);
|
|
310
|
-
}
|
|
311
119
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const valueOrPromise = instantiateClass(
|
|
316
|
-
this._controllerCtor,
|
|
317
|
-
requestContext,
|
|
120
|
+
debug('No route found for %s %s', request.method, request.path);
|
|
121
|
+
throw new HttpErrors.NotFound(
|
|
122
|
+
`Endpoint "${request.method} ${request.path}" not found.`,
|
|
318
123
|
);
|
|
319
|
-
return (await Promise.resolve(valueOrPromise)) as ControllerInstance;
|
|
320
124
|
}
|
|
321
125
|
}
|
|
322
126
|
|
|
323
127
|
function describeOperationParameters(opSpec: OperationObject) {
|
|
324
128
|
return ((opSpec.parameters as ParameterObject[]) || [])
|
|
325
|
-
.map(p => p
|
|
129
|
+
.map(p => p?.name || '')
|
|
326
130
|
.join(', ');
|
|
327
131
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 {inject} from '@loopback/context';
|
|
7
|
+
import {inspect} from 'util';
|
|
8
|
+
import {RestBindings} from '../keys';
|
|
9
|
+
import {RestRouterOptions} from './rest-router';
|
|
10
|
+
import {createResolvedRoute, ResolvedRoute, RouteEntry} from './route-entry';
|
|
11
|
+
import {BaseRouter} from './router-base';
|
|
12
|
+
import {Trie} from './trie';
|
|
13
|
+
|
|
14
|
+
const debug = require('debug')('loopback:rest:router:trie');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Router implementation based on trie
|
|
18
|
+
*/
|
|
19
|
+
export class TrieRouter extends BaseRouter {
|
|
20
|
+
private trie = new Trie<RouteEntry>();
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
@inject(RestBindings.ROUTER_OPTIONS, {optional: true})
|
|
24
|
+
options?: RestRouterOptions,
|
|
25
|
+
) {
|
|
26
|
+
super(options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected addRouteWithPathVars(route: RouteEntry) {
|
|
30
|
+
// Add the route to the trie
|
|
31
|
+
const key = this.getKeyForRoute(route);
|
|
32
|
+
this.trie.create(key, route);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected findRouteWithPathVars(
|
|
36
|
+
verb: string,
|
|
37
|
+
path: string,
|
|
38
|
+
): ResolvedRoute | undefined {
|
|
39
|
+
const key = this.getKey(verb, path);
|
|
40
|
+
|
|
41
|
+
const found = this.trie.match(key);
|
|
42
|
+
debug('Route matched: %j', found);
|
|
43
|
+
|
|
44
|
+
if (found) {
|
|
45
|
+
const route = found.node.value!;
|
|
46
|
+
if (route) {
|
|
47
|
+
debug('Route found: %s', inspect(route, {depth: 5}));
|
|
48
|
+
return createResolvedRoute(route, found.params ?? {});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected listRoutesWithPathVars() {
|
|
55
|
+
return this.trie.list().map(n => n.value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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 {Key, pathToRegexp} from 'path-to-regexp';
|
|
7
|
+
import {PathParameterValues} from '../types';
|
|
8
|
+
import {toExpressPath} from './openapi-path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A Node in the trie
|
|
12
|
+
*/
|
|
13
|
+
export interface Node<T> {
|
|
14
|
+
/**
|
|
15
|
+
* Key of the node
|
|
16
|
+
*/
|
|
17
|
+
key: string;
|
|
18
|
+
/**
|
|
19
|
+
* Value of the node
|
|
20
|
+
*/
|
|
21
|
+
value?: T;
|
|
22
|
+
/**
|
|
23
|
+
* Children of the node
|
|
24
|
+
*/
|
|
25
|
+
readonly children: {[key: string]: Node<T>};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Regular expression for the template
|
|
29
|
+
*/
|
|
30
|
+
regexp?: RegExp;
|
|
31
|
+
/**
|
|
32
|
+
* Names of the node if it contains named parameters
|
|
33
|
+
*/
|
|
34
|
+
names?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type NodeWithValue<T> = Node<T> & {value: T};
|
|
38
|
+
|
|
39
|
+
export interface ResolvedNode<T> {
|
|
40
|
+
node: Node<T>;
|
|
41
|
+
params?: PathParameterValues;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* An implementation of trie for routes. The key hierarchy is built with parts
|
|
46
|
+
* of the route path delimited by `/`
|
|
47
|
+
*/
|
|
48
|
+
export class Trie<T> {
|
|
49
|
+
readonly root: Node<T> = {key: '', children: {}};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a node for a given path template
|
|
53
|
+
* @param pathTemplate - The path template,
|
|
54
|
+
* @param value - Value of the route
|
|
55
|
+
*/
|
|
56
|
+
create(routeTemplate: string, value: T) {
|
|
57
|
+
const keys = routeTemplate.split('/').filter(Boolean);
|
|
58
|
+
return createNode(keys, 0, value, this.root);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Match a route path against the trie
|
|
63
|
+
* @param path - The route path, such as `/customers/c01`
|
|
64
|
+
*/
|
|
65
|
+
match(
|
|
66
|
+
path: string,
|
|
67
|
+
): (ResolvedNode<T> & {node: NodeWithValue<T>}) | undefined {
|
|
68
|
+
const keys = path.split('/').filter(Boolean);
|
|
69
|
+
const params = {};
|
|
70
|
+
const resolved = search(keys, 0, params, this.root);
|
|
71
|
+
if (resolved == null || !isNodeWithValue(resolved.node)) return undefined;
|
|
72
|
+
return {
|
|
73
|
+
node: resolved.node,
|
|
74
|
+
params: resolved.params,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* List all nodes with value of the trie
|
|
80
|
+
*/
|
|
81
|
+
list(): NodeWithValue<T>[] {
|
|
82
|
+
const nodes: NodeWithValue<T>[] = [];
|
|
83
|
+
traverse(this.root, node => {
|
|
84
|
+
nodes.push(node);
|
|
85
|
+
});
|
|
86
|
+
return nodes;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isNodeWithValue<T>(node: Node<T>): node is NodeWithValue<T> {
|
|
91
|
+
return node.value != null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Use depth-first preorder traversal to list all nodes with values
|
|
96
|
+
* @param root - Root node
|
|
97
|
+
* @param visitor - A function to process nodes with values
|
|
98
|
+
*/
|
|
99
|
+
function traverse<T>(root: Node<T>, visitor: (node: NodeWithValue<T>) => void) {
|
|
100
|
+
if (isNodeWithValue(root)) visitor(root);
|
|
101
|
+
for (const k in root.children) {
|
|
102
|
+
traverse(root.children[k], visitor);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Match the given key to one or more children of the parent node
|
|
108
|
+
* @param key - Key
|
|
109
|
+
* @param parent - Parent node
|
|
110
|
+
*/
|
|
111
|
+
function matchChildren<T>(key: string, parent: Node<T>): ResolvedNode<T>[] {
|
|
112
|
+
const resolvedNodes: ResolvedNode<T>[] = [];
|
|
113
|
+
// Match key literal first
|
|
114
|
+
let child = parent.children[key];
|
|
115
|
+
if (child) {
|
|
116
|
+
resolvedNodes.push({
|
|
117
|
+
node: child,
|
|
118
|
+
});
|
|
119
|
+
return resolvedNodes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Match templates
|
|
123
|
+
for (const k in parent.children) {
|
|
124
|
+
child = parent.children[k];
|
|
125
|
+
if (!child.names || !child.regexp) continue;
|
|
126
|
+
const match = child.regexp.exec(key);
|
|
127
|
+
if (match) {
|
|
128
|
+
const resolved: ResolvedNode<T> = {params: {}, node: child};
|
|
129
|
+
let i = 0;
|
|
130
|
+
for (const n of child.names) {
|
|
131
|
+
const val = match[++i];
|
|
132
|
+
resolved.params![n] = decodeURIComponent(val);
|
|
133
|
+
}
|
|
134
|
+
resolvedNodes.push(resolved);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return resolvedNodes;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Search a sub list of keys against the parent node
|
|
142
|
+
* @param keys - An array of keys
|
|
143
|
+
* @param index - Starting index of the key list
|
|
144
|
+
* @param params - An object to receive resolved parameter values
|
|
145
|
+
* @param parent - Parent node
|
|
146
|
+
*/
|
|
147
|
+
function search<T>(
|
|
148
|
+
keys: string[],
|
|
149
|
+
index: number,
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
+
params: {[name: string]: any},
|
|
152
|
+
parent: Node<T>,
|
|
153
|
+
): ResolvedNode<T> | undefined {
|
|
154
|
+
const key = keys[index];
|
|
155
|
+
const resolved: ResolvedNode<T> = {node: parent, params};
|
|
156
|
+
if (key === undefined) return resolved;
|
|
157
|
+
|
|
158
|
+
const children = matchChildren(key, parent);
|
|
159
|
+
if (children.length === 0) return undefined;
|
|
160
|
+
// There might be multiple matches, such as `/users/{id}` and `/users/{userId}`
|
|
161
|
+
for (const child of children) {
|
|
162
|
+
const result = search(keys, index + 1, params, child.node);
|
|
163
|
+
if (result && isNodeWithValue(result.node)) {
|
|
164
|
+
Object.assign(params, child.params);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// no matches found
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create a node for a sub list of keys against the parent node
|
|
174
|
+
* @param keys - An array of keys
|
|
175
|
+
* @param index - Starting index of the key list
|
|
176
|
+
* @param value - Value of the node
|
|
177
|
+
* @param parent - Parent node
|
|
178
|
+
*/
|
|
179
|
+
function createNode<T>(
|
|
180
|
+
keys: string[],
|
|
181
|
+
index: number,
|
|
182
|
+
value: T,
|
|
183
|
+
parent: Node<T>,
|
|
184
|
+
): Node<T> {
|
|
185
|
+
const key = keys[index];
|
|
186
|
+
if (key === undefined) return parent;
|
|
187
|
+
|
|
188
|
+
const isLast = keys.length - 1 === index;
|
|
189
|
+
let child = parent.children[key];
|
|
190
|
+
if (child != null) {
|
|
191
|
+
// Found an existing node
|
|
192
|
+
if (isLast) {
|
|
193
|
+
if (child.value == null) {
|
|
194
|
+
child.value = value;
|
|
195
|
+
} else {
|
|
196
|
+
if (child.value !== value) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
'Duplicate key found with different value: ' + keys.join('/'),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return createNode(keys, index + 1, value, child);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build a new node
|
|
208
|
+
*/
|
|
209
|
+
child = {
|
|
210
|
+
children: {},
|
|
211
|
+
key: key,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (isLast) {
|
|
215
|
+
child.value = value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if the key has variables such as `{var}`
|
|
219
|
+
const path = toExpressPath(key);
|
|
220
|
+
const params: Key[] = [];
|
|
221
|
+
const re = pathToRegexp(path, params);
|
|
222
|
+
|
|
223
|
+
if (params.length) {
|
|
224
|
+
child.names = params.map(p => `${p.name}`);
|
|
225
|
+
child.regexp = re;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Add the node to the parent
|
|
229
|
+
parent.children[key] = child;
|
|
230
|
+
|
|
231
|
+
// Create nodes for rest of the keys
|
|
232
|
+
return createNode(keys, index + 1, value, child);
|
|
233
|
+
}
|