├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── apidoc.md
├── custom_service.md
├── decorators.md
├── http_client.md
├── logging.md
├── migration_guides
│ └── 10_migrate_from_v6_to_v7.md
├── routes.md
└── testing.md
├── examples
├── advanced
│ ├── .npmrc
│ ├── greetBusinessLogic.js
│ ├── greetByGroup.md
│ ├── index.js
│ ├── package.json
│ └── tests
│ │ └── greetByGroup.test.js
├── basic
│ ├── .npmrc
│ ├── index.js
│ └── package.json
└── default.env
├── index.d.ts
├── index.js
├── lib
├── ajvSetup.js
├── decoratorsCommonFunctions.js
├── httpClient.js
├── postDecorator.js
├── preDecorator.js
├── rawCustomPlugin.js
└── util.js
├── package.json
├── scripts
└── update-version.sh
└── tests
├── environment.test.js
├── fixtures
└── keys
│ ├── ca.crt
│ ├── client.crt
│ ├── client.key
│ ├── server.crt
│ └── server.key
├── getHeadersToProxy.test.js
├── getHttpClient.test.js
├── httpClient.test.js
├── index.test.js
├── oas.test.js
├── postDecorator.test.js
├── preDecorator.test.js
├── services
├── advanced-config.js
├── advanced-custom-service.js
├── advanced-first-level-properties-service.js
├── all-of-env-validation-custom-service.js
├── any-of-env-validation-custom-service.js
├── get-headers-to-proxy.js
├── http-client.js
├── if-then-else-env-validation-custom-service.js
├── one-of-env-validation-custom-service.js
├── overlapping-env-validation-custom-service.js
├── plain-custom-service.js
├── post-decorator.js
├── pre-decorator.js
└── service-with-formats.js
└── types
├── index.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Desktop (please complete the following information):**
24 | - Version [e.g. 22]
25 | - Node Version
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "04:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: tap
11 | versions:
12 | - 15.0.0
13 | - 15.0.1
14 | - 15.0.2
15 | - 15.0.3
16 | - 15.0.4
17 | - dependency-name: ajv
18 | versions:
19 | - 7.0.3
20 | - 7.0.4
21 | - 7.1.0
22 | - 7.1.1
23 | - 7.2.1
24 | - 7.2.3
25 | - 8.0.1
26 | - 8.0.3
27 | - 8.0.5
28 | - 8.1.0
29 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | tags:
10 | - '*'
11 | pull_request:
12 | branches: [ master ]
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | node-version: [18.x, 20.x, 22.x]
22 |
23 | steps:
24 | - uses: actions/checkout@v2
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | - run: npm i
30 | - run: npm run build --if-present
31 | - run: npm test
32 | - run: npm run coverage
33 |
34 | - name: Coveralls
35 | uses: coverallsapp/github-action@master
36 | with:
37 | github-token: ${{ secrets.GITHUB_TOKEN }}
38 |
39 | publish:
40 | runs-on: ubuntu-latest
41 | needs: [build]
42 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
43 | steps:
44 | - uses: actions/checkout@v3
45 | - uses: JS-DevTools/npm-publish@v2
46 | with:
47 | token: ${{ secrets.NPM_TOKEN }}
48 | access: 'public'
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # parcel-bundler cache (https://parceljs.org/)
61 | .cache
62 |
63 | # next.js build output
64 | .next
65 |
66 | # nuxt.js build output
67 | .nuxt
68 |
69 | # vuepress build output
70 | .vuepress/dist
71 |
72 | # Serverless directories
73 | .serverless
74 |
75 | # We are a library, ignore lock files
76 | package-lock.json
77 | yarn.lock
78 |
79 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
80 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
81 | .idea
82 | # User-specific stuff
83 | .idea/**/workspace.xml
84 | .idea/**/tasks.xml
85 | .idea/**/usage.statistics.xml
86 | .idea/**/dictionaries
87 | .idea/**/shelf
88 |
89 | # Generated files
90 | .idea/**/contentModel.xml
91 |
92 | # Sensitive or high-churn files
93 | .idea/**/dataSources/
94 | .idea/**/dataSources.ids
95 | .idea/**/dataSources.local.xml
96 | .idea/**/sqlDataSources.xml
97 | .idea/**/dynamic.xml
98 | .idea/**/uiDesigner.xml
99 | .idea/**/dbnavigator.xml
100 |
101 | # Gradle
102 | .idea/**/gradle.xml
103 | .idea/**/libraries
104 |
105 | # Gradle and Maven with auto-import
106 | # When using Gradle or Maven with auto-import, you should exclude module files,
107 | # since they will be recreated, and may cause churn. Uncomment if using
108 | # auto-import.
109 | # .idea/modules.xml
110 | # .idea/*.iml
111 | # .idea/modules
112 |
113 | # CMake
114 | cmake-build-*/
115 |
116 | # Mongo Explorer plugin
117 | .idea/**/mongoSettings.xml
118 |
119 | # File-based project format
120 | *.iws
121 |
122 | # IntelliJ
123 | out/
124 |
125 | # mpeltonen/sbt-idea plugin
126 | .idea_modules/
127 |
128 | # JIRA plugin
129 | atlassian-ide-plugin.xml
130 |
131 | # Cursive Clojure plugin
132 | .idea/replstate.xml
133 |
134 | # Crashlytics plugin (for Android Studio and IntelliJ)
135 | com_crashlytics_export_strings.xml
136 | crashlytics.properties
137 | crashlytics-build.properties
138 | fabric.properties
139 |
140 | # Editor-based Rest Client
141 | .idea/httpRequests
142 |
143 | # Android studio 3.1+ serialized cache file
144 | .idea/caches/build_file_checksums.ser
145 |
146 | .vscode
147 | .vscode/*
148 | !.vscode/settings.json
149 | !.vscode/tasks.json
150 | !.vscode/launch.json
151 | !.vscode/extensions.json
152 |
153 | # Linux OS
154 | *~
155 |
156 | # temporary files which can be created if a process still has a handle open of a deleted file
157 | .fuse_hidden*
158 |
159 | # KDE directory preferences
160 | .directory
161 |
162 | # Linux trash folder which might appear on any partition or disk
163 | .Trash-*
164 |
165 | # .nfs files are created when an open file is removed but is still being accessed
166 | .nfs*
167 |
168 | # macOS
169 | .DS_Store
170 | .AppleDouble
171 | .LSOverride
172 |
173 | # Icon must end with two \r
174 | Icon
175 |
176 |
177 | # Thumbnails
178 | ._*
179 |
180 | # Files that might appear in the root of a volume
181 | .DocumentRevisions-V100
182 | .fseventsd
183 | .Spotlight-V100
184 | .TemporaryItems
185 | .Trashes
186 | .VolumeIcon.icns
187 | .com.apple.timemachine.donotpresent
188 |
189 | # Directories potentially created on remote AFP share
190 | .AppleDB
191 | .AppleDesktop
192 | Network Trash Folder
193 | Temporary Items
194 | .apdisk
195 |
196 | examples/**/package-lock.json
197 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github
2 | .nyc_output
3 | coverage
4 | examples
5 | scripts
6 | tests
7 | .editorconfig
8 | .nvmrc
9 | .travis.yml
10 | *.log
11 | *.tgz
12 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/iron
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## v7.0.1 - 2024-12-16
10 |
11 | ### Updated
12 |
13 | - axios: v1.7.9
14 |
15 | ## v7.0.0 - 2024-07-04
16 |
17 | ### BREAKING
18 |
19 | - dropped node 14 and node 16 support
20 | - dropped deprecated method `getServiceProxy` and `getDirectServiceProxy`, see the [migration guide](./docs/migration_guides/10_migrate_from_v6_to_v7.md) for further details.
21 |
22 | ### Updated
23 |
24 | - lc39: v8
25 |
26 | ## v6.0.3
27 |
28 | ### Updated
29 |
30 | - update axios to v1.6.7
31 |
32 | ## v6.0.2 - 2023-10-30
33 |
34 | ### Updated
35 |
36 | - update axios to v1.6.0
37 |
38 | ## v6.0.1 - 2023-10-20
39 |
40 | ### Changes
41 | - update @mia-platform/lc39 lib to v7.0.2 to fix the "fastify.close is not a function" error
42 |
43 | ## v6.0.0 - 2023-05-11
44 |
45 | ### BREAKING CHANGES
46 |
47 | - when returns stream, content-type `application/octet-stream` is not added by default anymore. So, you should add it on your own. See [here](https://github.com/fastify/fastify/pull/3086) for reference
48 | - upgrade lc39 to v7 and fastify to v4
49 |
50 | ## v5.1.7 - 2023-03-13
51 |
52 | ### Fixed
53 |
54 | - fix ts types
55 |
56 | ### v5.1.6 - 2023-02-08
57 |
58 | ### Fixed
59 |
60 | - merging of baseProperties with customProperties causing inconsistent microservices' env Variables schema
61 |
62 | ### Added
63 |
64 | - add optional metrics on request duration. The metrics collection is enabled by mean of the variable `ENABLE_HTTP_CLIENT_METRICS` (default: false)
65 |
66 | ## v5.1.5 - 2022-11-24
67 |
68 | ### Fixed
69 |
70 | - fix the handle of env var required with a configured default
71 |
72 | ## v5.1.4 - 2022-11-23
73 |
74 | THIS VERSION CONTAINS A BREAKING CHANGES
75 |
76 | This version contains a bug when it's configured a required env var with a default. The default is not handled before the default, so the service will not start.
77 |
78 | ### Added
79 |
80 | - add property `duration` to the httpClient response
81 |
82 | ### Fixed
83 |
84 | - [issue-123](https://github.com/mia-platform/custom-plugin-lib/issues/231): use if-then-else syntax at root level in json-schema of envs
85 |
86 | ## v5.1.3 - 2022-10-07
87 |
88 | ### Fixed
89 |
90 | - http client base options
91 |
92 | ## v5.1.2 - 2022-09-23
93 |
94 | ### Fixed
95 |
96 | - http client base options
97 |
98 | ## v5.1.1 - 2022-07-21
99 |
100 | ### Fixed
101 |
102 | - make `pino` a dependency
103 |
104 | ## v5.1.0 - 2022-07-08
105 |
106 | ### Fixed
107 |
108 | - fixed http client typings
109 | - base url with path prefix
110 | - httpsAgent in options
111 |
112 | ## v5.0.0 - 2022-05-13
113 |
114 | ### BREAKING CHANGES
115 |
116 | - upgrade lc39 to v6
117 | - drop Node support <14
118 |
119 | ### Added
120 |
121 | - add an http client method to replace deprecated `getServiceProxy` and `getDirectServiceProxy` (see all the features in the http client docs).
122 | Main breaking changes from the already existent `getServiceProxy` and `getDirectServiceProxy`:
123 | - streams respond with an object with headers, payload and statusCode. The payload has the stream interface
124 | - `allowedStatusCodes` array of status codes is replaced by the function `validateStatus` (which accept by default 2xx)
125 | - `agent` to configure the proxy is renamed to `proxy` and it is now an object
126 | - `port` and `protocol` are now accepted only in url and baseUrl
127 | - expose `getHeadersToProxy` function to calculate headers to proxy
128 |
129 | ### Deprecation
130 |
131 | - deprecate `getServiceProxy` and `getDirectServiceProxy` function. Please, substitute it with the `getHttpClient` function.
132 |
133 | ### Fixes
134 |
135 | - fix Mia header propagation (resolved [#161](https://github.com/mia-platform/custom-plugin-lib/issues/161))
136 |
137 | ### Changes
138 |
139 | - upgrade dependencies
140 |
141 | ## v4.3.2 - 2022-02-15
142 |
143 | ### Fixes
144 |
145 | - Fixed typos and changed docs links inside `docs` directory
146 | - CVE-2022-0355
147 |
148 | ## v4.3.1 - 2022-02-02
149 |
150 | ### Fixes
151 |
152 | - Fixed error JSON.parse while passing both `returnAs:'BUFFER'` and `allowedStatusCode:[204]`
153 |
154 | ## v4.3.0 - 2022-01-26
155 |
156 | ### Added
157 |
158 | - Typings improvement
159 |
160 | ### Fixes
161 |
162 | - Fixed PostDecoratorDecoratedRequest typing
163 |
164 | ## v4.2.0 - 2021-10-15
165 |
166 | ### Added
167 |
168 | - optional configuration object to customize ajv formats and vocabulary when defining the custom plugin function
169 |
170 | ## v4.1.1 - 2021-09-28
171 |
172 | ### Added
173 |
174 | - Change `addValidatorSchema` to add also schema using `fastify.addSchema` methods to correctly resolve swagger and serialization.
175 |
176 | ## v4.1.0 - 2021-09-23
177 |
178 | ### Added
179 |
180 | - It is now possible to use ajv instance to extend schema using `addValidatorSchema`. Use fastify addSchema feature is not supported because the validator compiler is overwritten.
181 | - Add new function `getValidatorSchema` to get schema added to the validator.
182 |
183 | ## v4.0.0 - 2021-08-31
184 |
185 | ### BREAKING CHANGES
186 |
187 | - [@mia-platform/lc39](https://github.com/mia-platform/lc39) updated to v5
188 | - OpenAPI 3 is now the default version. Read the [official documentation](https://github.com/mia-platform/lc39/blob/master/docs/main-entrypoint.md#exposed-swagger-documentation) for more
189 | - Improved types definitions
190 |
191 | ## v3.1.1 - 2021-07-27
192 |
193 | ### Fixed
194 |
195 | - fix validation error message from other resource than body
196 |
197 | ## v3.1.0 - 2021-07-27
198 |
199 | ### Added
200 |
201 | - improve validation error message
202 |
203 | ## v3.0.0 - 2021-28-06
204 |
205 | ### BREAKING CHANGES
206 |
207 | - ajv formats are no longer supported. Validation can now be achieved using a pattern field and specifying a regular expression.
208 |
209 | This change is due to an incompatibility between fastify-env dependency env-schema and ajv formats. From ajv version 7.0.0 formats have been separated from the main package, requiring to be explicitly added. Visit ajv [official documentation](https://ajv.js.org/guide/formats.html#string-formats) and env-schema [official release page](https://github.com/fastify/env-schema/releases/tag/v3.0.0) for further details regarding this topic.
210 |
211 | - Environment variables using default values cannot be required anymore (and viceversa).
212 |
213 | Fastify-env dependency env-schema no longer support both required and default properties to be defined simultaneously. Please make sure your environment variables now respect this constraint. You can have further details regarding this issue [here](https://github.com/fastify/env-schema/blob/master/index.js#L51)
214 |
215 | ### Fixed
216 |
217 | - replaced expired test certificates with new ones that will expire on 3 September 3061
218 |
219 | ### Changed
220 |
221 | - the `getDirectServiceProxy` function now can receive a complete url as `serviceName` parameter
222 |
223 | - update dependencies to fastify v3 and lc39 to v4.0.0
224 |
225 | ## v2.3.0 - 2021-03-03
226 |
227 | - update lc39 to 3.3.0
228 |
229 | ## v2.2.0 - 2021-03-03
230 |
231 | ### Added
232 |
233 | - added tls options in service proxies
234 |
235 | ## v2.1.1 - 2020-10-26
236 |
237 | ### Fixed
238 |
239 | - change charset from `utf8` to `utf-8`
240 |
241 | ## v2.1.0 - 2020-10-21
242 |
243 | ### Added
244 |
245 | - add `timeout` option to service builder to make requests
246 | - add `agent` option to service builder to make requests
247 |
248 | ## v2.0.4 - 2020-10-08
249 |
250 | - update lc39 to 3.1.4
251 |
252 | ## v2.0.3 - 2020-10-02
253 |
254 | - update lc39 to 3.1.3
255 |
256 | ## v2.0.2 - 2020-09-29
257 |
258 | ### Added
259 |
260 | - updated lc39 dependency to 3.1.2
261 |
262 | ## v2.0.1 - 2020-09-22
263 |
264 | - updated lc39 dependency to 3.1.1
265 |
266 | ## v2.0.0 - 2020-09-17
267 |
268 | ### BREAKING CHANGES
269 |
270 | - Dropped support to Node 8
271 | - Update @mia-platform/lc39 2.2.2 -> 3.1.0.
272 |
273 | This update bring this breaking change:
274 | * Request and response logged information are now compliant with Mia-Platform logging guidelines. To see the guidelines, please check [Mia Platform Docs](https://docs.mia-platform.eu/docs/development_suite/api-console/api-design/guidelines-for-logs). You can find the implementation details [here](https://github.com/mia-platform/lc39/blob/master/lib/custom-logger.js)
275 |
276 | ### Added
277 |
278 | - Added `getUserProperties` to decorated `fastify.Request`, which returns the user properties
279 |
280 | ### Changed
281 |
282 | - Update ajv 6.10.2 -> 6.12.0
283 | - Update fastify-plugin 1.6.0 -> 1.6.1
284 | - Update simple-get 3.0.3 -> 3.1.0
285 |
286 | ## v1.1.1 - 2019-12-09
287 | ## Add
288 | - Add README
289 |
290 | ## v1.1.0 - 2019-12-09
291 | ## Add
292 | - advanced config in route
293 |
294 | ## v1.0.5 - 2019-09-09
295 |
296 | ### Changed
297 |
298 | - Update @types/node 12.7.1 -> 12.7.4
299 | - Moved URL construction from deprecated url.format to the new WHATWG API
300 |
301 | ## v1.0.4 - 2019-08-08
302 |
303 | ### Changed
304 |
305 | - Update @mia-platform/lc39 2.2.0 -> 2.2.2
306 | - Update @types/node 12.6.0 -> 12.7.1
307 | - Update ajv 6.10.1 -> 6.10.2
308 |
309 | ## v1.0.3 - 2019-07-08
310 |
311 | ### Changed
312 |
313 | - Update @mia-platform/lc39 2.1.2 -> 2.2.0
314 | - Update @types/node 12.0.2 -> 12.6.0
315 | - Update ajv 6.10.0 -> 6.10.1
316 | - Update http-errors 1.7.2 -> 1.7.3
317 | - Update fastify-env 0.6.2 -> 1.0.1
318 |
319 | ## v1.0.1 - 2019-05-28
320 |
321 | ### Changed
322 |
323 | - Improved condition for hinding error messages to users to avoid false positives
324 |
325 | ## v1.0.0 - 2019-05-23
326 |
327 | ### Added
328 |
329 | - Add new flag for filtering mia header injection
330 | - Add extra headers proxy during internal http calls
331 |
332 | ### Changed
333 |
334 | - Update repository to use lc39 and fastify 2
335 | - Modify error message on status code validation failure
336 |
337 | ## v1.0.0-rc.2 - 2019-05-08
338 |
339 | ### Changed
340 |
341 | - Fix wrong dependencies in `package.json` file
342 |
343 | ## v1.0.0-rc.1 - 2019-05-08
344 |
345 | ### Changed
346 |
347 | - Fix wrong encapsulation from fastify for custom functions
348 |
349 | ## v1.0.0-rc.0 - 2019-05-06
350 |
351 | ### Changed
352 |
353 | - Update repository to use lc39 and fastify 2
354 | - Modify error message on status code validation failure
355 |
356 | ## v0.7.0 - 2019-03-12
357 |
358 | ### Added
359 |
360 | - Add checks for `env` variables
361 | - Add response status code validation
362 | - Add new way to create a service proxy
363 |
364 | ## v0.6.0 - 2019-02-13
365 |
366 | ### Added
367 |
368 | - Add support for proxy custom headers to the services pipeline
369 |
370 | ## v0.5.2 - 2019-02-06
371 |
372 | ### Added
373 |
374 | - Add support for proxy custom headers to other services
375 |
376 | ### Changed
377 |
378 | - Update fastify-cli 0.22.0 -> 0.24.0
379 |
380 | ## v0.5.1 - 2018-10-19
381 |
382 | ### Added
383 |
384 | - Add support for trusted proxies
385 |
386 | ### Changed
387 |
388 | - Update fastify 1.9.0 -> 1.12.1
389 | - Update fastify-cli 0.19.0 -> 0.22.0
390 |
391 | ## v0.5.0 - 2018-08-23
392 |
393 | ### Added
394 |
395 | - Add more customization options for calling other services
396 |
397 | ## v0.4.0 - 2018-08-01
398 |
399 | Initial public release
400 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Mia service Node.js Library
4 |
5 | [![Build Status][travis-svg]][travis-org]
6 | [![javascript style guide][standard-mia-svg]][standard-mia]
7 | [![Coverage Status][coverall-svg]][coverall-io]
8 | [![NPM version][npmjs-svg]][npmjs-com]
9 |
10 |
11 |
12 | This library is intended to ease the [creation of new services](https://docs.mia-platform.eu/development_suite/api-console/api-design/services/) to deploy
13 | on [Mia-Platform][mia-platform].
14 | Built on [`Fastify`][fastify], it takes advantage of Mia-Platform Node.js service launcher [`lc39`][lc39].
15 |
16 | # Getting Started
17 | You can use this module in your projects or, from the [DevOps Console](https://docs.mia-platform.eu/development_suite/overview-dev-suite/), get started quickly and easily with a [ready-to-use microservice template](https://docs.mia-platform.eu/development_suite/api-console/api-design/custom_microservice_get_started/). Even in the [Mia-Platform Marketplace](https://github.com/mia-platform-marketplace) repository, you can find some examples and templates that using this library.
18 |
19 | ## Setup the local development environment
20 |
21 | To develop the service locally you need:
22 | - Node.js v12 or later.
23 |
24 | To setup node.js, we suggest using [nvm][nvm], so you can manage multiple versions easily.
25 | Once you have installed nvm, you can go inside the directory of the project and simply run
26 | `nvm install`, the `.nvmrc` file will install and select the correct version
27 | if you don’t already have it.
28 |
29 | Once you have all the dependency in place, you can launch:
30 | ```shell
31 | npm i
32 | npm run coverage
33 | ```
34 |
35 | These two commands, will install the dependencies and run the tests with the coverage report that you can view as an HTML
36 | page in `coverage/lcov-report/index.html`.
37 |
38 | ## Install module
39 |
40 | To install the package with npm:
41 |
42 | ```sh
43 | npm i @mia-platform/custom-plugin-lib --save
44 | ```
45 | To install with Yarn:
46 |
47 | ```sh
48 | yarn add @mia-platform/custom-plugin-lib
49 | ```
50 | ## Define a Custom Service
51 |
52 | You can define a new Custom Service that integrates with the platform simply writing this:
53 | ```js
54 | const customService = require('@mia-platform/custom-plugin-lib')()
55 |
56 | module.exports = customService(async function helloWorldService(service) {
57 | service.addRawCustomPlugin('GET', '/hello', function handler(request, reply) {
58 | request.log.trace('requested myuser')
59 | // if the user is not logged, this method returns a falsy value
60 | const user = request.getUserId() || 'World'
61 | reply.send({
62 | hello: `${user}!`,
63 | })
64 | })
65 | })
66 | ```
67 | - The library exports a function which creates the infrastructure ready to accept the definition of routes and decorators. Optionally can take a schema of the required environment variables, you can find the reference [here][fastify-env]. The function returned, `customService`, expects an async function to initialize and configure the `service`.
68 | - `service` is a [Fastify instance](https://www.fastify.io/docs/latest/Server/), that is decorated by the library to help you interact with Mia-Platform resources. You can use *service* to register any Fastify routes, custom decorations and plugin, see [here][fastify-ecosystem] for a list of currently available plugins.
69 |
70 | - `addRawCustomPlugin` is a function that requires the HTTP method, the path of the route and a handler. The handler can also be an [async function](https://www.fastify.io/docs/latest/Routes/#async-await).
71 | Optionally you can indicate the JSONSchemas to validate the querystring, the parameters, the payload and the response.
72 |
73 | To get more info about Custom Services can you look at the [related section](./docs/custom_service.md).
74 |
75 | ## Environment Variables configuration
76 | To works correctly, this library needs some specific environment variables:
77 |
78 | * `USERID_HEADER_KEY`
79 | * `USER_PROPERTIES_HEADER_KEY`
80 | * `GROUPS_HEADER_KEY`
81 | * `CLIENTTYPE_HEADER_KEY`
82 | * `BACKOFFICE_HEADER_KEY`
83 | * `MICROSERVICE_GATEWAY_SERVICE_NAME`
84 |
85 | When creating a new service from Mia-Platform DevOps Console, they come already defined but you can always change or add them anytime as described [in the DevOps console documentation](https://docs.mia-platform.eu/docs/development_suite/api-console/api-design/services#environment-variable-configuration).
86 | In local, the environment variables are defined
87 | in this [file](examples/default.env).
88 |
89 | Other variables can be specified by setting your envSchema when calling the plugin.
90 |
91 | ## Examples
92 | You can see an [advanced example](examples/advanced/) to see different use cases of the library.
93 |
94 | To see other examples of library use you can visit [GitHub dependant repository graph](https://github.com/mia-platform/custom-plugin-lib/network/dependents?package_id=UGFja2FnZS00NTc2OTY4OTE%3D) and check all packages that depends on it.
95 |
96 | To run the [examples](examples) directly you can move to specific example folder and run:
97 |
98 | ```sh
99 | npm run start:local
100 | ```
101 |
102 | This command will launch the service on `localhost:3000` with the environment variables defined
103 | in this [file](examples/default.env).
104 | Now you can consult the swagger documentation of the service at
105 | [http://localhost:3000/documentation/](http://localhost:3000/documentation/).
106 |
107 | # How to
108 |
109 | * Create a Custom Service
110 | * Declare routes
111 | * Add decorators
112 | * Call the other services on the Platform project
113 | * API documentation
114 | * Testing
115 | * Logging
116 |
117 | [17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
118 |
119 | [18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
120 |
121 | [19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
122 |
123 | [20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
124 |
125 | [21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
126 |
127 | [22]: https://developer.mozilla.org/docs/Web/API/Comment/Comment
128 |
129 | [23]: https://daringfireball.net/projects/markdown/
130 |
131 | [travis-svg]: https://travis-ci.org/mia-platform/custom-plugin-lib.svg?branch=master
132 | [travis-org]: https://travis-ci.org/mia-platform/custom-plugin-lib
133 | [standard-mia-svg]: https://img.shields.io/badge/code_style-standard--mia-orange.svg
134 | [standard-mia]: https://github.com/mia-platform/eslint-config-mia
135 | [coverall-svg]: https://coveralls.io/repos/github/mia-platform/custom-plugin-lib/badge.svg
136 | [coverall-io]: https://coveralls.io/github/mia-platform/custom-plugin-lib
137 | [npmjs-svg]: https://img.shields.io/npm/v/@mia-platform/custom-plugin-lib.svg?logo=npm
138 | [npmjs-com]: https://www.npmjs.com/package/@mia-platform/custom-plugin-lib
139 |
140 | [mia-platform]: https://www.mia-platform.eu/
141 | [lc39]: https://github.com/mia-platform/lc39
142 | [nvm]: https://github.com/creationix/nvm
143 |
144 | [fastify]: https://www.fastify.io/
145 | [fastify-env]: https://github.com/fastify/fastify-env
146 | [fastify-async]: https://www.fastify.io/docs/latest/Routes/#async-await
147 | [fastify-ecosystem]: https://www.fastify.io/ecosystem/
148 | [fastify-parsers]: https://www.fastify.io/docs/latest/ContentTypeParser/
149 |
--------------------------------------------------------------------------------
/docs/apidoc.md:
--------------------------------------------------------------------------------
1 | # API documentation
2 |
3 | Services developed with this library automatically also exposes the documentation of the routes and decorators that
4 | are implemented. The documentation is specified using the [OpenAPI 2.0 standard](https://swagger.io/specification/v2/)
5 | and exhibited through [Swagger](https://swagger.io).
6 |
7 | Once the service is started, its documentation can be accessed at
8 | route [`http://localhost:3000/documentation`](http://localhost:3000/documentation).
9 |
10 | The specification of the request scheme
11 | and responses to a route must conform to the format accepted by
12 | [Fastify](https://www.fastify.io/docs/latest/Validation-and-Serialization).
13 |
14 | ## Example
15 |
16 | ```js
17 | const customService = require('@mia-platform/custom-plugin-lib')()
18 |
19 | const schema = {
20 | body: {
21 | type: 'object',
22 | properties: {
23 | someKey: { type: 'string' },
24 | someOtherKey: { type: 'number' }
25 | }
26 | },
27 |
28 | querystring: {
29 | name: { type: 'string' },
30 | excitement: { type: 'integer' }
31 | },
32 |
33 | params: {
34 | type: 'object',
35 | properties: {
36 | par1: { type: 'string' },
37 | par2: { type: 'number' }
38 | }
39 | },
40 |
41 | headers: {
42 | type: 'object',
43 | properties: {
44 | 'x-foo': { type: 'string' }
45 | },
46 | required: ['x-foo']
47 | }
48 | }
49 |
50 | module.exports = customService(async function exampleService(service) {
51 | service.addRawCustomPlugin('GET', '/endpoint', function handler(request,reply) { ... }, schema)
52 | })
53 | ```
54 |
--------------------------------------------------------------------------------
/docs/custom_service.md:
--------------------------------------------------------------------------------
1 | # Custom Service
2 |
3 | A Custom Microservice is a service that receives HTTP requests, whose cycle of use and deploy is managed by the platform. A Custom Microservice encapsulates ad-hoc business logics that can be developed by any user of the platform. To know how manage your services in the DevOps Console see the [documentation](https://docs.mia-platform.eu/docs/development_suite/api-console/api-design/services)
4 |
5 | The library exports a function which creates the infrastructure ready to accept the definition of routes and decorators.
6 | The function optionally can take a schema of the required environment variables (you can find the reference [fastify-env](https://github.com/fastify/fastify-env).
7 |
8 | ## Example
9 |
10 | ```js
11 | const customService = require('@mia-platform/custom-plugin-lib')({
12 | type: 'object',
13 | required: ['ENV_VAR'],
14 | properties: {
15 | ENV_VAR: { type: 'string' },
16 | },
17 | })
18 | ```
19 |
20 | > **_More examples?_** Go [here](https://github.com/mia-platform/custom-plugin-lib/blob/master/examples/advanced/index.js) to see another use cases.
21 |
22 | You can configure the environment variables from DevOps console, in your service configuration. For further detail see [Mia-Platform documentation](https://docs.mia-platform.eu/docs/development_suite/api-console/api-design/services#environment-variable-configuration).
23 |
24 | The customService function expects two arguments:
25 |
26 | - an async function to initialize and configure the `service`, a [Fastify instance](https://www.fastify.io/docs/latest/Server/);
27 | - an optional `serviceOptions` object useful for configuring the plugins used by the library.
28 |
29 | You can access the environment variables values from `service.config`:
30 |
31 | ```js
32 | module.exports = customService(async function handler(service) {
33 | const { ENV_VAR } = service.config
34 | ...
35 | }, serviceOptions)
36 | ```
37 |
38 | Upon `service`, you can you can add [routes](./routes.md) and [decorators](./decorators.md).
39 |
40 | The `serviceOptions` argument supports the following properties:
41 |
42 | - `ajv`: an object useful to customize certain configurations of the `ajv` instance used by the service:
43 | - `vocabulary`: a list of strings used to setup a custom keyword vocabulary,
44 | keep in mind that if you whish to extend the OAS specification generated by your services and create a valid API specification, each
45 | vocabulary entry **must** start with the `x-` prefix, check out the [OAS specification](https://swagger.io/docs/specification/openapi-extensions/) for further details;
46 | - `plugins`: allows setting up the different plugins used by the service:
47 | - `ajv-formats` (`^2.1.1`): with this option you can configure certain [`ajv-formats`](https://github.com/ajv-validator/ajv-formats) configurations
48 | - `formats`: a list of strings used to setup the formats that should be allowed when validating schemas
49 |
50 | An example of service options is the following:
51 |
52 | ```js
53 | const serviceOptions = {
54 | ajv: {
55 | plugins: {
56 | 'ajv-formats': { formats: ['date-time'] },
57 | },
58 | vocabulary: ['x-name'],
59 | },
60 | }
61 | module.exports = customService(async function myService(service) {
62 | ...
63 | }, serviceOptions)
64 | ```
65 |
--------------------------------------------------------------------------------
/docs/decorators.md:
--------------------------------------------------------------------------------
1 | # Add decorators
2 |
3 | Decorators are particular endpoint that a microservice can expose. Using the DevOps console you can manage your decorators and link them to your endpoint routes. Check out [decorators documentation](https://docs.mia-platform.eu/docs/development_suite/api-console/api-design/decorators) for further detail on their usage and management.
4 |
5 | Decorators allow you to perform custom actions upon specific API handler invocations. There are three types of decorators:
6 |
7 | * **PRE**: invoked *before* the configured route handler.
8 | * **POST**: invoked *after* the successful execution of configured route handler (a 2xx status code is returned).
9 | * **CATCH**: invoked after the failure of the configured route handler (any other error status code, 4xx or 5xx).
10 |
11 | You can add a decorator with these methods:
12 |
13 | * ```addPreDecorator(path, handler)```
14 | * ```addPostDecorator(path, handler)```
15 |
16 | whose arguments are, in order:
17 |
18 | * `path` - the route path (e.g.,`/status /alive`).
19 | * `handler`- function that contains the actual behavior. It must respect the same interface defined in the
20 | documentation of the handlers of [fastify](https://www.fastify.io/docs/latest/Routes/#routes-config).
21 |
22 | ## PRE decorators
23 |
24 | ```js
25 | const customService = require('@mia-platform/custom-plugin-lib')()
26 |
27 | module.exports = customService(async function handler(service) {
28 | service.addPreDecorator('/checkwho', function checkWhoHandler(request) {
29 | const defaultWho = 'John Doe'
30 | const body = request.getOriginalRequestBody()
31 | const { who } = body
32 | const newBody = {
33 | ...body,
34 | who: who || request.getUserId() || defaultWho,
35 | }
36 | // Set original request with retrieved data, the target microservice will receive your newly defined body.
37 | return request.changeOriginalRequest().setBody(newBody)
38 | })
39 | })
40 | ```
41 |
42 | The first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is decorated as the `addRawCustomPlugin` method, in addition, with the following methods:
43 |
44 | * `getOriginalRequest()` - returns the original request.
45 | * `getOriginalRequestMethod()` - returns the original request HTTP method.
46 | * `getOriginalRequestPath()` - returns the path of the original request.
47 | * `getOriginalRequestHeaders()` - returns the headers of the original request.
48 | * `getOriginalRequestQuery()` - returns the querystring of the original request.
49 | * `getOriginalRequestBody()` - returns the body of the original request.
50 | * `changeOriginalRequest()`- returns a builder object with following methods:
51 | * `setBody(newBody)` - modify the body of the original request.
52 | * `setHeaders(newHeaders)` - modify the headers of the original request.
53 | * `setQuery(newPath)` - modify the querystring of the original request.
54 | * `leaveOriginalRequestUnmodified()` - leave the original request unmodified .
55 |
56 | ## POST and CATCH decorators
57 |
58 | ```js
59 | ...
60 | /*
61 | POST decorator
62 | */
63 | service.addPostDecorator('/notify', function notifyHandler(request) {
64 | // Get "notifications" setting from the request querystring
65 | const { notifications } = request.getOriginalRequestQuery()
66 | if(!notifications) {
67 | // It's not necessary to send notification
68 | // leave the original response unmodified
69 | return req.leaveOriginalResponseUnmodified()
70 | }
71 |
72 | const notifier = new Notifier()
73 | // Try to send a notification
74 | const response = await notifier.send({ text: `${who} says: ${mymsg}`})
75 | // Adds to original response body the time of notification send
76 | return request.changeOriginalResponse().setBody(
77 | { ...req.getOriginalRequestBody(),notifySendedAt:new Date() }
78 | )
79 |
80 | /*
81 | CATCH decorator
82 | */
83 | service.addPostDecorator('/catch', function exceptionHandler(request) {
84 | return request.changeOriginalResponse()
85 | .setBody({msg:"Error"})
86 | .setStatusCode(500)
87 | })
88 | ```
89 |
90 | Even in this case the first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is decorated as the `addRawCustomPlugin` method, in addition, with the following methods:
91 |
92 | * `getOriginalRequest()` - returns the original request.
93 | * `getOriginalRequestMethod()` - returns the original request method.
94 | * `getOriginalRequestPath()` - returns the path of the original request.
95 | * `getOriginalRequestHeaders()` - returns the headers of the original request.
96 | * `getOriginalRequestQuery()` - returns the querystring of the original request.
97 | * `getOriginalRequestBody()` - returns the body of the original request.
98 |
99 | Related to the original response:
100 |
101 | * `getOriginalResponseBody()` - returns the body of the original response.
102 | * `getOriginalResponseHeaders()` - returns the headers of the original response.
103 | * `getOriginalResponseStatusCode()` - returns the status code of the original response.
104 | * `changeOriginalResponse()`- returns a builder object with following methods:
105 | * `setBody(newBody)` - modify the body of the original response.
106 | * `setHeaders(newHeaders)` - modifies the headers of the original response.
107 | * `setStatusCode(newCode)` - changes the status code of the original response.
108 | * `leaveOriginalResponseUnmodified()` - leaves the original response unmodified.
109 |
110 | ## Abort chain
111 |
112 | To abort the decorator chain, you can call on the `request` the method:
113 |
114 | `abortChain(finalStatusCode, finalBody, finalHeaders)`
115 |
116 | whose arguments are, in order
117 |
118 | * `finalStatusCode` - the final returned status code.
119 | * `finalBody` - the final returned body.
120 | * `finalHeaders` - the final returned headers.
121 |
122 | > **_More examples?_** Go [here](https://github.com/mia-platform/custom-plugin-lib/blob/master/examples/advanced/index.js) to see another decorators implementations.
123 |
--------------------------------------------------------------------------------
/docs/http_client.md:
--------------------------------------------------------------------------------
1 | # Call the other services on the Platform project
2 |
3 | You can call any service or any endpoint defined on the Platform project, obtaining and using a proxy object.
4 |
5 | For example, if you need to connect to a CRUD, you have to use a Proxy towards the `crud-service`.
6 |
7 | You can get a proxy calling these methods both on `Request` (the first argument of handler) and `Service` (the Fastify instance):
8 |
9 | * `getHttpClient(baseURL, options)` - returns an http client configured with the given base URL and options.
10 | * `baseURL` - the base URL of the service, with protocol, host and it is possible to add a base prefix. The prefix must ends with a slash. Keep in mind that in method functions you must start the path without slash if you want to reuse the path prefix.
11 | * `options` - an object with the following optional fields:
12 | * `headers` - an object that represents the set of headers to send to the service
13 | * `timeout` - set a request timeout
14 | * `cert` - set a custom certificate to use for the requests
15 | * `key` - set a custom key to use for the requests
16 | * `ca` - set a custom ca to use for the requests
17 | * `logger` - the Pino logger instance, it is used to log request (headers, payload and url) and response (headers, payload and status code) in trace level and error message if there is an error during the API call. If not passed, no log are printed. Keep in mind that headers, payload and url could contains sensitive information. If it is the case, do not pass the logger instance or use the redact options to hide the sensitive information ([read here](https://docs.mia-platform.eu/docs/runtime_suite_libraries/lc39/service-options) for more information).
18 | * `isMiaHeaderInjected` - a boolean value that identifies whether Mia's headers should be forwarded in the request. Default `true`.
19 | * `httpsAgent` - an instance of `require('https').Agent` that will be used for the requests, only if `cert`, `key` and `ca` are not configured.
20 |
21 | Potentially, the `getHttpClient` method allows you to also query services outside the Platform. In this case, however, it is necessary to bear in mind that the platform headers will be automatically forwarded. You can do it by setting the `isMiaHeaderInjected` option value to false.
22 |
23 | The http client created from the Request by default forwards the four Mia headers to the service called. In addition, other headers of the original request can also be forwarded to the named service. To do this it is necessary to define an additional environment variable, `ADDITIONAL_HEADERS_TO_PROXY`, whose value must be a string containing the keys of the headers to be forwarded separated by a comma.
24 |
25 | The client expose the methods to perform a specific HTTP request to service.
26 |
27 | * `get(path, options)`
28 | * `post(path, body, options)`
29 | * `put(path, body, options)`
30 | * `patch(path, body, options)`
31 | * `delete(path, body, options)`
32 |
33 | The params to be passed to these functions are:
34 |
35 | * `path` *required* - a string which identifies the route to which you want to send the request (it handles also the query string). Keep in mind that if base url contains a prefix, you must start the path without slash if you want to reuse the path prefix.
36 | * `body` - the body of the request which can be:
37 | * a JSON object
38 | * a [Buffer](https://nodejs.org/api/buffer.html#)
39 | * one [Stream](https://nodejs.org/api/stream.html)
40 | * `options` - optional, an object that admits all the `options` listed above for the `getHttpHeader` methods (which will eventually be overwritten), plus the following fields:
41 | * `returnAs` - a string that identifies the format in which you want to receive the response. It can be `JSON`,`BUFFER` or `STREAM`. Default `JSON`.
42 | * `validateStatus` - a function which returns a boolean indicating whether the status code is valid or not. Default `(status) => status >= 200 && status < 300`.
43 | * `errorMessageKey` - key of the response object (if response in JSON) that will be used to identify the error message. Default `message`.
44 | * `proxy`: object that contains the following fields:
45 | * `protocol`: 'http' or 'https'
46 | * `host`: host of the proxy service
47 | * `port`: port of the proxy service in number format
48 | * `auth`: object that contains the following fields:
49 | * `username`: username to use for the proxy authentication
50 | * `password`: password to use for the proxy authentication
51 | * `query`: object that will be stringified and add to the query
52 |
53 | All methods return a *Promise object*. You can access to:
54 |
55 | * **Status code** of the response trough the `statusCode` property
56 | * **Body** of the response trough the `payload` property
57 | * **Headers** of the response trough the `headers` property
58 | * **Duration** of the http call trough the `duration` property
59 |
60 | If service responds with status code not valid (it is possible to modify this using the `validateStatus` ), the error object contains:
61 |
62 | * **Message** of the response trough the `message` property (or it is possible to customize the key using the `errorMessageKey` option) if response is in JSON. Otherwise, it returns a default error message `Something went wrong`
63 | * **Status code** of the response trough the `statusCode` property
64 | * **Body** of the response trough the `payload` property
65 | * **Headers** of the response trough the `headers` property
66 | * **Duration** of the http call trough the `duration` property
67 |
68 | If error is not an http error, it is throw the error message and the error code.
69 |
70 | ## Examples
71 |
72 | ```js
73 | async function tokenGeneration(request, response) {
74 | const crudProxy = request.getHttpClient('http://my-service/base-path/')
75 | const result = await crudProxy
76 | .post('/tokens-collection/', {
77 | id: request.body.quotationId,
78 | valid: true
79 | })
80 |
81 | const tokens=result.payload;
82 | // ...
83 | }
84 | ```
85 |
--------------------------------------------------------------------------------
/docs/logging.md:
--------------------------------------------------------------------------------
1 | # Logging
2 |
3 | You can log a message to see in DevOps console. The library use the [Fastify logging system](https://www.fastify.io/docs/v2.0.x/Logging/), that is based on [pino](https://github.com/pinojs/pino).
4 |
5 | To log messages call these methods on the logger instance of request. Logging is enabled by default. Therefore you can call on `request.log` or `service.log`:
6 |
7 | * `debug()`
8 | * `info()`
9 | * `warn()`
10 | * `error()`
11 | * `fatal()`
12 |
13 | Each method creates a log with the homonym level.
14 |
15 | By default the library will generate two logs for each request, one representing the incoming request and one for the request completion, logs are created with *trace* and *info* levels respectively and already provide useful information for later analysis or debugging. If you need more, you can add your logs.
16 |
17 | ## Example
18 |
19 | ```js
20 | service.addPostDecorator('/notify', function notifyHandler(request) {
21 | // Get "notifications" setting from the request querystring
22 | const { notifications } = request.getOriginalRequestQuery()
23 | if(!notifications) {
24 | return req.leaveOriginalResponseUnmodified()
25 | }
26 |
27 | try {
28 | const notifier = new Notifier()
29 | const response = await notifier.send({ text: `${who} says: ${mymsg}`})
30 | const sendedAt = new Date();
31 |
32 | // Log at "INFO" level
33 | req.log.info({ statusCode: response.statusCode }, 'Notify sent')
34 |
35 | return request.changeOriginalResponse().setBody(
36 | { ...req.getOriginalRequestBody(), notifySendedAt:sendedAt}
37 | )
38 | } catch (error) {
39 | // Log at "ERROR" level
40 | req.log.error({ error }, 'Error sending notification')
41 | }
42 | )
43 | ```
44 |
45 | For further detail about logs can you see the [guidelines for logs](https://docs.mia-platform.eu/docs/development_suite/monitoring/resources/pods#pod-logs).
46 |
--------------------------------------------------------------------------------
/docs/migration_guides/10_migrate_from_v6_to_v7.md:
--------------------------------------------------------------------------------
1 | # Migrate from v6 to v7
2 |
3 | With v7 the `getServiceProxy` and `getDirectServiceProxy` methods have been removed.
4 |
5 | In order to upgrade to v7 you need to change the implementation using such methods to use another HTTP Client.
6 |
7 | :::tip
8 |
9 | Custom Plugin Lib already provides the [`getHttpClient`](../http_client.md) method to build an axios-based HTTP client since [v5.0.0](../../CHANGELOG.md#v500---2022-05-13).
10 |
11 | Main breaking changes from the already existent `getServiceProxy` and `getDirectServiceProxy`:
12 |
13 | - streams respond with an object with headers, payload and statusCode. The payload has the stream interface
14 | - `allowedStatusCodes` array of status codes is replaced by the function `validateStatus` (which accept by default 2xx)
15 | - `agent` to configure the proxy is renamed to `proxy` and it is now an object
16 | - `port` and `protocol` are now accepted only in url and baseUrl
17 |
18 | :::
19 |
20 | ## Migrate getDirectServiceProxy
21 |
22 | ```js
23 | const proxy = fastify.getServiceProxy('my-service', {})
24 | // becomes
25 | const proxy = fastify.getHttpClient('http://microservice-gateway/', {})
26 | ```
27 |
28 | ## Migrate getServiceProxy
29 |
30 | ```js
31 | const proxy = fastify.getServiceProxy('my-service', {})
32 | // becomes
33 | const proxy = fastify.getHttpClient('http://my-service/', {})
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/routes.md:
--------------------------------------------------------------------------------
1 | # Declare routes
2 |
3 | You can define the behavior of the Custom Microservice in response to an HTTP request by declaring the routes. For this purpose, you can use the `addRawCustomPlugin` method:
4 |
5 | ```js
6 | service.addRawCustomPlugin(httpVerb, path, handler, schema)
7 | ```
8 |
9 | whose arguments are, in order
10 |
11 | * `httpVerb` - the HTTP verb of the request (e.g.,`GET`).
12 | * `path` - the route path (e.g.,`/status /alive`).
13 | * `handler` - function that contains the actual behavior. It must respect the same interface defined in the
14 | documentation of the handlers of [fastify](https://www.fastify.io/docs/latest/Routes/#async-await).
15 | * `schema` - definition of the request and response data schema.
16 | The format is the one accepted by [fastify](https://www.fastify.io/docs/latest/Validation-and-Serialization). To further detail see [`related section`](./apidoc.md).
17 |
18 | To get more info about how to declare a route can you look at the related [Fastify documentation](https://github.com/fastify/fastify/blob/master/docs/Routes.md).
19 |
20 | ## Example
21 |
22 | ```js
23 | const customService = require('@mia-platform/custom-plugin-lib')()
24 |
25 | module.exports = customService(async function handler(service) {
26 | service.addRawCustomPlugin('GET', '/hello', function helloHandler(request, reply) {
27 | const user = request.getUserId() || 'World'
28 |
29 | reply.send({
30 | hello: `${user}!`,
31 | })
32 | })
33 | })
34 | ```
35 |
36 | > **_More examples?_** Go [here](https://github.com/mia-platform/custom-plugin-lib/blob/master/examples/advanced/index.js) to see another `addRawCustomPlugin` uses case.
37 |
38 | * The first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is automatically decorated, indeed we can call `request.getUserId()`.
39 |
40 | The instance of `Request` is decorated with functions:
41 |
42 | * `getUserId()` - exposes the user's id, if logged in or `null`.
43 | * `getGroups()` - exposes an array containing strings that identify the groups to which the logged-in user belongs.
44 | * `getClientType()` - exposes the type of client that performed the HTTP request.
45 | * `isFromBackOffice()` - exposes a boolean to discriminate whether the HTTP request from the CMS.
46 | * `getMiaHeaders()` - exposes an object with all previous information.
47 | The set of this data is called `Mia headers` and getting the values from the following environment variables:
48 | * *USERID_HEADER_KEY*
49 | * *GROUPS_HEADER_KEY*
50 | * *CLIENTTYPE_HEADER_KEY*
51 | * *BACKOFFICE_HEADER_KEY*
52 | * `getHeadersToProxy({isMiaHeaderInjected})` - returns the object of the request headers to proxy in API calls, using the extra headers, the mia headers and the headers set with variable `CUSTOM_HEADERS_TO_PROXY`.
53 |
54 | * The second parameter is a [Reply instance](https://www.fastify.io/docs/latest/Reply/). Use this object to reply to the request. Its main methods are the following:
55 | * `headers(object)` - sets the headers of the response.
56 | * `code(statusCode)` - sets the HTTP status code of the response.
57 | * `send(data)` - sends the payload `data` to the end user.
58 |
59 | * Inside the handler scope it's possible to access Fastify instance using `this`.
60 |
61 | ## Adding a shared schema
62 |
63 | It is possible to add shared schema between routes. For this purpose, you can access to the `ajv` instance used to perform route validation. It is possible to add a schema to the `ajv` instance using the `addValidatorSchema` method. `addValidatorSchema` method also adds schema to fastify using *fastify.addSchema* function. It is also possible to get the added schema using the `getValidatorSchema` method.
64 |
65 | ```js
66 | const customService = require('@mia-platform/custom-plugin-lib')()
67 |
68 | module.exports = customService(async function handler(service) {
69 | service.addValidatorSchema({
70 | $id: 'example',
71 | type: 'object',
72 | properties: {
73 | foobar: {
74 | type: 'string',
75 | }
76 | }
77 | })
78 |
79 | const exampleSchema = service.getValidatorSchema('example')
80 | console.log('Log the example schema', exampleSchema)
81 |
82 | service.addRawCustomPlugin('GET', '/hello', function helloHandler(request, reply) {
83 | const user = request.getUserId() || 'World'
84 |
85 | reply.send({
86 | hello: `${user}!`,
87 | })
88 | }, {
89 | body: {
90 | $ref: 'example#'
91 | }
92 | })
93 | })
94 | ```
95 |
--------------------------------------------------------------------------------
/docs/testing.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | `Mia service Node.js Library` is built on Fastify and therefore integrates with [testing tools](https://www.fastify.io/docs/latest/Testing/)
4 | made available by the framework. A complete example of this type of test is available [here](https://github.com/mia-platform/custom-plugin-lib/tree/master/examples/advanced/tests).
5 |
6 | ## Integration and Unit test
7 |
8 | The testing of service can be performed at multiple levels of abstraction. One possibility is to use a technique called _fake http injection_ for which it is possible to simulate
9 | receiving an HTTP request. In this way, all the service logic is exercised from the HTTP layer to the handlers. This pattern is an example of Integration Testing.
10 |
11 | ### Example Integration Test
12 |
13 | In the example below the test framework [Mocha](https://mochajs.org/).
14 |
15 | ```js
16 | 'use strict'
17 |
18 | const assert = require('assert')
19 | const fastify = require('fastify')
20 |
21 | const customPlugin = require('@mia-platform/custom-plugin-lib')()
22 | const index = customPlugin(async service => {
23 | service.addRawCustomPlugin(
24 | 'GET',
25 | '/status/alive',
26 | async (request, reply) => ({
27 | status: 'ok'
28 | })
29 | )
30 | })
31 |
32 | const createTestServer = () => {
33 | // Silent => trace for enabling logs
34 | const createdServer = fastify({ logger: { level: 'silent' } })
35 |
36 | createdServer.register(index)
37 | return createdServer
38 | }
39 |
40 | describe('/status/alive', () => {
41 | it('should be available', async () => {
42 | const server = createTestServer()
43 |
44 | const response = await server.inject({
45 | url: '/status/alive',
46 | })
47 |
48 | assert.equal(response.statusCode, 200)
49 | })
50 | })
51 | ```
52 |
--------------------------------------------------------------------------------
/examples/advanced/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock = false
2 |
--------------------------------------------------------------------------------
/examples/advanced/greetBusinessLogic.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | class BusinessLogic {
20 | constructor(groupToGreet) {
21 | this.groupToGreet = groupToGreet
22 | }
23 | greet(user, groups) {
24 | const userString = user || 'World'
25 | if (groups.includes(this.groupToGreet)) {
26 | return {
27 | message: `Hello ${userString} of group: ${this.groupToGreet}!\n`,
28 | user,
29 | groups,
30 | }
31 | }
32 |
33 | return {
34 | message: `Hello ${userString}!\n`,
35 | user,
36 | groups,
37 | }
38 | }
39 | }
40 |
41 | module.exports = BusinessLogic
42 |
--------------------------------------------------------------------------------
/examples/advanced/greetByGroup.md:
--------------------------------------------------------------------------------
1 | # Greet by group
2 |
3 | This endpoint greets you only if your groups include a special one
4 | This is a markdown `yeah!`
5 |
--------------------------------------------------------------------------------
/examples/advanced/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const { readFileSync } = require('fs')
20 | const { join } = require('path')
21 | const customService = require('@mia-platform/custom-plugin-lib')({
22 | type: 'object',
23 | required: ['GROUP_TO_GREET'],
24 | properties: {
25 | GROUP_TO_GREET: { type: 'string' },
26 | },
27 | })
28 |
29 | const BusinessLogic = require('./greetBusinessLogic')
30 |
31 | const greetByGroupSchema = {
32 | description: readFileSync(join(__dirname, 'greetByGroup.md'), { encoding: 'utf-8' }),
33 | tags: ['Greet by Group'],
34 | response: {
35 | 200: {
36 | type: 'object',
37 | properties: {
38 | message: { type: 'string', description: 'the greeting message' },
39 | user: { type: 'string', description: 'the user' },
40 | groups: { type: 'array', items: { type: 'string' } },
41 | },
42 | },
43 | },
44 | }
45 |
46 | const sumSchema = {
47 | description: 'Sum an array of numbers',
48 | tags: ['Sum'],
49 | body: {
50 | type: 'array',
51 | items: { type: 'number' },
52 | },
53 | response: {
54 | 200: { type: 'number' },
55 | },
56 | }
57 |
58 | function greetByGroupHandler(request, reply) {
59 | request.log.trace('requested myuser')
60 | const user = request.getUserId()
61 | const groups = request.getGroups()
62 | // call the decorated fastify instance
63 | reply.send(this.myBusinessLogic.greet(user, groups))
64 | }
65 |
66 | function handleSum(request, reply) {
67 | const args = request.body
68 | request.log.trace({ args }, 'requested sum')
69 | let acc = 0
70 | for (let idx = 0; idx < args.length; idx++) {
71 | acc += args[idx]
72 | }
73 | reply.send({ acc })
74 | }
75 |
76 | module.exports = customService(async function exampleService(service) {
77 | // retrieve environment variables from config
78 | const { GROUP_TO_GREET } = service.config
79 | // instantiate your business logic
80 | const myBusinessLogic = new BusinessLogic(GROUP_TO_GREET)
81 | // instantiate your business logic and decorate the fastify instance
82 | // to call it with this.myBusinessLogic in the handler
83 | service.decorate('myBusinessLogic', myBusinessLogic)
84 |
85 | service
86 | .addRawCustomPlugin('GET', '/greetbygroup', greetByGroupHandler, greetByGroupSchema)
87 | .addRawCustomPlugin('POST', '/sum', handleSum, sumSchema)
88 | })
89 |
--------------------------------------------------------------------------------
/examples/advanced/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mia-platform/advanced-custom-plugin-test",
3 | "version": "1.0.0",
4 | "description": "An advanced example for a custom plugin",
5 | "homepage": "https://www.mia-platform.eu/",
6 | "bugs": {
7 | "url": "https://github.com/mia-platform/custom-plugin-lib/issues",
8 | "email": "core@mia-platform.eu"
9 | },
10 | "license": "Apache-2.0",
11 | "author": "Mia Platform Core Team ",
12 | "contributors": [
13 | "Tommaso Allevi ",
14 | "Luca Scannapieco ",
15 | "Paolo Sarti ",
16 | "Jacopo Andrea Giola ",
17 | "Davide Bianchi "
18 | ],
19 | "main": "index.js",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/mia-platform/custom-plugin-lib.git"
23 | },
24 | "scripts": {
25 | "start": "lc39 ./index.js",
26 | "start:local": "npm start -- --env-path ./../default.env",
27 | "test": "tap -b tests/*.test.js"
28 | },
29 | "dependencies": {
30 | "@mia-platform/custom-plugin-lib": "mia-platform/custom-plugin-lib"
31 | },
32 | "engines": {
33 | "node": ">=8"
34 | },
35 | "devDependencies": {
36 | "tap": "^13.1.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/advanced/tests/greetByGroup.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const tap = require('tap')
20 | const lc39 = require('@mia-platform/lc39')
21 |
22 | const USERID_HEADER_KEY = 'userid-header-key'
23 | const GROUPS_HEADER_KEY = 'groups-header-key'
24 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
25 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
26 | const GROUP_TO_GREET = 'group-to-greet'
27 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
28 | const env = {
29 | USERID_HEADER_KEY,
30 | GROUPS_HEADER_KEY,
31 | CLIENTTYPE_HEADER_KEY,
32 | BACKOFFICE_HEADER_KEY,
33 | GROUP_TO_GREET,
34 | MICROSERVICE_GATEWAY_SERVICE_NAME,
35 | }
36 |
37 | async function setupFastify(envVariables) {
38 | const fastify = await lc39('./index.js', {
39 | logLevel: 'silent',
40 | envVariables,
41 | })
42 | return fastify
43 | }
44 |
45 | tap.test('greetByGroup', test => {
46 | test.test('greets the special group', async assert => {
47 | const fastify = await setupFastify(env)
48 | const user = 'Mark'
49 |
50 | const response = await fastify.inject({
51 | method: 'GET',
52 | url: '/greetbygroup',
53 | headers: {
54 | [USERID_HEADER_KEY]: user,
55 | [GROUPS_HEADER_KEY]: 'group-to-greet',
56 | },
57 | })
58 |
59 | assert.strictSame(response.statusCode, 200)
60 | assert.ok(/application\/json/.test(response.headers['content-type']))
61 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
62 | assert.strictSame(JSON.parse(response.payload), {
63 | message: `Hello ${user} of group: group-to-greet!\n`,
64 | user,
65 | groups: ['group-to-greet'],
66 | })
67 | assert.end()
68 | })
69 |
70 | test.end()
71 | })
72 |
--------------------------------------------------------------------------------
/examples/basic/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock = false
2 |
--------------------------------------------------------------------------------
/examples/basic/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('@mia-platform/custom-plugin-lib')()
20 |
21 | module.exports = customService(async function helloWorldService(service) {
22 | service.addRawCustomPlugin('GET', '/hello', function handler(request, reply) {
23 | request.log.trace('requested myuser')
24 | // if the user is not logged, this method returns a falsy value
25 | const user = request.getUserId() || 'World'
26 | reply.send({
27 | hello: `${user}!`,
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/examples/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mia-platform/basic-custom-plugin-test",
3 | "version": "1.0.0",
4 | "description": "A simple example for a custom plugin",
5 | "homepage": "https://www.mia-platform.eu/",
6 | "bugs": {
7 | "url": "https://github.com/mia-platform/custom-plugin-lib/issues",
8 | "email": "core@mia-platform.eu"
9 | },
10 | "license": "Apache-2.0",
11 | "author": "Mia Platform Core Team ",
12 | "contributors": [
13 | "Tommaso Allevi ",
14 | "Luca Scannapieco ",
15 | "Paolo Sarti ",
16 | "Jacopo Andrea Giola ",
17 | "Davide Bianchi "
18 | ],
19 | "main": "index.js",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/mia-platform/custom-plugin-lib.git"
23 | },
24 | "scripts": {
25 | "start": "lc39 ./index.js",
26 | "start:local": "npm start -- --env-path ./../default.env"
27 | },
28 | "dependencies": {
29 | "@mia-platform/custom-plugin-lib": "mia-platform/custom-plugin-lib"
30 | },
31 | "engines": {
32 | "node": ">=8"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/default.env:
--------------------------------------------------------------------------------
1 | USERID_HEADER_KEY=userid-header-key
2 | USER_PROPERTIES_HEADER_KEY=miauserproperties
3 | GROUPS_HEADER_KEY=groups-header-key
4 | CLIENTTYPE_HEADER_KEY=clienttype-header-key
5 | BACKOFFICE_HEADER_KEY=backoffice-header-key
6 | MICROSERVICE_GATEWAY_SERVICE_NAME=microservice-gateway-service-name
7 | GROUP_TO_GREET=group-name
8 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as fastify from 'fastify'
18 | import * as http from 'http'
19 | import * as https from 'https'
20 |
21 | import {FormatName} from 'ajv-formats'
22 |
23 | export = customPlugin
24 |
25 | declare function customPlugin(envSchema?: customPlugin.environmentSchema): customPlugin.CustomService
26 |
27 | declare namespace customPlugin {
28 | type ServiceConfig> = T
29 |
30 | type CustomService = (asyncInitFunction: AsyncInitFunction, serviceOptions?: CustomServiceOptions) => any
31 |
32 | function getHttpClient(url: string, options?: HttpClientBaseOptions): HttpClient
33 |
34 | interface environmentSchema {
35 | type: 'object',
36 | required?: readonly string[],
37 | properties: object
38 | }
39 |
40 | type AsyncInitFunction = (service: DecoratedFastify) => Promise
41 |
42 | interface CustomServiceOptions {
43 | ajv?: {
44 | plugins?: {
45 | 'ajv-formats'?: {formats: FormatName[]}
46 | }
47 | }
48 | vocabulary?: string[]
49 | }
50 |
51 | type RawCustomPluginAdvancedConfig = Pick
60 |
61 | type RawCustomPluginMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'
62 |
63 | type RequestGeneric = T
64 |
65 | interface DecoratedFastify extends fastify.FastifyInstance {
66 | config: Config,
67 | addRawCustomPlugin(method: RawCustomPluginMethod, path: string, handler: AsyncHandler | Handler, schema?: InputOutputSchemas, advancedConfigs?: RawCustomPluginAdvancedConfig): DecoratedFastify,
68 | addPreDecorator(path: string, handler: preDecoratorHandler): DecoratedFastify
69 | addPostDecorator(path: string, handler: postDecoratorHandler): DecoratedFastify
70 | getHttpClient(url: string, options?: HttpClientBaseOptions): HttpClient,
71 | addValidatorSchema(schema: object): void,
72 | getValidatorSchema(schemaId: string): undefined | ((data: any) => boolean | Promise),
73 | }
74 |
75 | interface DecoratedRequest extends fastify.FastifyRequest {
76 | getUserId: () => string | null,
77 | getUserProperties: () => object | null,
78 | getGroups: () => string[],
79 | getClientType: () => string | null,
80 | isFromBackOffice: () => boolean,
81 | getMiaHeaders: () => NodeJS.Dict
82 | getHeadersToProxy({isMiaHeaderInjected}: {isMiaHeaderInjected?: boolean}): NodeJS.Dict
83 | getHttpClient(url: string, options?: HttpClientBaseOptions): HttpClient,
84 | USERID_HEADER_KEY: string,
85 | USER_PROPERTIES_HEADER_KEY: string,
86 | GROUPS_HEADER_KEY: string,
87 | CLIENTTYPE_HEADER_KEY: string,
88 | BACKOFFICE_HEADER_KEY: string,
89 | MICROSERVICE_GATEWAY_SERVICE_NAME: string,
90 | ADDITIONAL_HEADERS_TO_PROXY: string[]
91 | }
92 |
93 | //
94 | // CUSTOM PLUGIN
95 | //
96 | type BasicHandler> = (this: DecoratedFastify, request: DecoratedRequest, reply: fastify.FastifyReply) => ResponseType
97 | type Handler = BasicHandler
98 | type AsyncHandler = BasicHandler>
99 |
100 | //
101 | // HTTP CLIENT
102 | //
103 | type HttpClientMetrics = {
104 | disabled: boolean
105 | urlLabel: string
106 | }
107 | interface HttpClientBaseOptions {
108 | headers?: http.IncomingHttpHeaders,
109 | timeout?: number,
110 | cert?: string,
111 | key?: string,
112 | ca?: string,
113 | httpsAgent?: https.Agent,
114 | logger?: fastify.FastifyLoggerInstance,
115 | isMiaHeaderInjected?: boolean,
116 | disableMetrics?: boolean
117 | }
118 | interface BaseHttpClientResponse {
119 | headers: http.IncomingHttpHeaders
120 | statusCode: number
121 | duration: number
122 | }
123 | interface StreamResponse extends BaseHttpClientResponse {
124 | payload: NodeJS.ReadableStream
125 | }
126 | interface JSONResponse extends BaseHttpClientResponse {
127 | payload: Payload
128 | }
129 | interface BufferResponse extends BaseHttpClientResponse {
130 | payload: Buffer
131 | }
132 | type Response = StreamResponse | JSONResponse | BufferResponse
133 | interface HttpClientProxy {
134 | protocol: 'http' | 'https'
135 | host: string
136 | port: number
137 | auth: {
138 | username: string
139 | password: string
140 | }
141 | }
142 | interface HttpClientOptions extends HttpClientBaseOptions {
143 | returnAs?: 'STREAM' | 'JSON' | 'BUFFER';
144 | validateStatus?: (statusCode: number) => boolean;
145 | errorMessageKey?: string;
146 | proxy?: HttpClientProxy;
147 | query?: Record;
148 | metrics?: HttpClientMetrics;
149 | }
150 |
151 | type RequestBody = any | Buffer | ReadableStream
152 | type RequestMethodWithBody = (path: string, body: RequestBody, options?: HttpClientOptions) => Promise
153 | type RequestMethodWithoutBody = (path: string, options?: HttpClientOptions) => Promise
154 |
155 | interface HttpClient {
156 | get: RequestMethodWithoutBody
157 | post: RequestMethodWithBody
158 | put: RequestMethodWithBody
159 | patch: RequestMethodWithBody
160 | delete: RequestMethodWithBody
161 | }
162 |
163 | //
164 | // SERVICE
165 | //
166 | interface InitServiceOptions {
167 | port?: number,
168 | protocol?: 'http' | 'https',
169 | headers?: http.IncomingHttpHeaders,
170 | prefix?: string,
171 | }
172 | type Certificate = string | Buffer
173 | type ResponseFormats = 'JSON' | 'BUFFER' | 'STREAM'
174 | interface ServiceOptions extends InitServiceOptions{
175 | returnAs?: ResponseFormats
176 | allowedStatusCodes?: number[]
177 | isMiaHeaderInjected?: boolean
178 | cert?: Certificate
179 | key?: Certificate
180 | ca?: Certificate
181 | }
182 | interface BaseServiceResponse extends http.ServerResponse {
183 | headers: http.IncomingHttpHeaders
184 | }
185 | interface StreamedServiceResponse extends BaseServiceResponse {
186 | }
187 | interface JSONServiceResponse extends BaseServiceResponse {
188 | payload: Payload
189 | }
190 | interface BufferServiceResponse extends BaseServiceResponse {
191 | payload: Buffer
192 | }
193 | type ServiceResponse = StreamedServiceResponse | JSONServiceResponse | BufferServiceResponse
194 | type QueryString = string | NodeJS.Dict> | Iterable<[string, string]> | ReadonlyArray<[string, string]>
195 |
196 | interface Service {
197 | get: (path: string, queryString?: QueryString, options?: ServiceOptions) => Promise,
198 | post: (path: string, body: any | Buffer | ReadableStream, queryString?: QueryString, options?: ServiceOptions) => Promise,
199 | put: (path: string, body: any | Buffer | ReadableStream, queryString?: QueryString, options?: ServiceOptions) => Promise,
200 | patch: (path: string, body: any | Buffer | ReadableStream, queryString?: QueryString, options?: ServiceOptions) => Promise,
201 | delete: (path: string, body: any | Buffer | ReadableStream, queryString?: QueryString, options?: ServiceOptions) => Promise,
202 | }
203 |
204 | //
205 | // PRE DECORATOR
206 | //
207 | interface LeaveRequestUnchangedAction { }
208 | interface ChangeRequestAction {
209 | setBody: (newBody: any) => ChangeRequestAction,
210 | setQuery: (newQuery: QueryString) => ChangeRequestAction,
211 | setHeaders: (newHeaders: http.IncomingHttpHeaders) => ChangeRequestAction
212 | }
213 | interface AbortRequestAction { }
214 | type PreDecoratorAction = LeaveRequestUnchangedAction | ChangeRequestAction | AbortRequestAction;
215 | type preDecoratorHandler = (this: DecoratedFastify, request: PreDecoratorDecoratedRequest, reply: fastify.FastifyReply) => Promise;
216 |
217 | interface OriginalRequest {
218 | method: string,
219 | path: string,
220 | query: QueryString,
221 | headers: http.IncomingHttpHeaders,
222 | body?: any
223 | }
224 |
225 | interface OriginalResponse {
226 | statusCode: number,
227 | headers: http.OutgoingHttpHeaders,
228 | body?: any
229 | }
230 |
231 | interface PreDecoratorDecoratedRequest extends DecoratedRequest {
232 | getOriginalRequest: () => OriginalRequest,
233 | getOriginalRequestMethod: () => string,
234 | getOriginalRequestPath: () => string,
235 | getOriginalRequestHeaders: () => http.IncomingHttpHeaders,
236 | getOriginalRequestQuery: () => QueryString
237 | getOriginalRequestBody: () => any,
238 |
239 | getMiaHeaders: () => NodeJS.Dict,
240 |
241 | changeOriginalRequest: () => ChangeRequestAction,
242 | leaveOriginalRequestUnmodified: () => LeaveRequestUnchangedAction,
243 | abortChain: (statusCode: number, finalBody: any, headers?: http.IncomingHttpHeaders) => AbortRequestAction
244 | }
245 |
246 | //
247 | // POST DECORATOR
248 | //
249 | interface LeaveResponseUnchangedAction { }
250 | interface ChangeResponseAction {
251 | setBody: (newBody: any) => ChangeResponseAction,
252 | setStatusCode: (newStatusCode: number) => ChangeResponseAction,
253 | setHeaders: (newHeaders: http.IncomingHttpHeaders) => ChangeResponseAction
254 | }
255 | interface AbortResponseAction { }
256 | type PostDecoratorAction = LeaveResponseUnchangedAction | ChangeResponseAction | AbortResponseAction;
257 | type postDecoratorHandler = (this: DecoratedFastify, request: PostDecoratorDecoratedRequest, reply: fastify.FastifyReply) => Promise;
258 |
259 | interface PostDecoratorDecoratedRequest extends DecoratedRequest {
260 | getOriginalRequest: () => OriginalRequest,
261 | getOriginalRequestMethod: () => string,
262 | getOriginalRequestPath: () => string,
263 | getOriginalRequestHeaders: () => http.IncomingHttpHeaders,
264 | getOriginalRequestQuery: () => QueryString
265 | getOriginalRequestBody: () => any,
266 |
267 | getOriginalResponse: () => OriginalResponse,
268 | getOriginalResponseHeaders: () => http.OutgoingHttpHeaders,
269 | getOriginalResponseBody: () => any,
270 | getOriginalResponseStatusCode: () => number,
271 |
272 | getMiaHeaders: () => NodeJS.Dict,
273 |
274 | changeOriginalResponse: () => ChangeResponseAction,
275 | leaveOriginalResponseUnmodified: () => LeaveResponseUnchangedAction,
276 | abortChain: (statusCode: number, finalBody: any, headers?: http.IncomingHttpHeaders) => AbortResponseAction
277 | }
278 |
279 | // Utilities
280 | interface InputOutputSchemas extends fastify.FastifySchema {
281 | tags?: string[]
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyEnv = require('@fastify/env')
20 | const fp = require('fastify-plugin')
21 | const fastifyFormbody = require('@fastify/formbody')
22 | const Ajv = require('ajv')
23 | const path = require('path')
24 | const { name, description, version } = require(path.join(process.cwd(), 'package.json'))
25 |
26 | const addRawCustomPlugin = require('./lib/rawCustomPlugin')
27 | const addPreDecorator = require('./lib/preDecorator')
28 | const addPostDecorator = require('./lib/postDecorator')
29 | const ajvSetup = require('./lib/ajvSetup')
30 | const HttpClient = require('./lib/httpClient')
31 | const { extraHeadersKeys } = require('./lib/util')
32 |
33 | const USERID_HEADER_KEY = 'USERID_HEADER_KEY'
34 | const USER_PROPERTIES_HEADER_KEY = 'USER_PROPERTIES_HEADER_KEY'
35 | const GROUPS_HEADER_KEY = 'GROUPS_HEADER_KEY'
36 | const CLIENTTYPE_HEADER_KEY = 'CLIENTTYPE_HEADER_KEY'
37 | const BACKOFFICE_HEADER_KEY = 'BACKOFFICE_HEADER_KEY'
38 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'MICROSERVICE_GATEWAY_SERVICE_NAME'
39 | const ADDITIONAL_HEADERS_TO_PROXY = 'ADDITIONAL_HEADERS_TO_PROXY'
40 | const ENABLE_HTTP_CLIENT_METRICS = 'ENABLE_HTTP_CLIENT_METRICS'
41 |
42 | const baseSchema = {
43 | type: 'object',
44 | required: [
45 | USERID_HEADER_KEY,
46 | GROUPS_HEADER_KEY,
47 | CLIENTTYPE_HEADER_KEY,
48 | BACKOFFICE_HEADER_KEY,
49 | MICROSERVICE_GATEWAY_SERVICE_NAME,
50 | ],
51 | properties: {
52 | [USERID_HEADER_KEY]: {
53 | type: 'string',
54 | description: 'the header key to get the user id',
55 | minLength: 1,
56 | },
57 | [USER_PROPERTIES_HEADER_KEY]: {
58 | type: 'string',
59 | description: 'the header key to get the user permissions',
60 | minLength: 1,
61 | default: 'miauserproperties',
62 | },
63 | [GROUPS_HEADER_KEY]: {
64 | type: 'string',
65 | description: 'the header key to get the groups comma separated list',
66 | minLength: 1,
67 | },
68 | [CLIENTTYPE_HEADER_KEY]: {
69 | type: 'string',
70 | description: 'the header key to get the client type',
71 | minLength: 1,
72 | },
73 | [BACKOFFICE_HEADER_KEY]: {
74 | type: 'string',
75 | description: 'the header key to get if the request is from backoffice (any truly string is true!!!)',
76 | minLength: 1,
77 | },
78 | [MICROSERVICE_GATEWAY_SERVICE_NAME]: {
79 | type: 'string',
80 | description: 'the service name of the microservice gateway',
81 | pattern: '^(?=.{1,253}.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*.?$',
82 | },
83 | [ADDITIONAL_HEADERS_TO_PROXY]: {
84 | type: 'string',
85 | default: '',
86 | description: 'comma separated list of additional headers to proxy',
87 | },
88 | [ENABLE_HTTP_CLIENT_METRICS]: {
89 | type: 'boolean',
90 | default: false,
91 | description: 'flag to enable the httpClient metrics',
92 | },
93 | },
94 | }
95 |
96 | function mergeObjectOrArrayProperty(toBeMergedValues, alreadyMergedValues, isArray) {
97 | return isArray ? [
98 | ...toBeMergedValues ?? [],
99 | ...alreadyMergedValues,
100 | ] : {
101 | ...toBeMergedValues ?? {},
102 | ...alreadyMergedValues,
103 | }
104 | }
105 |
106 | // WARNING: including any first level properties other than the ones already declared
107 | // may have undesired effects on the result of the merge
108 | function mergeWithDefaultJsonSchema(schema) {
109 | const defaultSchema = {
110 | ...baseSchema,
111 | }
112 |
113 | Object.keys(schema).forEach(key => {
114 | defaultSchema[key] = typeof schema[key] === 'object'
115 | ? mergeObjectOrArrayProperty(defaultSchema[key], schema[key], Array.isArray(schema[key]))
116 | : schema[key]
117 | })
118 |
119 | return defaultSchema
120 | }
121 |
122 | function getOverlappingKeys(properties, otherProperties) {
123 | if (!otherProperties) {
124 | return []
125 | }
126 | const propertiesNames = Object.keys(properties)
127 | const otherPropertiesNames = Object.keys(otherProperties)
128 | const overlappingProperties = propertiesNames.filter(propertyName =>
129 | otherPropertiesNames.includes(propertyName)
130 | )
131 | return overlappingProperties
132 | }
133 |
134 | function getCustomHeaders(headersKeyToProxy, headers) {
135 | return headersKeyToProxy.reduce((acc, headerKey) => {
136 | if (!{}.hasOwnProperty.call(headers, headerKey)) {
137 | return acc
138 | }
139 | const headerValue = headers[headerKey]
140 | return {
141 | ...acc,
142 | [headerKey]: headerValue,
143 | }
144 | }, {})
145 | }
146 |
147 | function getBaseOptionsDecorated(headersKeyToProxy, baseOptions, headers) {
148 | return {
149 | ...baseOptions,
150 | headers: {
151 | ...getCustomHeaders(headersKeyToProxy, headers),
152 | ...baseOptions.headers,
153 | },
154 | }
155 | }
156 |
157 | function getMiaHeaders() {
158 | const userId = this.getUserId()
159 | const userProperties = this.getUserProperties()
160 | const groups = this.getGroups().join(',')
161 | const clientType = this.getClientType()
162 | const fromBackoffice = this.isFromBackOffice() ? '1' : ''
163 | return {
164 | ...userId !== null ? { [this.USERID_HEADER_KEY]: userId } : {},
165 | ...userProperties !== null ? { [this.USER_PROPERTIES_HEADER_KEY]: JSON.stringify(userProperties) } : {},
166 | ...groups ? { [this.GROUPS_HEADER_KEY]: groups } : {},
167 | ...clientType !== null ? { [this.CLIENTTYPE_HEADER_KEY]: clientType } : {},
168 | ...fromBackoffice ? { [this.BACKOFFICE_HEADER_KEY]: fromBackoffice } : {},
169 | }
170 | }
171 |
172 | function getOriginalRequestHeaders() {
173 | return this.headers
174 | }
175 |
176 | function getHttpClientFromRequest(url, baseOptions = {}) {
177 | const requestHeaders = this.getOriginalRequestHeaders()
178 | const extraHeaders = getCustomHeaders(extraHeadersKeys, requestHeaders)
179 | const options = getBaseOptionsDecorated(this[ADDITIONAL_HEADERS_TO_PROXY], baseOptions, requestHeaders)
180 | const serviceHeaders = { ...this.getMiaHeaders(), ...extraHeaders }
181 | return new HttpClient(url, serviceHeaders, options, this.httpClientMetrics)
182 | }
183 |
184 | function getHttpClient(url, baseOptions = {}, httpClientMetrics = {}) {
185 | return new HttpClient(url, {}, baseOptions, httpClientMetrics)
186 | }
187 |
188 | function getHttpClientFastifyDecoration(url, baseOptions = {}) {
189 | return getHttpClient(url, baseOptions, this.httpClientMetrics)
190 | }
191 |
192 | function getHeadersToProxy({ isMiaHeaderInjected = true } = {}) {
193 | const requestHeaders = this.getOriginalRequestHeaders()
194 |
195 | const miaHeaders = this.getMiaHeaders()
196 | const extraMiaHeaders = getCustomHeaders(extraHeadersKeys, requestHeaders)
197 | const customHeaders = getCustomHeaders(this[ADDITIONAL_HEADERS_TO_PROXY], requestHeaders)
198 | return {
199 | ...isMiaHeaderInjected ? miaHeaders : {},
200 | ...isMiaHeaderInjected ? extraMiaHeaders : {},
201 | ...customHeaders,
202 | }
203 | }
204 |
205 | function decorateFastify(fastify) {
206 | const { config } = fastify
207 | const httpClientMetrics = config[ENABLE_HTTP_CLIENT_METRICS] ? getHttpClientMetrics(fastify) : {}
208 |
209 | fastify.decorateRequest(USERID_HEADER_KEY, config[USERID_HEADER_KEY])
210 | fastify.decorateRequest(USER_PROPERTIES_HEADER_KEY, config[USER_PROPERTIES_HEADER_KEY])
211 | fastify.decorateRequest(GROUPS_HEADER_KEY, config[GROUPS_HEADER_KEY])
212 | fastify.decorateRequest(CLIENTTYPE_HEADER_KEY, config[CLIENTTYPE_HEADER_KEY])
213 | fastify.decorateRequest(BACKOFFICE_HEADER_KEY, config[BACKOFFICE_HEADER_KEY])
214 | fastify.decorateRequest(MICROSERVICE_GATEWAY_SERVICE_NAME, config[MICROSERVICE_GATEWAY_SERVICE_NAME])
215 | fastify.decorateRequest(ADDITIONAL_HEADERS_TO_PROXY, {
216 | getter() {
217 | return config[ADDITIONAL_HEADERS_TO_PROXY].split(',').filter(header => header)
218 | },
219 | })
220 |
221 | fastify.decorateRequest('getMiaHeaders', getMiaHeaders)
222 | fastify.decorateRequest('getOriginalRequestHeaders', getOriginalRequestHeaders)
223 | fastify.decorateRequest('getHeadersToProxy', getHeadersToProxy)
224 |
225 | fastify.decorateRequest('getHttpClient', getHttpClientFromRequest)
226 | fastify.decorateRequest('httpClientMetrics', { getter: () => httpClientMetrics })
227 |
228 | fastify.decorate(MICROSERVICE_GATEWAY_SERVICE_NAME, config[MICROSERVICE_GATEWAY_SERVICE_NAME])
229 | fastify.decorate('addRawCustomPlugin', addRawCustomPlugin)
230 | fastify.decorate('addPreDecorator', addPreDecorator)
231 | fastify.decorate('addPostDecorator', addPostDecorator)
232 |
233 | fastify.decorate('getHttpClient', getHttpClientFastifyDecoration)
234 | fastify.decorate('httpClientMetrics', httpClientMetrics)
235 | }
236 |
237 | async function decorateRequestAndFastifyInstance(fastify, { asyncInitFunction, serviceOptions = {} }) {
238 | const { ajv: ajvServiceOptions } = serviceOptions
239 |
240 | const ajv = new Ajv({ coerceTypes: true, useDefaults: true })
241 | ajvSetup(ajv, ajvServiceOptions)
242 |
243 | fastify.setValidatorCompiler(({ schema }) => ajv.compile(schema))
244 |
245 | fastify.decorate('addValidatorSchema', (schema) => {
246 | ajv.addSchema(schema)
247 | fastify.addSchema(schema)
248 | })
249 | fastify.decorate('getValidatorSchema', ajv.getSchema.bind(ajv))
250 |
251 | decorateFastify(fastify)
252 |
253 | fastify.register(fp(asyncInitFunction))
254 | fastify.setErrorHandler(function errorHandler(error, request, reply) {
255 | if (reply.raw.statusCode === 500 && !error.statusCode) {
256 | request.log.error(error)
257 | reply.send(new Error('Something went wrong'))
258 | return
259 | }
260 | reply.send(error)
261 | })
262 | fastify.setSchemaErrorFormatter((errors, dataVar) => {
263 | const [{ instancePath, message }] = errors
264 | const objectPath = `${dataVar}${instancePath.replace(/\//g, '.')}`
265 | const customErr = new Error(`${objectPath} ${message}`)
266 | customErr.statusCode = 400
267 | return customErr
268 | })
269 | }
270 |
271 |
272 | function initCustomServiceEnvironment(envSchema) {
273 | return function customService(asyncInitFunction, serviceOptions) {
274 | async function index(fastify, opts) {
275 | const overlappingPropertiesNames = getOverlappingKeys(baseSchema.properties, envSchema?.properties)
276 | if (overlappingPropertiesNames.length > 0) {
277 | throw new Error(`The provided Environment JSON Schema includes properties declared in the Base JSON Schema of the custom-plugin-lib, please remove them from your schema. The properties to remove are: ${overlappingPropertiesNames.join(', ')}`)
278 | }
279 |
280 | const schema = envSchema ? mergeWithDefaultJsonSchema(envSchema) : baseSchema
281 | fastify.register(fastifyEnv, { schema, data: opts })
282 | fastify.register(fastifyFormbody)
283 | fastify.register(fp(decorateRequestAndFastifyInstance), { asyncInitFunction, serviceOptions })
284 | }
285 | index.options = {
286 | errorHandler: false,
287 | trustProxy: process.env.TRUSTED_PROXIES,
288 | }
289 |
290 | index.swaggerDefinition = {
291 | info: {
292 | title: name,
293 | description,
294 | version,
295 | },
296 | consumes: ['application/json', 'application/x-www-form-urlencoded'],
297 | produces: ['application/json'],
298 | }
299 | return index
300 | }
301 | }
302 |
303 | function getHttpClientMetrics(fastify) {
304 | if (fastify.metrics?.client) {
305 | const requestDuration = new fastify.metrics.client.Histogram({
306 | name: 'http_request_duration_milliseconds',
307 | help: 'request duration histogram',
308 | labelNames: ['baseUrl', 'url', 'method', 'statusCode'],
309 | buckets: [5, 10, 50, 100, 500, 1000, 5000, 10000],
310 | })
311 | return { requestDuration }
312 | }
313 | }
314 |
315 | module.exports = initCustomServiceEnvironment
316 | module.exports.getHttpClient = getHttpClient
317 |
--------------------------------------------------------------------------------
/lib/ajvSetup.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const addFormats = require('ajv-formats')
20 |
21 | function configureAjvPlugins(ajv, ajvPluginsOptions) {
22 | if (ajvPluginsOptions['ajv-formats'] && ajvPluginsOptions['ajv-formats'].formats) {
23 | addFormats(ajv, ajvPluginsOptions['ajv-formats'].formats)
24 | }
25 | }
26 | function configureAjvVocabulary(ajv, ajvVocabulary) {
27 | ajv.addVocabulary(ajvVocabulary)
28 | }
29 |
30 | module.exports = (ajv, ajvServiceOptions = {}) => {
31 | if (ajvServiceOptions.vocabulary) {
32 | configureAjvVocabulary(ajv, ajvServiceOptions.vocabulary)
33 | }
34 | if (ajvServiceOptions.plugins) {
35 | configureAjvPlugins(ajv, ajvServiceOptions.plugins)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/decoratorsCommonFunctions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const ABORT_CHAIN_STATUS_CODE = 418
4 |
5 | const { getUserId, getGroups, getClientType, isFromBackOffice, getUserProperties } = require('./util')
6 |
7 | function getUserIdFromBody() {
8 | return getUserId(this.getOriginalRequestHeaders()[this.USERID_HEADER_KEY])
9 | }
10 |
11 | function getUserPropertiesFromBody() {
12 | return getUserProperties(this.getOriginalRequestHeaders()[this.USER_PROPERTIES_HEADER_KEY])
13 | }
14 |
15 | function getGroupsFromBody() {
16 | return getGroups(this.getOriginalRequestHeaders()[this.GROUPS_HEADER_KEY] || '')
17 | }
18 |
19 | function getClientTypeFromBody() {
20 | return getClientType(this.getOriginalRequestHeaders()[this.CLIENTTYPE_HEADER_KEY])
21 | }
22 |
23 | function isFromBackOfficeFromBody() {
24 | return isFromBackOffice(this.getOriginalRequestHeaders()[this.BACKOFFICE_HEADER_KEY])
25 | }
26 |
27 | module.exports = {
28 | ABORT_CHAIN_STATUS_CODE,
29 | getUserIdFromBody,
30 | getUserPropertiesFromBody,
31 | getGroupsFromBody,
32 | getClientTypeFromBody,
33 | isFromBackOfficeFromBody,
34 | }
35 |
--------------------------------------------------------------------------------
/lib/httpClient.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const axios = require('axios')
20 | const httpsClient = require('https')
21 | const Pino = require('pino')
22 |
23 | class HttpClient {
24 | constructor(baseUrl, requestHeadersToProxy, baseOptions = {}, metrics = {}) {
25 | this.baseURL = baseUrl
26 | this.requestHeadersToProxy = requestHeadersToProxy
27 | this.baseOptions = baseOptions
28 | this.metrics = baseOptions.disableMetrics ? {} : metrics
29 |
30 | const httpsAgent = getHttpsAgent(baseOptions)
31 | this.axios = axios.create({
32 | ...httpsAgent ? { httpsAgent } : {},
33 | baseURL: baseUrl,
34 | timeout: baseOptions.timeout,
35 | })
36 | decorateResponseWithDuration(this.axios)
37 | }
38 |
39 | async get(path, options = {}) {
40 | return this.makeCall(path, undefined, {
41 | ...options,
42 | method: 'GET',
43 | })
44 | }
45 |
46 | async post(path, payload, options = {}) {
47 | return this.makeCall(path, payload, {
48 | ...options,
49 | method: 'POST',
50 | })
51 | }
52 |
53 | async put(path, payload, options = {}) {
54 | return this.makeCall(path, payload, {
55 | ...options,
56 | method: 'PUT',
57 | })
58 | }
59 |
60 | async patch(path, payload, options = {}) {
61 | return this.makeCall(path, payload, {
62 | ...options,
63 | method: 'PATCH',
64 | })
65 | }
66 |
67 | async delete(path, payload, options = {}) {
68 | return this.makeCall(path, payload, {
69 | ...options,
70 | method: 'DELETE',
71 | })
72 | }
73 |
74 | getLogger(options) {
75 | return options.logger || this.baseOptions.logger || Pino({ level: 'silent' })
76 | }
77 |
78 | getMaxValues(options) {
79 | return {
80 | maxContentLength: options.maxContentLength || this.baseOptions.maxContentLength,
81 | maxBodyLength: options.maxBodyLength || this.baseOptions.maxBodyLength,
82 | maxRedirects: options.maxRedirects || this.baseOptions.maxRedirects,
83 | }
84 | }
85 |
86 | // eslint-disable-next-line max-statements
87 | async makeCall(url, payload, options) {
88 | const logger = this.getLogger(options)
89 | const headers = getHeaders(options, this.requestHeadersToProxy, this.baseOptions, payload)
90 | const httpsAgent = getHttpsAgent(options)
91 | const errorMessageKey = getErrorMessageKey(options, this.baseOptions)
92 | const metricsOptions = getMetricsOptions(options.metrics, url, this.metrics)
93 | try {
94 | const validateStatus = getValidateStatus(options)
95 | logger.trace({ baseURL: this.baseURL, url, headers, payload }, 'make call')
96 | const response = await this.axios({
97 | url,
98 | method: options.method,
99 | headers,
100 | data: getData(payload),
101 | responseType: getResponseType(options),
102 | params: options.query,
103 | ...validateStatus ? { validateStatus } : {},
104 | timeout: options.timeout,
105 | proxy: options.proxy,
106 | ...httpsAgent ? { httpsAgent } : {},
107 | ...this.getMaxValues(options),
108 | })
109 | const responseBody = {
110 | statusCode: response.status,
111 | headers: { ...response.headers.toJSON() },
112 | payload: response.data,
113 | duration: response.duration,
114 | }
115 | logger.trace({ url, ...responseBody }, 'response info')
116 | if (metricsOptions.enabled) {
117 | this.metrics.requestDuration.observe({
118 | method: options.method,
119 | url: metricsOptions.urlLabel,
120 | baseUrl: this.baseURL,
121 | statusCode: response.status,
122 | },
123 | response.duration,
124 | )
125 | }
126 | return responseBody
127 | } catch (error) {
128 | if (error.response) {
129 | const errorMessage = getErrorMessage(error.response, options.returnAs, errorMessageKey)
130 | const responseError = new Error(errorMessage)
131 | responseError.headers = { ...error.response.headers.toJSON() }
132 | responseError.statusCode = error.response.status
133 | responseError.payload = error.response.data
134 | responseError.duration = error.duration
135 | logger.error({ statusCode: error.response.status, message: errorMessage }, 'response error')
136 | if (metricsOptions.enabled) {
137 | this.metrics.requestDuration.observe({
138 | method: options.method,
139 | url: metricsOptions.urlLabel,
140 | baseUrl: this.baseURL,
141 | statusCode: error.response.status,
142 | },
143 | error.duration,
144 | )
145 | }
146 | throw responseError
147 | }
148 | const errToReturn = new Error(error.message)
149 | errToReturn.code = error.code
150 | // eslint-disable-next-line id-blacklist
151 | logger.error({ err: errToReturn }, 'generic request error')
152 | throw errToReturn
153 | }
154 | }
155 | }
156 |
157 | function getErrorMessageKey(options, baseOptions) {
158 | return options.errorMessageKey || baseOptions.errorMessageKey || 'message'
159 | }
160 |
161 | function getHttpsAgent({ cert, ca, key, httpsAgent }) {
162 | if (cert || ca || key) {
163 | return new httpsClient.Agent({ cert, key, ca })
164 | }
165 | if (httpsAgent) {
166 | return httpsAgent
167 | }
168 | }
169 |
170 | function getValidateStatus({ validateStatus }) {
171 | if (validateStatus && typeof validateStatus !== 'function') {
172 | throw new Error('validateStatus must be a function')
173 | }
174 | return validateStatus
175 | }
176 |
177 | function getData(payload) {
178 | if (payload === null) {
179 | return 'null'
180 | }
181 | if (payload === undefined) {
182 | return ''
183 | }
184 | return payload
185 | }
186 |
187 | const DEFAULT_ERROR_MESSAGE = 'Something went wrong'
188 | function getErrorMessage(response, returnAs, errorMessageKey) {
189 | const { headers, data } = response
190 | const contentType = headers['content-type']
191 | if (!contentType || !contentType.includes('application/json')) {
192 | return DEFAULT_ERROR_MESSAGE
193 | }
194 |
195 | switch (returnAs) {
196 | case 'BUFFER':
197 | try {
198 | return JSON.parse(data)[errorMessageKey]
199 | } catch (error) {
200 | return DEFAULT_ERROR_MESSAGE
201 | }
202 | case 'JSON':
203 | default:
204 | return data[errorMessageKey] || DEFAULT_ERROR_MESSAGE
205 | }
206 | }
207 |
208 | function getResponseType({ returnAs = 'JSON' }) {
209 | switch (returnAs) {
210 | case 'JSON':
211 | return 'json'
212 | case 'BUFFER':
213 | return 'arraybuffer'
214 | case 'STREAM':
215 | return 'stream'
216 | default:
217 | throw new Error(`Unknown returnAs: ${returnAs}`)
218 | }
219 | }
220 |
221 | function isMiaHeaderToInject(baseOptions, options) {
222 | if (options.isMiaHeaderInjected !== undefined) {
223 | return options.isMiaHeaderInjected
224 | }
225 | if (baseOptions.isMiaHeaderInjected === undefined && options.isMiaHeaderInjected === undefined) {
226 | return true
227 | }
228 | return baseOptions.isMiaHeaderInjected
229 | }
230 |
231 | function getHeaders(options, miaHeaders, baseOptions, payload) {
232 | const isMiaHeaderInjected = isMiaHeaderToInject(baseOptions, options)
233 | return {
234 | ...typeof payload === 'object' ? { 'content-type': 'application/json;charset=utf-8' } : {},
235 | ...payload === undefined ? { 'content-length': '0', 'content-type': 'text/html' } : {},
236 | ...isMiaHeaderInjected ? miaHeaders : {},
237 | ...baseOptions.headers || {},
238 | ...options.headers || {},
239 | }
240 | }
241 |
242 | function getMetricsOptions(metricsOptions = {}, url, metrics) {
243 | return {
244 | enabled: Boolean(metrics.requestDuration && metricsOptions.disabled !== true),
245 | urlLabel: metricsOptions.urlLabel ?? url,
246 | }
247 | }
248 |
249 | function decorateResponseWithDuration(axiosInstance) {
250 | axiosInstance.interceptors.response.use(
251 | (response) => {
252 | response.config.metadata.endTime = new Date()
253 | response.duration = response.config.metadata.endTime - response.config.metadata.startTime
254 | return response
255 | },
256 | (error) => {
257 | error.config.metadata.endTime = new Date()
258 | error.duration = error.config.metadata.endTime - error.config.metadata.startTime
259 | return Promise.reject(error)
260 | }
261 | )
262 |
263 | axiosInstance.interceptors.request.use(
264 | (config) => {
265 | config.metadata = { startTime: new Date() }
266 | return config
267 | }, (error) => {
268 | return Promise.reject(error)
269 | })
270 | }
271 |
272 | module.exports = HttpClient
273 |
274 |
--------------------------------------------------------------------------------
/lib/postDecorator.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const LEAVE_UNCHANGED_RESPONSE_STATUS_CODE = 204
20 |
21 | const {
22 | ABORT_CHAIN_STATUS_CODE,
23 | getUserIdFromBody,
24 | getUserPropertiesFromBody,
25 | getGroupsFromBody,
26 | getClientTypeFromBody,
27 | isFromBackOfficeFromBody,
28 | } = require('./decoratorsCommonFunctions')
29 |
30 | const POST_DECORATOR_SCHEMA = {
31 | body: {
32 | type: 'object',
33 | required: ['request', 'response'],
34 | properties: {
35 | request: {
36 | type: 'object',
37 | required: ['method', 'path', 'headers', 'query'],
38 | properties: {
39 | method: { type: 'string' },
40 | path: { type: 'string' },
41 | headers: { type: 'object' },
42 | query: { type: 'object' },
43 | body: { },
44 | },
45 | },
46 | response: {
47 | type: 'object',
48 | required: ['headers', 'statusCode'],
49 | properties: {
50 | headers: { type: 'object' },
51 | statusCode: { type: 'number' },
52 | body: { },
53 | },
54 | },
55 | },
56 | },
57 | }
58 | function getRequest() {
59 | return this.body.request
60 | }
61 | function getMethod() {
62 | return this.body.request.method
63 | }
64 | function getPath() {
65 | return this.body.request.path
66 | }
67 | function getHeaders() {
68 | return this.body.request.headers
69 | }
70 | function getQuery() {
71 | return this.body.request.query
72 | }
73 | function getBody() {
74 | return this.body.request.body
75 | }
76 | function getResponse() {
77 | return this.body.response
78 | }
79 | function getResponseBody() {
80 | return this.body.response.body
81 | }
82 | function getResponseHeaders() {
83 | return this.body.response.headers
84 | }
85 | function getResponseStatusCode() {
86 | return this.body.response.statusCode
87 | }
88 |
89 | async function postDecoratorHandler(request, reply) {
90 | const ret = await reply.context.config.handler(request, reply)
91 | if (ret === undefined) {
92 | reply.code(LEAVE_UNCHANGED_RESPONSE_STATUS_CODE).send()
93 | return
94 | }
95 | if (ret.type === 'CHANGE_RESPONSE') {
96 | return ret.getResponseBody()
97 | }
98 | if (ret.type === 'ABORT_CHAIN') {
99 | reply.code(ABORT_CHAIN_STATUS_CODE)
100 | return ret.getResponseBody()
101 | }
102 | reply.internalServerError('Unknown return type')
103 | }
104 |
105 | function change() {
106 | const data = {
107 | body: undefined,
108 | statusCode: undefined,
109 | headers: undefined,
110 | setBody(body) {
111 | data.body = body
112 | return data
113 | },
114 | setStatusCode(statusCode) {
115 | data.statusCode = statusCode
116 | return data
117 | },
118 | setHeaders(headers) {
119 | data.headers = headers
120 | return data
121 | },
122 | type: 'CHANGE_RESPONSE',
123 | getResponseBody() {
124 | const { body, statusCode, headers } = this
125 | return { body, statusCode, headers }
126 | },
127 | }
128 | return data
129 | }
130 |
131 | function leaveOriginalRequestUnmodified() {
132 | return undefined
133 | }
134 |
135 | function abortChain(statusCode, body, headers = {}) {
136 | return {
137 | type: 'ABORT_CHAIN',
138 | getResponseBody() {
139 | return {
140 | statusCode,
141 | body,
142 | headers,
143 | }
144 | },
145 | }
146 | }
147 |
148 | function addPostDecorator(path, handler) {
149 | this.register(function postDecoratorPlugin(fastify, opts, next) {
150 | fastify.decorateRequest('getOriginalRequest', getRequest)
151 | fastify.decorateRequest('getOriginalRequestMethod', getMethod)
152 | fastify.decorateRequest('getOriginalRequestPath', getPath)
153 | fastify.decorateRequest('getOriginalRequestHeaders', getHeaders)
154 | fastify.decorateRequest('getOriginalRequestQuery', getQuery)
155 | fastify.decorateRequest('getOriginalRequestBody', getBody)
156 |
157 | fastify.decorateRequest('getOriginalResponse', getResponse)
158 | fastify.decorateRequest('getOriginalResponseBody', getResponseBody)
159 | fastify.decorateRequest('getOriginalResponseHeaders', getResponseHeaders)
160 | fastify.decorateRequest('getOriginalResponseStatusCode', getResponseStatusCode)
161 |
162 | fastify.decorateRequest('getUserId', getUserIdFromBody)
163 | fastify.decorateRequest('getUserProperties', getUserPropertiesFromBody)
164 | fastify.decorateRequest('getGroups', getGroupsFromBody)
165 | fastify.decorateRequest('getClientType', getClientTypeFromBody)
166 | fastify.decorateRequest('isFromBackOffice', isFromBackOfficeFromBody)
167 |
168 | fastify.decorateRequest('changeOriginalResponse', change)
169 | fastify.decorateRequest('leaveOriginalResponseUnmodified', leaveOriginalRequestUnmodified)
170 | fastify.decorateRequest('abortChain', abortChain)
171 |
172 | fastify.post(path, {
173 | schema: POST_DECORATOR_SCHEMA,
174 | config: { handler: handler.bind(fastify) },
175 | }, postDecoratorHandler)
176 |
177 | next()
178 | })
179 | // chainable
180 | return this
181 | }
182 |
183 | addPostDecorator[Symbol.for('plugin-meta')] = {
184 | decorators: {
185 | request: ['USERID_HEADER_KEY', 'USER_PROPERTIES_HEADER_KEY', 'GROUPS_HEADER_KEY', 'CLIENTTYPE_HEADER_KEY', 'BACKOFFICE_HEADER_KEY'],
186 | },
187 | }
188 |
189 | module.exports = addPostDecorator
190 |
--------------------------------------------------------------------------------
/lib/preDecorator.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const LEAVE_UNCHANGED_REQUEST_STATUS_CODE = 204
20 |
21 | const {
22 | ABORT_CHAIN_STATUS_CODE,
23 | getUserIdFromBody,
24 | getUserPropertiesFromBody,
25 | getGroupsFromBody,
26 | getClientTypeFromBody,
27 | isFromBackOfficeFromBody,
28 | } = require('./decoratorsCommonFunctions')
29 |
30 | const PRE_DECORATOR_SCHEMA = {
31 | body: {
32 | type: 'object',
33 | required: ['method', 'path', 'headers', 'query'],
34 | properties: {
35 | method: { type: 'string' },
36 | path: { type: 'string' },
37 | headers: { type: 'object' },
38 | query: { type: 'object' },
39 | body: { },
40 | },
41 | },
42 | }
43 | function getRequest() {
44 | return this.body
45 | }
46 | function getMethod() {
47 | return this.body.method
48 | }
49 | function getPath() {
50 | return this.body.path
51 | }
52 | function getHeaders() {
53 | return this.body.headers
54 | }
55 | function getQuery() {
56 | return this.body.query
57 | }
58 | function getBody() {
59 | return this.body.body
60 | }
61 |
62 | async function preDecoratorHandler(request, reply) {
63 | const ret = await reply.context.config.handler(request, reply)
64 | if (ret === undefined) {
65 | reply.code(LEAVE_UNCHANGED_REQUEST_STATUS_CODE).send()
66 | return
67 | }
68 | if (ret.type === 'CHANGE_REQUEST') {
69 | return ret.getResponseBody()
70 | }
71 | if (ret.type === 'ABORT_CHAIN') {
72 | reply.code(ABORT_CHAIN_STATUS_CODE)
73 | return ret.getResponseBody()
74 | }
75 | reply.internalServerError('Unknown return type')
76 | }
77 |
78 | function change() {
79 | const data = {
80 | body: undefined,
81 | query: undefined,
82 | headers: undefined,
83 | setBody(body) {
84 | data.body = body
85 | return data
86 | },
87 | setQuery(query) {
88 | data.query = query
89 | return data
90 | },
91 | setHeaders(headers) {
92 | data.headers = headers
93 | return data
94 | },
95 | type: 'CHANGE_REQUEST',
96 | getResponseBody() {
97 | const { body, query, headers } = this
98 | return { body, query, headers }
99 | },
100 | }
101 | return data
102 | }
103 |
104 | function leaveOriginalRequestUnmodified() {
105 | return undefined
106 | }
107 |
108 | function abortChain(statusCode, body, headers = {}) {
109 | return {
110 | type: 'ABORT_CHAIN',
111 | getResponseBody() {
112 | return {
113 | statusCode,
114 | body,
115 | headers,
116 | }
117 | },
118 | }
119 | }
120 |
121 | function addPreDecorator(path, handler) {
122 | this.register(function preDecoratorPlugin(fastify, opts, next) {
123 | fastify.decorateRequest('getOriginalRequest', getRequest)
124 | fastify.decorateRequest('getOriginalRequestMethod', getMethod)
125 | fastify.decorateRequest('getOriginalRequestPath', getPath)
126 | fastify.decorateRequest('getOriginalRequestHeaders', getHeaders)
127 | fastify.decorateRequest('getOriginalRequestQuery', getQuery)
128 | fastify.decorateRequest('getOriginalRequestBody', getBody)
129 |
130 | fastify.decorateRequest('getUserId', getUserIdFromBody)
131 | fastify.decorateRequest('getUserProperties', getUserPropertiesFromBody)
132 | fastify.decorateRequest('getGroups', getGroupsFromBody)
133 | fastify.decorateRequest('getClientType', getClientTypeFromBody)
134 | fastify.decorateRequest('isFromBackOffice', isFromBackOfficeFromBody)
135 |
136 | fastify.decorateRequest('changeOriginalRequest', change)
137 | fastify.decorateRequest('leaveOriginalRequestUnmodified', leaveOriginalRequestUnmodified)
138 | fastify.decorateRequest('abortChain', abortChain)
139 |
140 | fastify.post(path, {
141 | schema: PRE_DECORATOR_SCHEMA,
142 | config: { handler: handler.bind(fastify) },
143 | }, preDecoratorHandler)
144 |
145 | next()
146 | })
147 | // chainable
148 | return this
149 | }
150 |
151 | addPreDecorator[Symbol.for('plugin-meta')] = {
152 | decorators: {
153 | request: ['USERID_HEADER_KEY', 'USER_PROPERTIES_HEADER_KEY', 'GROUPS_HEADER_KEY', 'CLIENTTYPE_HEADER_KEY', 'BACKOFFICE_HEADER_KEY'],
154 | },
155 | }
156 |
157 | module.exports = addPreDecorator
158 |
--------------------------------------------------------------------------------
/lib/rawCustomPlugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const { getUserId, getGroups, getClientType, isFromBackOffice, getUserProperties } = require('./util')
20 |
21 | function getUserIdFromHeader() {
22 | return getUserId(this.headers[this.USERID_HEADER_KEY])
23 | }
24 |
25 | function getUserPropertiesFromHeader() {
26 | return getUserProperties(this.headers[this.USER_PROPERTIES_HEADER_KEY])
27 | }
28 |
29 | function getGroupsFromHeaders() {
30 | return getGroups(this.headers[this.GROUPS_HEADER_KEY] || '')
31 | }
32 |
33 | function getClientTypeFromHeaders() {
34 | return getClientType(this.headers[this.CLIENTTYPE_HEADER_KEY])
35 | }
36 |
37 | function isFromBackOfficeFromHeaders() {
38 | return isFromBackOffice(this.headers[this.BACKOFFICE_HEADER_KEY])
39 | }
40 |
41 | function addRawCustomPlugin(method, path, handler, schema, advancedConfigs) {
42 | this.register(function rawCustomPlugin(fastify, options, next) {
43 | fastify.decorateRequest('getUserId', getUserIdFromHeader)
44 | fastify.decorateRequest('getUserProperties', getUserPropertiesFromHeader)
45 | fastify.decorateRequest('getGroups', getGroupsFromHeaders)
46 | fastify.decorateRequest('getClientType', getClientTypeFromHeaders)
47 | fastify.decorateRequest('isFromBackOffice', isFromBackOfficeFromHeaders)
48 |
49 | fastify.route({ ...advancedConfigs, method, path, handler, schema })
50 | next()
51 | })
52 | return this
53 | }
54 |
55 | addRawCustomPlugin[Symbol.for('plugin-meta')] = {
56 | decorators: {
57 | request: ['USERID_HEADER_KEY', 'USER_PROPERTIES_HEADER_KEY', 'GROUPS_HEADER_KEY', 'CLIENTTYPE_HEADER_KEY', 'BACKOFFICE_HEADER_KEY', 'ADDITIONAL_HEADERS_TO_PROXY'],
58 | },
59 | }
60 |
61 | module.exports = addRawCustomPlugin
62 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | function longerThan(len) {
20 | return function longer(arraylike) {
21 | return arraylike.length > len
22 | }
23 | }
24 |
25 | function getUserId(userId) {
26 | return userId || null
27 | }
28 |
29 | function getUserProperties(userPropertiesAsString) {
30 | if (!userPropertiesAsString) {
31 | return null
32 | }
33 |
34 | try {
35 | return JSON.parse(userPropertiesAsString)
36 | } catch (error) {
37 | return null
38 | }
39 | }
40 |
41 | function getGroups(groups) {
42 | return groups.split(',').filter(longerThan(0))
43 | }
44 |
45 | function getClientType(clientType) {
46 | return clientType || null
47 | }
48 |
49 | function isFromBackOffice(isFBO) {
50 | return Boolean(isFBO)
51 | }
52 |
53 | const extraHeadersKeys = [
54 | 'x-request-id',
55 | 'x-forwarded-for',
56 | 'x-forwarded-proto',
57 | 'x-forwarded-host',
58 | ]
59 |
60 | module.exports = {
61 | getUserId,
62 | getUserProperties,
63 | getGroups,
64 | getClientType,
65 | isFromBackOffice,
66 | extraHeadersKeys,
67 | }
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mia-platform/custom-plugin-lib",
3 | "version": "7.0.1",
4 | "description": "Library that allows you to define Mia-Platform custom plugins easily",
5 | "keywords": [
6 | "mia-platform",
7 | "plugin",
8 | "http-server"
9 | ],
10 | "homepage": "https://www.mia-platform.eu/",
11 | "bugs": {
12 | "url": "https://github.com/mia-platform/custom-plugin-lib/issues",
13 | "email": "core@mia-platform.eu"
14 | },
15 | "license": "Apache-2.0",
16 | "author": "Mia Platform Core Team ",
17 | "contributors": [
18 | "Tommaso Allevi ",
19 | "Luca Scannapieco ",
20 | "Paolo Sarti ",
21 | "Jacopo Andrea Giola ",
22 | "Davide Bianchi ",
23 | "Riccardo Di Benedetto ",
24 | "Luca Marzi ",
25 | "Fabio Percivaldi ",
26 | "Francesco Francomano "
27 | ],
28 | "main": "index.js",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/mia-platform/custom-plugin-lib.git"
32 | },
33 | "scripts": {
34 | "checkonly": "! grep -R '\\.only' tests/",
35 | "coverage": "npm run unit -- --coverage-report=text-summary",
36 | "postcoverage": "tap --coverage-report=html --no-browser",
37 | "lint": "eslint . --ignore-path .gitignore",
38 | "start": "echo 'unable to start the library directly' && exit 1",
39 | "test": "npm run lint && npm run unit && npm run checkonly && npm run typescript",
40 | "typescript": "tsc --project ./tests/types/tsconfig.json",
41 | "unit": "tap -b -o tap.log tests/*.test.js",
42 | "version": "./scripts/update-version.sh ${npm_package_version} && git add CHANGELOG.md"
43 | },
44 | "dependencies": {
45 | "@fastify/env": "^4.2.0",
46 | "@fastify/formbody": "^7.0.0",
47 | "@mia-platform/lc39": "^8.0.0",
48 | "@types/node": "^18.15.3",
49 | "ajv": "^8.12.0",
50 | "ajv-formats": "^2.1.1",
51 | "axios": "^1.7.9",
52 | "fastify-plugin": "^4.5.0",
53 | "http-errors": "^2.0.0",
54 | "pino": "^8.0.0",
55 | "simple-get": "^4.0.1"
56 | },
57 | "devDependencies": {
58 | "@fastify/routes": "^5.1.0",
59 | "@mia-platform/eslint-config-mia": "^3.0.0",
60 | "eslint": "^8.40.0",
61 | "hpagent": "^1.2.0",
62 | "nock": "^13.3.1",
63 | "proxy": "^2.0.1",
64 | "split2": "^4.2.0",
65 | "swagger-parser": "^10.0.3",
66 | "tap": "^16.3.4",
67 | "typescript": "^5.0.4"
68 | },
69 | "engines": {
70 | "node": ">=14"
71 | },
72 | "eslintConfig": {
73 | "extends": "@mia-platform/eslint-config-mia"
74 | },
75 | "tap": {
76 | "check-coverage": false
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/scripts/update-version.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # Copyright 2019 Mia srl
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 |
20 | umask 077
21 |
22 | # Automatically get the folder where the source files must be placed.
23 | __DIR=$(dirname "${0}")
24 | SOURCE_DIR="${__DIR}/../"
25 | TAG_VALUE="${1}"
26 |
27 | # Create a variable that contains the current date in UTC
28 | # Different flow if this script is running on Darwin or Linux machines.
29 | if [ "$(uname)" = "Darwin" ]; then
30 | NOW_DATE="$(date -u +%F)"
31 | else
32 | NOW_DATE="$(date -u -I)"
33 | fi
34 |
35 | sed -i.bck "s|## Unreleased|## Unreleased\n\n## v${TAG_VALUE} - ${NOW_DATE}|g" "${SOURCE_DIR}/CHANGELOG.md"
36 | rm -fr "${SOURCE_DIR}/CHANGELOG.md.bck"
37 |
--------------------------------------------------------------------------------
/tests/environment.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const tap = require('tap')
20 | const lc39 = require('@mia-platform/lc39')
21 |
22 | const USERID_HEADER_KEY = 'userid-header-key'
23 | const GROUPS_HEADER_KEY = 'groups-header-key'
24 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
25 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
26 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
27 | const baseEnv = {
28 | USERID_HEADER_KEY,
29 | GROUPS_HEADER_KEY,
30 | CLIENTTYPE_HEADER_KEY,
31 | BACKOFFICE_HEADER_KEY,
32 | MICROSERVICE_GATEWAY_SERVICE_NAME,
33 | }
34 |
35 | let fastify
36 | async function setupFastify(filePath, envVariables) {
37 | fastify = await lc39(filePath, {
38 | logLevel: 'silent',
39 | envVariables,
40 | })
41 |
42 | return fastify
43 | }
44 |
45 | function testEnvVariableIsNotEmptyString(test, envVariableName) {
46 | test.test(`Should fail on empty ${envVariableName}`, async assert => {
47 | try {
48 | await setupFastify('./tests/services/plain-custom-service.js', { ...baseEnv, [envVariableName]: '' })
49 | assert.fail()
50 | } catch (error) {
51 | assert.ok(error)
52 | }
53 | })
54 | }
55 |
56 | tap.test('Test Environment variables', test => {
57 | const headersToTest = ['USERID_HEADER_KEY', 'GROUPS_HEADER_KEY', 'CLIENTTYPE_HEADER_KEY', 'BACKOFFICE_HEADER_KEY', 'MICROSERVICE_GATEWAY_SERVICE_NAME']
58 | headersToTest.forEach(header => testEnvVariableIsNotEmptyString(test, header))
59 |
60 | tap.afterEach(async() => {
61 | if (fastify) { fastify.close() }
62 | })
63 |
64 | test.test('Should fail if required properties are missing', async assert => {
65 | const { ...badEnv } = baseEnv
66 | delete badEnv.USERID_HEADER_KEY
67 |
68 | try {
69 | await setupFastify('./tests/services/plain-custom-service.js', badEnv)
70 | assert.fail()
71 | } catch (error) {
72 | assert.ok(error)
73 | }
74 | })
75 |
76 | test.test('Should fail on invalid microservice gateway name (special characters)', async assert => {
77 | try {
78 | await setupFastify('./tests/services/plain-custom-service.js', {
79 | ...baseEnv,
80 | MICROSERVICE_GATEWAY_SERVICE_NAME: '%$£!"',
81 | })
82 | assert.fail()
83 | } catch (error) {
84 | assert.strictSame(error.message, 'env/MICROSERVICE_GATEWAY_SERVICE_NAME must match pattern "^(?=.{1,253}.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*.?$"')
85 | }
86 | })
87 |
88 | test.test('Should not fail when microservice gateway name is a valid IP', async assert => {
89 | const options = {
90 | ...baseEnv,
91 | MICROSERVICE_GATEWAY_SERVICE_NAME: '172.16.0.0',
92 | }
93 |
94 | await assert.resolves(async() => {
95 | await setupFastify('./tests/services/plain-custom-service.js', options)
96 | })
97 | })
98 |
99 | test.test('Should fail since BASE_REQUIRED_FIELD is not present and CONDITION_FIELD is true and CONDITION_TRUE_REQUIRED_FIELD is not present', async assert => {
100 | const env = {
101 | ...baseEnv,
102 | CONDITION_FIELD: true,
103 | }
104 |
105 | // NOTE: use try catch instead of assert.reject to customize error message assertion
106 | assert.plan(1)
107 | try {
108 | await setupFastify('./tests/services/if-then-else-env-validation-custom-service.js', env)
109 | } catch (error) {
110 | const errorMessage = 'env must have required property \'CONDITION_TRUE_REQUIRED_FIELD\', env must match "then" schema, env must have required property \'BASE_REQUIRED_FIELD\''
111 | assert.strictSame(error.message, errorMessage)
112 | }
113 | })
114 |
115 | test.test('Should fail since CONDITION_FIELD is false and CONDITION_FALSE_REQUIRED_FIELD is not present', async assert => {
116 | const env = {
117 | ...baseEnv,
118 | CONDITION_FIELD: false,
119 | }
120 |
121 | // NOTE: use try catch instead of assert.reject to customize error message assertion
122 | assert.plan(1)
123 | try {
124 | await setupFastify('./tests/services/if-then-else-env-validation-custom-service.js', env)
125 | } catch (error) {
126 | const errorMessage = 'env must have required property \'CONDITION_FALSE_REQUIRED_FIELD\', env must match "else" schema, env must have required property \'BASE_REQUIRED_FIELD\''
127 | assert.strictSame(error.message, errorMessage)
128 | }
129 | })
130 |
131 | test.test('Should pass since CONDITION_FIELD is true and CONDITION_TRUE_REQUIRED_FIELD is present', async assert => {
132 | const env = {
133 | ...baseEnv,
134 | BASE_REQUIRED_FIELD: 'some-value',
135 | CONDITION_FIELD: true,
136 | CONDITION_TRUE_REQUIRED_FIELD: 'some-value',
137 | }
138 |
139 | await assert.resolves(async() => {
140 | await setupFastify('./tests/services/if-then-else-env-validation-custom-service.js', env)
141 | })
142 | })
143 |
144 | test.test('Should fail since none of the anyOf required fields are present', async assert => {
145 | // NOTE: use try catch instead of assert.reject to customize error message assertion
146 | assert.plan(1)
147 | try {
148 | await setupFastify('./tests/services/any-of-env-validation-custom-service.js', baseEnv)
149 | } catch (error) {
150 | const errorMessage = 'env must have required property \'ANY_OF_REQUIRED_FIELD_1\', env must have required property \'ANY_OF_REQUIRED_FIELD_2\', env must match a schema in anyOf'
151 | assert.strictSame(error.message, errorMessage)
152 | }
153 | })
154 |
155 | test.test('Should pass since one of the anyOf required fields is present', async assert => {
156 | const env = {
157 | ...baseEnv,
158 | ANY_OF_REQUIRED_FIELD_1: 'some-value',
159 | }
160 |
161 | await assert.resolves(async() => {
162 | await setupFastify('./tests/services/any-of-env-validation-custom-service.js', env)
163 | })
164 | })
165 |
166 | test.test('Should fail since not all of the allOf required fields are present', async assert => {
167 | const env = {
168 | ...baseEnv,
169 | ALL_OF_REQUIRED_FIELD_1: 'some-value',
170 | }
171 |
172 | // NOTE: use try catch instead of assert.reject to customize error message assertion
173 | assert.plan(1)
174 | try {
175 | await setupFastify('./tests/services/all-of-env-validation-custom-service.js', env)
176 | } catch (error) {
177 | const errorMessage = 'env must have required property \'ALL_OF_REQUIRED_FIELD_2\''
178 | assert.strictSame(error.message, errorMessage)
179 | }
180 | })
181 |
182 | test.test('Should pass since all of the allOf required fields are present', async assert => {
183 | const env = {
184 | ...baseEnv,
185 | ALL_OF_REQUIRED_FIELD_1: 'some-value',
186 | ALL_OF_REQUIRED_FIELD_2: 'some-value',
187 | }
188 |
189 | await assert.resolves(async() => {
190 | await setupFastify('./tests/services/all-of-env-validation-custom-service.js', env)
191 | })
192 | })
193 |
194 | test.test('Should fail since all of the oneOf required fields are present', async assert => {
195 | const env = {
196 | ...baseEnv,
197 | ONE_OF_REQUIRED_FIELD_1: 'some-value',
198 | ONE_OF_REQUIRED_FIELD_2: 'some-value',
199 | }
200 |
201 | // NOTE: use try catch instead of assert.reject to customize error message assertion
202 | assert.plan(1)
203 | try {
204 | await setupFastify('./tests/services/one-of-env-validation-custom-service.js', env)
205 | } catch (error) {
206 | const errorMessage = 'env must match exactly one schema in oneOf'
207 | assert.strictSame(error.message, errorMessage)
208 | }
209 | })
210 |
211 | test.test('Should pass since only one of the oneOf required fields is present', async assert => {
212 | const env = {
213 | ...baseEnv,
214 | ONE_OF_REQUIRED_FIELD_1: 'some-value',
215 | }
216 |
217 | await assert.resolves(async() => {
218 | await setupFastify('./tests/services/one-of-env-validation-custom-service.js', env)
219 | })
220 | })
221 |
222 | test.test('Should fail since the env has properties already present in the baseEnv of the lib', async assert => {
223 | // NOTE: use try catch instead of assert.reject to customize error message assertion
224 | assert.plan(1)
225 | try {
226 | await setupFastify('./tests/services/overlapping-env-validation-custom-service.js', baseEnv)
227 | } catch (error) {
228 | const errorMessage = 'The provided Environment JSON Schema includes properties declared in the Base JSON Schema of the custom-plugin-lib, please remove them from your schema. The properties to remove are: USERID_HEADER_KEY, USER_PROPERTIES_HEADER_KEY, GROUPS_HEADER_KEY, CLIENTTYPE_HEADER_KEY'
229 | assert.strictSame(error.message, errorMessage)
230 | }
231 | })
232 |
233 | test.test('Should pass since all of the required fields are present', async assert => {
234 | const env = {
235 | ...baseEnv,
236 | MY_REQUIRED_ENV_VAR: 'value',
237 | }
238 |
239 | await assert.resolves(async() => {
240 | await setupFastify('./tests/services/advanced-first-level-properties-service.js', env)
241 | })
242 | })
243 |
244 | test.test('Should fail since one of the required fields is not present', async assert => {
245 | // NOTE: use try catch instead of assert.reject to customize error message assertion
246 | assert.plan(1)
247 | try {
248 | await setupFastify('./tests/services/advanced-first-level-properties-service.js', baseEnv)
249 | } catch (error) {
250 | const errorMessage = 'env must have required property \'MY_REQUIRED_ENV_VAR\''
251 | assert.strictSame(error.message, errorMessage)
252 | }
253 | })
254 |
255 | test.end()
256 | })
257 |
--------------------------------------------------------------------------------
/tests/fixtures/keys/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDuzCCAqOgAwIBAgIUEtAjKOuAYiTSm3ye/Fg+y/rLljAwDQYJKoZIhvcNAQEL
3 | BQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9IMQ8wDQYDVQQHDAZEYXl0b24x
4 | DzANBgNVBAoMBk15IEluYzEPMA0GA1UECwwGRGV2T3BzMR0wGwYJKoZIhvcNAQkB
5 | Fg5yb290QGxvY2FsaG9zdDAgFw0yMTA0MDgwODM1MTdaGA8zMDYxMDkwMzA4MzUx
6 | N1owbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9IMQ8wDQYDVQQHDAZEYXl0b24x
7 | DzANBgNVBAoMBk15IEluYzEPMA0GA1UECwwGRGV2T3BzMR0wGwYJKoZIhvcNAQkB
8 | Fg5yb290QGxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
9 | AKJPrHFRwjcRtD5aHIdDYc2QGJZaIr5ig6G06w1z/kR+msSZnLlhja0WOzWQ5Q+x
10 | Rj+ShHqFZosp3uEnTP0xTOIdK0FMgGddr+Xcm7dK17bR8faGSHdt0VelqEJsFFWz
11 | 7oNq8bU511cLphHjcmv91jV6iSLlCiN9mDQHSSI77EId5o/3MIDM0VKZ5AR1g+ey
12 | 7FFe7+HWVnUijr4dReJS3RTWqBXMpPVxUXjXwCiKUuYUdycRUAX4MZkb0t/oKe54
13 | 0UTQWS2ghSqub1Oas/B/tA05RkH4QZe/l1i036eHYkA0Go75otLkU01lAVERC8pa
14 | EWe/8gHomggtQf1zmF23l00CAwEAAaNTMFEwHQYDVR0OBBYEFHKGvr7WIYQ3youZ
15 | zYPPmUm1zX8BMB8GA1UdIwQYMBaAFHKGvr7WIYQ3youZzYPPmUm1zX8BMA8GA1Ud
16 | EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFvHvZsMSDscDLHU3Oal6Mp9
17 | BE5IkeHI/sFYWfEBNT56b8tmF491bnl2FeC+/k569/gnXu6cmfms4Mrk34j+fya4
18 | VnB8ws/HRw6RwZRhZZT4m+3F1aSzYNX9uvbUJL4PcmaoMZzlRQ1V3XSB2DKq588L
19 | wkbOIWpt+hn+pfTkc2RKi9/ZZnJ+bLtrIVYqKSVGk33INjR/jmYnR9DpP/+acrdM
20 | 8kq5PD3WKEC1IsI2iD7xdi5k0/dcuzApeTRdkV1DQ5VuSo78xVufKv7qwE760OR4
21 | j/sCP9GPZ1zvu1fjwD6hdGX67cZDDCuy0lAxG+2b5i1+3T82Z6MV6SYCLs3V6JY=
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/tests/fixtures/keys/client.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC8jCCAdoCFGWyAxRiRI3BbW+PA+12rbyFFnk0MA0GCSqGSIb3DQEBCwUAMGwx
3 | CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJPSDEPMA0GA1UEBwwGRGF5dG9uMQ8wDQYD
4 | VQQKDAZNeSBJbmMxDzANBgNVBAsMBkRldk9wczEdMBsGCSqGSIb3DQEJARYOcm9v
5 | dEBsb2NhbGhvc3QwIBcNMjEwNDA4MDg0MDQ0WhgPMzA2MTA5MDMwODQwNDRaMIGA
6 | MQswCQYDVQQGEwJVUzELMAkGA1UECAwCT0gxDzANBgNVBAcMBkRheXRvbjEPMA0G
7 | A1UECgwGTXkgSW5jMQ8wDQYDVQQLDAZEZXZPcHMxEjAQBgNVBAMMCWxvY2FsaG9z
8 | dDEdMBsGCSqGSIb3DQEJARYOcm9vdEBsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB
9 | BQADgY0AMIGJAoGBAOpHKvIIKgE9QQ7QRsdViX6YPJ0KgcB+j7fypr3o5JuOCIy8
10 | YOsyGCm8ltgrA1coyaaxfXe2aEB1nlI5RbK8MaIZldIyLU7ib6gz3WeBgoEwYIBV
11 | pUUQ9PM+mPKoh8zag3DKeBC7Uo5bMcaLVStBO5/cHPdlnwxtPJp/kuqik9spAgMB
12 | AAEwDQYJKoZIhvcNAQELBQADggEBABfZe7SazqAks9/zt1jduchf70n0HgZyubao
13 | DjzCcC5YwYR/zBpJI9WvXobg4OuK7zUm+Jt1ekxu7smwZ3IQH5mwtuajvoAu6Nhb
14 | T2pIyxlvGUxDksSEUjE9HGOM0I9ueFZrWZtlJp3ZfyH5/kQd5hp4mYEmf5MRAH36
15 | ROfEMPmeU4vhQOZ6sbXDUK/AUUirYp2yA/hOkAsQCbtKjshJRJbYhBtdmxe0DFlq
16 | 1AcfI4aIcIEpDVImvBQmlbWIpJJY7NpuWrSvgVY4cFW4A21FmXB2kP5IqVfMd7NC
17 | UR3qOvShvVndDp7BrDUt3d7Y2hHi0T05/xRW0BeKOEXkAMi04Fc=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/tests/fixtures/keys/client.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOpHKvIIKgE9QQ7Q
3 | RsdViX6YPJ0KgcB+j7fypr3o5JuOCIy8YOsyGCm8ltgrA1coyaaxfXe2aEB1nlI5
4 | RbK8MaIZldIyLU7ib6gz3WeBgoEwYIBVpUUQ9PM+mPKoh8zag3DKeBC7Uo5bMcaL
5 | VStBO5/cHPdlnwxtPJp/kuqik9spAgMBAAECgYAAxpWM0ZwZ/vLtMqFZXZ4j7isd
6 | NgCx+3lMWaBVtqSzIyhCFwmXHcZYtQgm1RWgv0DTrXjF4bO8qucITKo/akIbzqyR
7 | lQm2YP2Epw/BULUiGicD+o5I+Mp6pt+SdDLtuWFIYaQe4PDJKtRrAD3znnQdnads
8 | gddyE+ZzMwShe1EGtQJBAPspNdyGW9dEG4lbQlGIZrfSAehAnnkPKKlwIEWdnCmf
9 | 0oCq2XfItNuaV14dRguXLL30Z+lt8EskdtMC3hE4mU8CQQDuyq+smqkSXrLpu4HW
10 | 9/fsB9nFj62VAZUUTi/ay5uuhYWanDvpGHgI7cD6i9HbHeEakouh/6gcwLjyXek8
11 | /zYHAkEA76cE8+VoYOFl/QyNHUsv6BqFI131WMI0JGkPAh4/0h/kNk3Pr9JdcGW3
12 | E+CO46ReVuqM9FmovaqPjwzfZENDVQJBAJHV9a5LkoBml4wJ+UUpkh9zt/thQVjk
13 | a4DIM4/Dk+PlJfCEBE7ao8yIL7iUlejlean54A9jDO/qf8l9s0mOcBcCQFNQQu/g
14 | byiPspJrsCriNhGxFJ/fVubR3l+p+eUDwjMY5JTkdCN6sN/sChL9v5EE0s2eHoBL
15 | bUEWt61lERTeKrI=
16 | -----END PRIVATE KEY-----
17 |
--------------------------------------------------------------------------------
/tests/fixtures/keys/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC8jCCAdoCFGWyAxRiRI3BbW+PA+12rbyFFnkzMA0GCSqGSIb3DQEBCwUAMGwx
3 | CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJPSDEPMA0GA1UEBwwGRGF5dG9uMQ8wDQYD
4 | VQQKDAZNeSBJbmMxDzANBgNVBAsMBkRldk9wczEdMBsGCSqGSIb3DQEJARYOcm9v
5 | dEBsb2NhbGhvc3QwIBcNMjEwNDA4MDgzNzAwWhgPMzA2MTA5MDMwODM3MDBaMIGA
6 | MQswCQYDVQQGEwJVUzELMAkGA1UECAwCT0gxDzANBgNVBAcMBkRheXRvbjEPMA0G
7 | A1UECgwGTXkgSW5jMQ8wDQYDVQQLDAZEZXZPcHMxEjAQBgNVBAMMCWxvY2FsaG9z
8 | dDEdMBsGCSqGSIb3DQEJARYOcm9vdEBsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB
9 | BQADgY0AMIGJAoGBANA65xPRecdouGwVQTxZwdmT2DjGEpsstrr4IBbjjki3H1Sm
10 | qcLfTuPR/8Bb/8kRB2qszu8M6YURHo6J+FOfP9zumk/cJmxfQWJxuO/NmS/jbYM7
11 | fXZGHkbPNJK3J0Ej+tYipwi2yNESmEWDI3GqK+0Wf6fChI3YjOiehl7o355BAgMB
12 | AAEwDQYJKoZIhvcNAQELBQADggEBABrmIejE1CFKXMZbTO9P7prKIQS5VJAxzfiF
13 | aCeBjW98W0BuA1EVyZI55dphZOnAv6gnKJ7ln0pkaRzBou7TJddpshMxfysALAin
14 | lhT8+IfsrJ7VsCJpzOmh63h8iUDiMCHu+Y73Js/EJMwvwUxJTmHvC/aTHopJAFit
15 | w0KUssggBaIEKmxQm/EXcamcjJOy7o0/gXYCppvztY7iOHTDsLgtemctAHW5SyB4
16 | O4JwS8cr9uSyUq0vUkXXOQIM+v0ciHs1yMtXrTj1tbrube6WqOS8iv6u7J0RuTDS
17 | To5DMjWYz00htbpYy74xE6NDP9Mguqi4EFfPnyLM8eDllO8zKew=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/tests/fixtures/keys/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBANA65xPRecdouGwV
3 | QTxZwdmT2DjGEpsstrr4IBbjjki3H1SmqcLfTuPR/8Bb/8kRB2qszu8M6YURHo6J
4 | +FOfP9zumk/cJmxfQWJxuO/NmS/jbYM7fXZGHkbPNJK3J0Ej+tYipwi2yNESmEWD
5 | I3GqK+0Wf6fChI3YjOiehl7o355BAgMBAAECgYAFi1v1wiEc9yPZ9OLsyXMI6Q/b
6 | yyJZsWIYLl0kXuC9/Oo/pcRlZO7D0CagJ5Ly9poc9Ab6hHx/R4ppvzC2gUoA+qfJ
7 | zlkblN6cIKdh9VaGvI17UZZzBBUiHN8AatFAXFdXm5w7vY8mAHXpRd4a5Y2BKQml
8 | 5g8dNNmEYRzfA+pwFQJBAO9lINd+YZRA1Q9FNeR+X4axvkgs3LBPHbs63bokkJIk
9 | 4iou/XJomtjAVYZSF8daOvtubXUrKagC/QPEIdO/IYsCQQDerGMUErT5VTOQAJ6q
10 | GOVUVtryLNyysC1U8uqP6SMPKlDXe86PzGXQ0Mm65E+FKZ0xsB5TEZ0dzRPbIBxm
11 | CqDjAkAkYdIj7ekWhuPadkJCf5I0/j5U6byAbwWttryL1ZLDIyfcEVgjUxJ1boWQ
12 | 7FkAyw27uISaEf06s3mQYPZjH+ERAkBTubnfWFFX1uN2Z+VAy++e0LGukZPCVGAX
13 | KudriRu3ng+bll/Kze4SoA7aCPKlfUov3qroTR0okW2/3MkQzTpFAj8aiUoESQ6d
14 | UT0X+Wviikjb8f5PWMmE5/rXrh/ayZWGDIUIaSvpQ4JZJuKzUVD9fWCXS3tVNUDK
15 | kq54XfysiwY=
16 | -----END PRIVATE KEY-----
17 |
--------------------------------------------------------------------------------
/tests/getHeadersToProxy.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const tap = require('tap')
20 | const lc39 = require('@mia-platform/lc39')
21 |
22 | const USERID_HEADER_KEY = 'userid-header-key'
23 | const GROUPS_HEADER_KEY = 'groups-header-key'
24 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
25 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
26 | const USER_PROPERTIES_HEADER_KEY = 'userproperties-header-key'
27 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
28 | const ADDITIONAL_HEADERS_TO_PROXY = 'additionalheader1,additionalheader2'
29 |
30 | const X_REQUEST_ID_HEADER_KEY = 'x-request-id'
31 | const X_FORWARDED_FOR_HEADER_KEY = 'x-forwarded-for'
32 | const X_FORWARDED_PROTO_HEADER_KEY = 'x-forwarded-proto'
33 | const X_FORWARDED_HOST_HEADER_KEY = 'x-forwarded-host'
34 |
35 | const baseEnv = {
36 | USERID_HEADER_KEY,
37 | GROUPS_HEADER_KEY,
38 | CLIENTTYPE_HEADER_KEY,
39 | BACKOFFICE_HEADER_KEY,
40 | USER_PROPERTIES_HEADER_KEY,
41 | MICROSERVICE_GATEWAY_SERVICE_NAME,
42 | }
43 |
44 | async function setupFastify(filePath, envVariables) {
45 | const fastify = await lc39(filePath, {
46 | logLevel: 'silent',
47 | envVariables,
48 | })
49 |
50 | return fastify
51 | }
52 |
53 | const headers = {
54 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
55 | [USERID_HEADER_KEY]: 'userid',
56 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
57 | [BACKOFFICE_HEADER_KEY]: '',
58 | [USER_PROPERTIES_HEADER_KEY]: '{"foo":"bar"}',
59 | [X_REQUEST_ID_HEADER_KEY]: 'request-id',
60 | [X_FORWARDED_FOR_HEADER_KEY]: '8.8.8.8, 10.0.0.1, 172.16.0.1, 192.168.0.1',
61 | [X_FORWARDED_PROTO_HEADER_KEY]: 'https',
62 | [X_FORWARDED_HOST_HEADER_KEY]: 'www.hostname.tld',
63 | donotproxy: 'foo',
64 | additionalheader1: 'bar',
65 | }
66 |
67 | tap.test('getHeadersToProxy basic', async assert => {
68 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', baseEnv)
69 | const response = await fastify.inject({
70 | method: 'POST',
71 | url: '/default',
72 | headers,
73 | })
74 |
75 | assert.equal(response.statusCode, 200)
76 | assert.strictSame(JSON.parse(response.payload), {
77 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
78 | [USERID_HEADER_KEY]: 'userid',
79 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
80 | [USER_PROPERTIES_HEADER_KEY]: '{"foo":"bar"}',
81 | [X_REQUEST_ID_HEADER_KEY]: 'request-id',
82 | [X_FORWARDED_FOR_HEADER_KEY]: '8.8.8.8, 10.0.0.1, 172.16.0.1, 192.168.0.1',
83 | [X_FORWARDED_PROTO_HEADER_KEY]: 'https',
84 | [X_FORWARDED_HOST_HEADER_KEY]: 'www.hostname.tld',
85 | })
86 | })
87 |
88 | tap.test('getHeadersToProxy with isMiaHeaderInjected to false', async assert => {
89 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', baseEnv)
90 | const response = await fastify.inject({
91 | method: 'POST',
92 | payload: {
93 | options: {
94 | isMiaHeaderInjected: false,
95 | },
96 | },
97 | url: '/default',
98 | headers,
99 | })
100 |
101 | assert.equal(response.statusCode, 200)
102 | assert.strictSame(JSON.parse(response.payload), {})
103 | })
104 |
105 | tap.test('getHeadersToProxy - with isMiaHeaderInjected to false and additional headers to proxy', async assert => {
106 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', {
107 | ...baseEnv,
108 | ADDITIONAL_HEADERS_TO_PROXY,
109 | })
110 | const response = await fastify.inject({
111 | method: 'POST',
112 | payload: {
113 | options: {
114 | isMiaHeaderInjected: false,
115 | },
116 | },
117 | url: '/default',
118 | headers,
119 | })
120 |
121 | assert.equal(response.statusCode, 200)
122 | assert.strictSame(JSON.parse(response.payload), {
123 | additionalheader1: 'bar',
124 | })
125 | })
126 |
127 | tap.test('getHeadersToProxy - additional headers to proxy', async assert => {
128 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', {
129 | ...baseEnv,
130 | ADDITIONAL_HEADERS_TO_PROXY,
131 | })
132 | const response = await fastify.inject({
133 | method: 'POST',
134 | url: '/default',
135 | headers,
136 | })
137 |
138 | assert.equal(response.statusCode, 200)
139 | assert.strictSame(JSON.parse(response.payload), {
140 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
141 | [USERID_HEADER_KEY]: 'userid',
142 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
143 | [USER_PROPERTIES_HEADER_KEY]: '{"foo":"bar"}',
144 | [X_REQUEST_ID_HEADER_KEY]: 'request-id',
145 | [X_FORWARDED_FOR_HEADER_KEY]: '8.8.8.8, 10.0.0.1, 172.16.0.1, 192.168.0.1',
146 | [X_FORWARDED_PROTO_HEADER_KEY]: 'https',
147 | [X_FORWARDED_HOST_HEADER_KEY]: 'www.hostname.tld',
148 | additionalheader1: 'bar',
149 | })
150 | })
151 |
152 | tap.test('getHeadersToProxy - no request headers', async assert => {
153 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', {
154 | ...baseEnv,
155 | ADDITIONAL_HEADERS_TO_PROXY,
156 | })
157 | const response = await fastify.inject({
158 | method: 'POST',
159 | url: '/default',
160 | })
161 |
162 | assert.equal(response.statusCode, 200)
163 | assert.strictSame(JSON.parse(response.payload), {})
164 | })
165 |
166 | tap.test('getHeadersToProxy - backoffice header to true', async assert => {
167 | const fastify = await setupFastify('./tests/services/get-headers-to-proxy.js', {
168 | ...baseEnv,
169 | ADDITIONAL_HEADERS_TO_PROXY,
170 | })
171 | const response = await fastify.inject({
172 | method: 'POST',
173 | url: '/default',
174 | headers: {
175 | [BACKOFFICE_HEADER_KEY]: '1',
176 | },
177 | })
178 |
179 | assert.equal(response.statusCode, 200)
180 | assert.strictSame(JSON.parse(response.payload), {
181 | [BACKOFFICE_HEADER_KEY]: '1',
182 | })
183 | })
184 |
--------------------------------------------------------------------------------
/tests/getHttpClient.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const tap = require('tap')
4 | const nock = require('nock')
5 |
6 | const { getHttpClient } = require('../index')
7 |
8 | const MY_AWESOME_SERVICE_PROXY_HTTP_URL = 'http://my-awesome-service'
9 | const MY_AWESOME_SERVICE_PROXY_HTTPS_URL = 'https://my-awesome-service'
10 | const MY_AWESOME_SERVICE_PROXY_HTTP_URL_CUSTOM_PORT = 'http://my-awesome-service:3000'
11 | const MY_AWESOME_SERVICE_PROXY_HTTPS_URL_CUSTOM_PORT = 'https://my-awesome-service:3001'
12 |
13 | const fastifyMock = {
14 | httpClientMetrics: {
15 | requestDuration: {
16 | observe: () => { /* no-op*/ },
17 | },
18 | },
19 | }
20 |
21 | tap.test('getHttpClient available for testing - complete url passed', async t => {
22 | nock.disableNetConnect()
23 | t.teardown(() => {
24 | nock.enableNetConnect()
25 | })
26 |
27 | const RETURN_MESSAGE = 'OK'
28 | const customProxy = getHttpClient.call(fastifyMock, MY_AWESOME_SERVICE_PROXY_HTTP_URL)
29 | const awesomeHttpServiceScope = nock(`${MY_AWESOME_SERVICE_PROXY_HTTP_URL}:80`)
30 | .get('/test-endpoint')
31 | .reply(200, {
32 | message: RETURN_MESSAGE,
33 | })
34 |
35 | const result = await customProxy.get('/test-endpoint')
36 |
37 | t.strictSame(result.statusCode, 200)
38 | t.strictSame(result.payload.message, RETURN_MESSAGE)
39 | awesomeHttpServiceScope.done()
40 | })
41 |
42 | tap.test('getHttpClient available for testing - timeout passed', async t => {
43 | nock.disableNetConnect()
44 | t.teardown(() => {
45 | nock.enableNetConnect()
46 | })
47 |
48 | const RETURN_MESSAGE = 'OK'
49 | const customProxy = getHttpClient.call(fastifyMock, MY_AWESOME_SERVICE_PROXY_HTTP_URL, {
50 | timeout: 100,
51 | })
52 | const awesomeHttpServiceScope = nock(`${MY_AWESOME_SERVICE_PROXY_HTTP_URL}:80`)
53 | .get('/test-endpoint')
54 | .delay(101)
55 | .reply(200, {
56 | message: RETURN_MESSAGE,
57 | })
58 |
59 | try {
60 | await customProxy.get('/test-endpoint')
61 | t.fail('should not reach this point')
62 | } catch (error) {
63 | t.strictSame(error.message, 'timeout of 100ms exceeded')
64 | }
65 |
66 | awesomeHttpServiceScope.done()
67 | })
68 |
69 | tap.test('getHttpClient available for testing - https url', async t => {
70 | nock.disableNetConnect()
71 | t.teardown(() => {
72 | nock.enableNetConnect()
73 | })
74 |
75 | const RETURN_MESSAGE = 'OK'
76 | const customProxy = getHttpClient.call(fastifyMock, MY_AWESOME_SERVICE_PROXY_HTTPS_URL)
77 | const awesomeHttpsServiceScope = nock(`${MY_AWESOME_SERVICE_PROXY_HTTPS_URL}:443`)
78 | .get('/test-endpoint')
79 | .reply(200, {
80 | message: RETURN_MESSAGE,
81 | })
82 |
83 | const result = await customProxy.get('/test-endpoint')
84 |
85 | t.strictSame(result.statusCode, 200)
86 | t.strictSame(result.payload.message, RETURN_MESSAGE)
87 | awesomeHttpsServiceScope.done()
88 | })
89 |
90 | tap.test('getHttpClient available for testing - custom port 3000 - custom headers', async t => {
91 | nock.disableNetConnect()
92 | t.teardown(() => {
93 | nock.enableNetConnect()
94 | })
95 |
96 | const RETURN_MESSAGE = 'OK'
97 | const customProxy = getHttpClient.call(fastifyMock, MY_AWESOME_SERVICE_PROXY_HTTP_URL_CUSTOM_PORT,
98 | {
99 | headers: {
100 | 'test-header': 'test header works',
101 | },
102 | })
103 | const awesomeHttpServiceScope = nock(`${MY_AWESOME_SERVICE_PROXY_HTTP_URL}:3000`)
104 | .matchHeader('test-header', 'test header works')
105 | .get('/test-endpoint')
106 | .reply(200, {
107 | message: RETURN_MESSAGE,
108 | })
109 |
110 | const result = await customProxy.get('/test-endpoint')
111 |
112 | t.strictSame(result.statusCode, 200)
113 | t.strictSame(result.payload.message, RETURN_MESSAGE)
114 | awesomeHttpServiceScope.done()
115 | })
116 |
117 | tap.test('getHttpClient available for testing - https url - custom port 3001', async t => {
118 | nock.disableNetConnect()
119 | t.teardown(() => {
120 | nock.enableNetConnect()
121 | })
122 |
123 | const RETURN_MESSAGE = 'OK'
124 | const customProxy = getHttpClient.call(fastifyMock, MY_AWESOME_SERVICE_PROXY_HTTPS_URL_CUSTOM_PORT)
125 | const awesomeHttpsServiceScope = nock(`${MY_AWESOME_SERVICE_PROXY_HTTPS_URL}:3001`)
126 | .get('/test-endpoint')
127 | .reply(200, {
128 | message: RETURN_MESSAGE,
129 | })
130 |
131 | const result = await customProxy.get('/test-endpoint')
132 |
133 | t.strictSame(result.statusCode, 200)
134 | t.strictSame(result.payload.message, RETURN_MESSAGE)
135 | awesomeHttpsServiceScope.done()
136 | })
137 |
138 | tap.test('getHttpClient throws on invalid url', async t => {
139 | const invalidUrl = 'httpnot-a-complete-url'
140 | try {
141 | getHttpClient.call(fastifyMock, invalidUrl)
142 | } catch (error) {
143 | t.notOk(true, 'The function should not throw anymore if the url is not a valid one, bet return the standard proxy')
144 | }
145 | })
146 |
--------------------------------------------------------------------------------
/tests/oas.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const tap = require('tap')
4 | const lc39 = require('@mia-platform/lc39')
5 | const Swagger = require('swagger-parser')
6 |
7 | async function setupFastify(filePath, envVariables) {
8 | return lc39(filePath, {
9 | logLevel: 'silent',
10 | envVariables,
11 | })
12 | }
13 |
14 | const USERID_HEADER_KEY = 'userid-header-key'
15 | const GROUPS_HEADER_KEY = 'groups-header-key'
16 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
17 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
18 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
19 |
20 | const baseEnv = {
21 | USERID_HEADER_KEY,
22 | GROUPS_HEADER_KEY,
23 | CLIENTTYPE_HEADER_KEY,
24 | BACKOFFICE_HEADER_KEY,
25 | MICROSERVICE_GATEWAY_SERVICE_NAME,
26 | }
27 |
28 | tap.test('create a valid docs with the support of $ref schema', async t => {
29 | const fastify = await setupFastify('./tests/services/plain-custom-service.js', baseEnv)
30 |
31 | const res = await fastify.inject({
32 | method: 'GET',
33 | url: '/documentation/json',
34 | })
35 |
36 | await Swagger.validate(JSON.parse(res.payload))
37 |
38 | t.end()
39 | })
40 |
41 |
42 | tap.test('create a valid docs with custom vocabulary', async t => {
43 | const fastify = await setupFastify('./tests/services/service-with-formats.js', baseEnv)
44 |
45 | const res = await fastify.inject({
46 | method: 'GET',
47 | url: '/documentation/json',
48 | })
49 |
50 | await Swagger.validate(JSON.parse(res.payload))
51 |
52 | t.end()
53 | })
54 |
--------------------------------------------------------------------------------
/tests/postDecorator.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const tap = require('tap')
20 | const lc39 = require('@mia-platform/lc39')
21 |
22 | const USERID_HEADER_KEY = 'userid-header-key'
23 | const USER_PROPERTIES_HEADER_KEY = 'miauserproperties'
24 | const GROUPS_HEADER_KEY = 'groups-header-key'
25 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
26 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
27 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
28 | const baseEnv = {
29 | USERID_HEADER_KEY,
30 | USER_PROPERTIES_HEADER_KEY,
31 | GROUPS_HEADER_KEY,
32 | CLIENTTYPE_HEADER_KEY,
33 | BACKOFFICE_HEADER_KEY,
34 | MICROSERVICE_GATEWAY_SERVICE_NAME,
35 | }
36 |
37 | async function setupFastify(envVariables) {
38 | const fastify = await lc39('./tests/services/post-decorator.js', {
39 | logLevel: 'silent',
40 | envVariables,
41 | })
42 | return fastify
43 | }
44 |
45 | tap.test('Test Post Decorator function', test => {
46 | test.test('Return the signal to not change the response', async assert => {
47 | const fastify = await setupFastify(baseEnv)
48 |
49 | const response = await fastify.inject({
50 | method: 'POST',
51 | url: '/response-unmodified',
52 | payload: {
53 | request: {
54 | method: 'GET',
55 | path: '/the-original-request-path',
56 | query: { my: 'query' },
57 | body: { the: 'body' },
58 | headers: { my: 'headers' },
59 | },
60 | response: {
61 | body: { the: 'response body' },
62 | headers: { my: 'response headers' },
63 | statusCode: 200,
64 | },
65 | },
66 | })
67 | assert.strictSame(response.statusCode, 204)
68 | assert.strictSame(response.payload, '')
69 | assert.end()
70 | })
71 |
72 | test.test('Return a modified body response', async assert => {
73 | const fastify = await setupFastify(baseEnv)
74 |
75 | const response = await fastify.inject({
76 | method: 'POST',
77 | url: '/change-original-body',
78 | payload: {
79 | request: {
80 | method: 'GET',
81 | path: '/the-original-request-path',
82 | query: { my: 'query' },
83 | body: { the: 'body' },
84 | headers: { my: 'headers' },
85 | },
86 | response: {
87 | body: { the: 'response body' },
88 | headers: { my: 'response headers' },
89 | statusCode: 200,
90 | },
91 | },
92 | })
93 | assert.strictSame(response.statusCode, 200)
94 | assert.ok(/application\/json/.test(response.headers['content-type']))
95 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
96 | assert.strictSame(response.payload, JSON.stringify({
97 | body: { the: 'new-body' },
98 | statusCode: 201,
99 | }))
100 | assert.end()
101 | })
102 |
103 | test.test('Return a modified headers response', async assert => {
104 | const fastify = await setupFastify(baseEnv)
105 |
106 | const response = await fastify.inject({
107 | method: 'POST',
108 | url: '/change-original-headers',
109 | payload: {
110 | request: {
111 | method: 'GET',
112 | path: '/the-original-request-path',
113 | query: { my: 'query' },
114 | body: { the: 'body' },
115 | headers: { my: 'headers' },
116 | },
117 | response: {
118 | body: { the: 'response body' },
119 | headers: { my: 'response headers' },
120 | statusCode: 200,
121 | },
122 | },
123 | })
124 | assert.strictSame(response.statusCode, 200)
125 | assert.ok(/application\/json/.test(response.headers['content-type']))
126 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
127 | assert.strictSame(JSON.parse(response.payload), {
128 | headers: { the: 'new-header' },
129 | })
130 | assert.end()
131 | })
132 |
133 | test.test('Test a bad handler that doesn\'t return the right type', async assert => {
134 | const fastify = await setupFastify(baseEnv)
135 |
136 | const response = await fastify.inject({
137 | method: 'POST',
138 | url: '/bad-hook',
139 | payload: {
140 | request: {
141 | method: 'GET',
142 | path: '/the-original-request-path',
143 | query: { my: 'query' },
144 | body: { the: 'body' },
145 | headers: { my: 'headers' },
146 | },
147 | response: {
148 | body: { the: 'response body' },
149 | headers: { my: 'response headers' },
150 | statusCode: 200,
151 | },
152 | },
153 | })
154 | assert.strictSame(response.statusCode, 500)
155 | assert.ok(/application\/json/.test(response.headers['content-type']))
156 | assert.strictSame(JSON.parse(response.payload), {
157 | error: 'Internal Server Error',
158 | message: 'Unknown return type',
159 | statusCode: 500,
160 | })
161 | })
162 |
163 | test.test('abortChain', async assert => {
164 | const fastify = await setupFastify(baseEnv)
165 |
166 | const response = await fastify.inject({
167 | method: 'POST',
168 | url: '/abort-chain',
169 | payload: {
170 | request: {
171 | method: 'GET',
172 | path: '/the-original-request-path',
173 | query: { my: 'query' },
174 | body: { the: 'body' },
175 | headers: { my: 'headers' },
176 | },
177 | response: {
178 | body: { the: 'response body' },
179 | headers: { my: 'response headers' },
180 | statusCode: 200,
181 | },
182 | },
183 | })
184 | assert.strictSame(response.statusCode, 418)
185 | assert.ok(/application\/json/.test(response.headers['content-type']))
186 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
187 | assert.strictSame(response.payload, JSON.stringify({
188 | statusCode: 406,
189 | body: { the: 'final body' },
190 | headers: {},
191 | }))
192 | assert.end()
193 | })
194 |
195 | test.test('is able to access to the mia headers correctly', async assert => {
196 | const fastify = await setupFastify(baseEnv)
197 |
198 | const response = await fastify.inject({
199 | method: 'POST',
200 | url: '/can-access-data',
201 | payload: {
202 | request: {
203 | method: 'GET',
204 | path: '/the-original-request-path',
205 | query: { my: 'query' },
206 | body: { the: 'body' },
207 | headers: {
208 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
209 | [USERID_HEADER_KEY]: 'userid',
210 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
211 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
212 | [BACKOFFICE_HEADER_KEY]: '1',
213 | my: 'headers',
214 | },
215 | },
216 | response: {
217 | body: { the: 'response body' },
218 | headers: { my: 'response headers' },
219 | statusCode: 200,
220 | },
221 | },
222 | })
223 | assert.strictSame(response.statusCode, 204, response.payload)
224 | assert.end()
225 | })
226 |
227 | test.test('addPostDecorator is chainable', async assert => {
228 | const fastify = await setupFastify(baseEnv)
229 |
230 | const response = await fastify.inject({
231 | method: 'POST',
232 | url: '/chained1',
233 | payload: {
234 | request: {
235 | method: 'GET',
236 | path: '/the-original-request-path',
237 | query: { my: 'query' },
238 | body: { the: 'body' },
239 | headers: { my: 'headers' },
240 | },
241 | response: {
242 | body: { the: 'response body' },
243 | headers: { my: 'response headers' },
244 | statusCode: 200,
245 | },
246 | },
247 | })
248 | assert.strictSame(response.statusCode, 204)
249 |
250 | const response1 = await fastify.inject({
251 | method: 'POST',
252 | url: '/chained2',
253 | payload: {
254 | request: {
255 | method: 'GET',
256 | path: '/the-original-request-path',
257 | query: { my: 'query' },
258 | body: { the: 'body' },
259 | headers: { my: 'headers' },
260 | },
261 | response: {
262 | body: { the: 'response body' },
263 | headers: { my: 'response headers' },
264 | statusCode: 200,
265 | },
266 | },
267 | })
268 | assert.strictSame(response1.statusCode, 204)
269 | })
270 |
271 | test.end()
272 | })
273 |
--------------------------------------------------------------------------------
/tests/preDecorator.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const tap = require('tap')
20 | const lc39 = require('@mia-platform/lc39')
21 |
22 | const USERID_HEADER_KEY = 'userid-header-key'
23 | const USER_PROPERTIES_HEADER_KEY = 'miauserproperties'
24 | const GROUPS_HEADER_KEY = 'groups-header-key'
25 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
26 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
27 | const MICROSERVICE_GATEWAY_SERVICE_NAME = 'microservice-gateway'
28 | const baseEnv = {
29 | USERID_HEADER_KEY,
30 | USER_PROPERTIES_HEADER_KEY,
31 | GROUPS_HEADER_KEY,
32 | CLIENTTYPE_HEADER_KEY,
33 | BACKOFFICE_HEADER_KEY,
34 | MICROSERVICE_GATEWAY_SERVICE_NAME,
35 | }
36 |
37 | async function setupFastify(envVariables) {
38 | const fastify = await lc39('./tests/services/pre-decorator.js', {
39 | logLevel: 'silent',
40 | envVariables,
41 | })
42 | return fastify
43 | }
44 |
45 | tap.test('preDecorator', test => {
46 | test.test('leaveOriginalRequestUnmodified', async assert => {
47 | const fastify = await setupFastify(baseEnv)
48 |
49 | const response = await fastify.inject({
50 | method: 'POST',
51 | url: '/response-unmodified',
52 | payload: {
53 | method: 'GET',
54 | path: '/the-original-request-path',
55 | query: { my: 'query' },
56 | body: { the: 'body' },
57 | headers: { my: 'headers' },
58 | },
59 | })
60 |
61 | assert.strictSame(response.statusCode, 204)
62 | assert.strictSame(response.payload, '')
63 | assert.end()
64 | })
65 |
66 | test.test('changeOriginalRequest', async assert => {
67 | const fastify = await setupFastify(baseEnv)
68 | const response = await fastify.inject({
69 | method: 'POST',
70 | url: '/change-original-request',
71 | payload: {
72 | method: 'GET',
73 | path: '/the-original-request-path',
74 | query: { my: 'query' },
75 | body: { the: 'body' },
76 | headers: { my: 'headers' },
77 | },
78 | })
79 | assert.strictSame(response.statusCode, 200)
80 | assert.ok(/application\/json/.test(response.headers['content-type']))
81 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
82 | assert.strictSame(response.payload, JSON.stringify({
83 | body: { the: 'new-body' },
84 | query: { new: 'querystring' },
85 | }))
86 | assert.end()
87 | })
88 |
89 | test.test('changeOriginalRequest with headers', async assert => {
90 | const fastify = await setupFastify(baseEnv)
91 | const response = await fastify.inject({
92 | method: 'POST',
93 | url: '/change-original-headers',
94 | payload: {
95 | method: 'GET',
96 | path: '/the-original-request-path',
97 | query: { my: 'query' },
98 | body: { the: 'body' },
99 | headers: { my: 'headers' },
100 | },
101 | })
102 | assert.strictSame(response.statusCode, 200)
103 | assert.ok(/application\/json/.test(response.headers['content-type']))
104 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
105 | assert.strictSame(response.payload, JSON.stringify({
106 | body: { the: 'new-body' },
107 | headers: { new: 'headers' },
108 | }))
109 | assert.end()
110 | })
111 |
112 | test.test('test bad handler that doesn\'t return the right type', async assert => {
113 | const fastify = await setupFastify(baseEnv)
114 | const response = await fastify.inject({
115 | method: 'POST',
116 | url: '/bad-hook',
117 | payload: {
118 | method: 'GET',
119 | path: '/the-original-request-path',
120 | query: { my: 'query' },
121 | body: { the: 'body' },
122 | headers: { my: 'headers' },
123 | },
124 | })
125 | assert.strictSame(response.statusCode, 500)
126 | assert.ok(/application\/json/.test(response.headers['content-type']))
127 | assert.strictSame(JSON.parse(response.payload), {
128 | error: 'Internal Server Error',
129 | message: 'Unknown return type',
130 | statusCode: 500,
131 | })
132 | })
133 |
134 | test.test('abortChain', async assert => {
135 | const fastify = await setupFastify(baseEnv)
136 | const response = await fastify.inject({
137 | method: 'POST',
138 | url: '/abort-chain',
139 | payload: {
140 | method: 'GET',
141 | path: '/the-original-request-path',
142 | query: { my: 'query' },
143 | body: { the: 'body' },
144 | headers: { my: 'headers' },
145 | },
146 | })
147 | assert.strictSame(response.statusCode, 418)
148 | assert.ok(/application\/json/.test(response.headers['content-type']))
149 | assert.ok(/charset=utf-8/.test(response.headers['content-type']))
150 | assert.strictSame(response.payload, JSON.stringify({
151 | statusCode: 406,
152 | body: { the: 'final body' },
153 | headers: {},
154 | }))
155 | assert.end()
156 | })
157 |
158 | test.test('is able to access to the mia headers correctly', async assert => {
159 | const fastify = await setupFastify(baseEnv)
160 | const response = await fastify.inject({
161 | method: 'POST',
162 | url: '/can-access-data',
163 | payload: {
164 | method: 'GET',
165 | path: '/the-original-request-path',
166 | query: { my: 'query' },
167 | body: { the: 'body' },
168 | headers: {
169 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
170 | [USERID_HEADER_KEY]: 'userid',
171 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
172 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
173 | [BACKOFFICE_HEADER_KEY]: '1',
174 | my: 'headers',
175 | },
176 | },
177 | })
178 | assert.strictSame(response.statusCode, 204)
179 | assert.strictSame(response.payload, '')
180 | assert.end()
181 | })
182 |
183 | test.test('addPreDecorator is Chainable', async assert => {
184 | const fastify = await setupFastify(baseEnv)
185 | const response = await fastify.inject({
186 | method: 'POST',
187 | url: '/chained1',
188 | payload: {
189 | method: 'GET',
190 | path: '/the-original-request-path',
191 | query: { my: 'query' },
192 | body: { the: 'body' },
193 | headers: { my: 'headers' },
194 | },
195 | })
196 | assert.strictSame(response.statusCode, 204)
197 |
198 | const response1 = await fastify.inject({
199 | method: 'POST',
200 | url: '/chained2',
201 | payload: {
202 | method: 'GET',
203 | path: '/the-original-request-path',
204 | query: { my: 'query' },
205 | body: { the: 'body' },
206 | headers: { my: 'headers' },
207 | },
208 | })
209 | assert.strictSame(response1.statusCode, 204)
210 | })
211 |
212 | test.end()
213 | })
214 |
--------------------------------------------------------------------------------
/tests/services/advanced-config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | module.exports = customService(async function clientGroups(service) {
22 | function handlerAdvancedConfig(request, reply) {
23 | reply.send({
24 | error: request.validationError.message,
25 | config: reply.context.config,
26 | })
27 | }
28 |
29 | const advancedConfig = {
30 | attachValidation: true,
31 | config: { myConfig: [1, 2, 3, 4] },
32 | method: 'overwritten property',
33 | path: 'overwritten property',
34 | handler: 'overwritten property',
35 | schema: 'overwritten property',
36 | }
37 |
38 | const schema = {
39 | body: {
40 | type: 'object',
41 | additionalProperties: false,
42 | required: ['foo'],
43 | properties: {
44 | foo: { type: 'number' },
45 | },
46 | },
47 | }
48 | service.addRawCustomPlugin('POST', '/advanced-config', handlerAdvancedConfig, schema, advancedConfig)
49 | })
50 |
--------------------------------------------------------------------------------
/tests/services/advanced-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 | const envSchema = {
19 | type: 'object',
20 | required: ['MY_AWESOME_ENV', 'MY_REQUIRED_ENV_VAR'],
21 | properties: {
22 | MY_AWESOME_ENV: { type: 'string', default: 'the default value' },
23 | MY_REQUIRED_ENV_VAR: { type: 'string' },
24 | },
25 | }
26 |
27 | const customService = require('../../index')(envSchema)
28 |
29 | module.exports = customService(async function clientGroups(service) {
30 | const customFunctionality = request => request.body
31 | service.decorate('customFunctionality', customFunctionality)
32 | async function handlerCustom(request, reply) {
33 | reply.send(this.customFunctionality(request))
34 | }
35 |
36 | function handlerEnv(request, reply) {
37 | reply.send(this.config.MY_AWESOME_ENV)
38 | }
39 |
40 | service.addRawCustomPlugin('GET', '/env', handlerEnv)
41 | service.addRawCustomPlugin('POST', '/custom', handlerCustom)
42 | })
43 |
--------------------------------------------------------------------------------
/tests/services/advanced-first-level-properties-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 | const envSchema = {
19 | type: 'object',
20 | required: ['MY_AWESOME_ENV', 'MY_REQUIRED_ENV_VAR'],
21 | properties: {
22 | MY_AWESOME_ENV: { type: 'string', default: 'the default value' },
23 | MY_REQUIRED_ENV_VAR: { type: 'string' },
24 | },
25 | patternProperties: {
26 | '^S_': { 'type': 'string' },
27 | '^I_': { 'type': 'number' },
28 | },
29 | minProperties: 1,
30 | additionalProperties: true,
31 | }
32 |
33 | const customService = require('../../index')(envSchema)
34 |
35 | module.exports = customService(async function clientGroups(service) {
36 | async function handlerApiRest(_, reply) {
37 | reply.send(this.config.required)
38 | }
39 |
40 | service.addRawCustomPlugin('GET', '/api-rest', handlerApiRest)
41 | })
42 |
--------------------------------------------------------------------------------
/tests/services/all-of-env-validation-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyRoutes = require('@fastify/routes')
20 |
21 | const properties = {
22 | ALL_OF_REQUIRED_FIELD_1: { type: 'string' },
23 | ALL_OF_REQUIRED_FIELD_2: { type: 'string' },
24 | }
25 |
26 | const envJsonSchema = {
27 | type: 'object',
28 | properties,
29 | required: [],
30 | allOf: [
31 | {
32 | required: [
33 | 'ALL_OF_REQUIRED_FIELD_1',
34 | ],
35 | },
36 | {
37 | required: [
38 | 'ALL_OF_REQUIRED_FIELD_2',
39 | ],
40 | },
41 | ],
42 | }
43 |
44 | const customService = require('../../index')(envJsonSchema)
45 |
46 | module.exports = customService(async function clientGroups(service) {
47 | service.register(fastifyRoutes)
48 | service.addRawCustomPlugin('GET', '/', (request, reply) => {
49 | reply.send('Hello world!')
50 | })
51 | })
52 |
53 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
54 | fastify.assert.ok(fastify.getHttpClient)
55 | fastify.assert.ok(fastify.routes)
56 | return {
57 | statusOk: true,
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/services/any-of-env-validation-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyRoutes = require('@fastify/routes')
20 |
21 | const properties = {
22 | ANY_OF_REQUIRED_FIELD_1: { type: 'string' },
23 | ANY_OF_REQUIRED_FIELD_2: { type: 'string' },
24 | }
25 |
26 | const envJsonSchema = {
27 | type: 'object',
28 | properties,
29 | required: [],
30 | anyOf: [
31 | {
32 | required: [
33 | 'ANY_OF_REQUIRED_FIELD_1',
34 | ],
35 | },
36 | {
37 | required: [
38 | 'ANY_OF_REQUIRED_FIELD_2',
39 | ],
40 | },
41 | ],
42 | }
43 |
44 | const customService = require('../../index')(envJsonSchema)
45 |
46 | module.exports = customService(async function clientGroups(service) {
47 | service.register(fastifyRoutes)
48 | service.addRawCustomPlugin('GET', '/', (request, reply) => {
49 | reply.send('Hello world!')
50 | })
51 | })
52 |
53 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
54 | fastify.assert.ok(fastify.getHttpClient)
55 | fastify.assert.ok(fastify.routes)
56 | return {
57 | statusOk: true,
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/services/get-headers-to-proxy.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | module.exports = customService(async function clientGroups(service) {
22 | service.addRawCustomPlugin('POST', '/default', async function handler(req) {
23 | return req.getHeadersToProxy(req.body?.options)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/tests/services/http-client.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | const httpOtherService = 'http://other-service'
22 | const httpsOtherService = 'https://other-service'
23 |
24 | module.exports = customService(async function clientGroups(service) {
25 | service.addRawCustomPlugin('POST', '/default', async function handler(req) {
26 | const { some } = req.body
27 | const proxy = req.getHttpClient(httpOtherService)
28 | const res = await proxy.get('/res')
29 | const { id } = res.payload
30 | return { id, some }
31 | })
32 |
33 | service.addRawCustomPlugin('POST', '/custom', async function handler(req) {
34 | const { some } = req.body
35 | const proxy = req.getHttpClient(`${httpOtherService}:3000`)
36 | const res = await proxy.get('/res')
37 | const { id } = res.payload
38 | return { id, some }
39 | })
40 |
41 | service.addPreDecorator('/pre', async function handler(req) {
42 | const { some } = req.getOriginalRequestBody()
43 | const proxy = req.getHttpClient(`${httpsOtherService}:3000`)
44 | const res = await proxy.get('/res')
45 | const { id } = res.payload
46 | return req.changeOriginalRequest().setBody({ id, some })
47 | })
48 |
49 | service.addPostDecorator('/post', async function handler(req) {
50 | const { some } = req.getOriginalResponseBody()
51 | const proxy = req.getHttpClient(`${httpsOtherService}:3000`)
52 | const res = await proxy.get('/res')
53 | const { id } = res.payload
54 | return req.changeOriginalResponse().setBody({ id, some })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/tests/services/if-then-else-env-validation-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyRoutes = require('@fastify/routes')
20 |
21 | const baseProperties = {
22 | BASE_REQUIRED_FIELD: { type: 'string' },
23 | CONDITION_FIELD: { type: 'boolean' },
24 | }
25 |
26 | const thenProperties = {
27 | CONDITION_TRUE_REQUIRED_FIELD: { type: 'string' },
28 | }
29 |
30 | const elseProperties = {
31 | CONDITION_FALSE_REQUIRED_FIELD: { type: 'string' },
32 | }
33 |
34 | const thenRequired = [
35 | 'CONDITION_TRUE_REQUIRED_FIELD',
36 | ]
37 |
38 | const baseRequired = [
39 | 'BASE_REQUIRED_FIELD',
40 | ]
41 |
42 | const elseRequired = [
43 | 'CONDITION_FALSE_REQUIRED_FIELD',
44 | ]
45 |
46 | const envJsonSchema = {
47 | type: 'object',
48 | properties: baseProperties,
49 | required: baseRequired,
50 | if: {
51 | properties: {
52 | CONDITION_FIELD: { const: true },
53 | },
54 | },
55 | then: {
56 | properties: thenProperties,
57 | required: thenRequired,
58 | },
59 | else: {
60 | properties: elseProperties,
61 | required: elseRequired,
62 | },
63 | }
64 |
65 | const customService = require('../../index')(envJsonSchema)
66 |
67 | module.exports = customService(async function clientGroups(service) {
68 | service.register(fastifyRoutes)
69 | service.addRawCustomPlugin('GET', '/', (request, reply) => {
70 | reply.send('Hello world!')
71 | })
72 | })
73 |
74 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
75 | fastify.assert.ok(fastify.getHttpClient)
76 | fastify.assert.ok(fastify.routes)
77 | return {
78 | statusOk: true,
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/services/one-of-env-validation-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyRoutes = require('@fastify/routes')
20 |
21 | const properties = {
22 | ONE_OF_REQUIRED_FIELD_1: { type: 'string' },
23 | ONE_OF_REQUIRED_FIELD_2: { type: 'string' },
24 | }
25 |
26 | const envJsonSchema = {
27 | type: 'object',
28 | properties,
29 | required: [],
30 | oneOf: [
31 | {
32 | required: [
33 | 'ONE_OF_REQUIRED_FIELD_1',
34 | ],
35 | },
36 | {
37 | required: [
38 | 'ONE_OF_REQUIRED_FIELD_2',
39 | ],
40 | },
41 | ],
42 | }
43 |
44 | const customService = require('../../index')(envJsonSchema)
45 |
46 | module.exports = customService(async function clientGroups(service) {
47 | service.register(fastifyRoutes)
48 | service.addRawCustomPlugin('GET', '/', (request, reply) => {
49 | reply.send('Hello world!')
50 | })
51 | })
52 |
53 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
54 | fastify.assert.ok(fastify.getHttpClient)
55 | fastify.assert.ok(fastify.routes)
56 | return {
57 | statusOk: true,
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/services/overlapping-env-validation-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fastifyRoutes = require('@fastify/routes')
20 |
21 | const USERID_HEADER_KEY = 'USERID_HEADER_KEY'
22 | const USER_PROPERTIES_HEADER_KEY = 'USER_PROPERTIES_HEADER_KEY'
23 | const GROUPS_HEADER_KEY = 'GROUPS_HEADER_KEY'
24 | const CLIENTTYPE_HEADER_KEY = 'CLIENTTYPE_HEADER_KEY'
25 |
26 | const envJsonSchema = {
27 | type: 'object',
28 | properties: {
29 | USERID_HEADER_KEY,
30 | USER_PROPERTIES_HEADER_KEY,
31 | GROUPS_HEADER_KEY,
32 | CLIENTTYPE_HEADER_KEY,
33 | },
34 | required: [],
35 | }
36 |
37 | const customService = require('../../index')(envJsonSchema)
38 |
39 | module.exports = customService(async function clientGroups(service) {
40 | service.register(fastifyRoutes)
41 | service.addRawCustomPlugin('GET', '/', (request, reply) => {
42 | reply.send('Hello world!')
43 | })
44 | })
45 |
46 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
47 | fastify.assert.ok(fastify.getHttpClient)
48 | fastify.assert.ok(fastify.routes)
49 | return {
50 | statusOk: true,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/services/plain-custom-service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const fs = require('fs')
20 | const fastifyRoutes = require('@fastify/routes')
21 |
22 | const customService = require('../../index')()
23 |
24 | const schema = {
25 | body: {
26 | type: 'object',
27 | required: ['some'],
28 | properties: {
29 | some: { type: 'string' },
30 | foobar: {
31 | $ref: 'foobar#',
32 | },
33 | nested: {
34 | type: 'object',
35 | properties: {
36 | field: {
37 | type: 'string',
38 | },
39 | },
40 | additionalProperties: false,
41 | required: ['field'],
42 | },
43 | },
44 | additionalProperties: false,
45 | },
46 | querystring: {
47 | type: 'object',
48 | properties: {
49 | some: { type: 'number' },
50 | },
51 | },
52 | headers: {
53 | type: 'object',
54 | properties: {
55 | foobar: {
56 | $ref: 'foobar#',
57 | },
58 | },
59 | },
60 | response: {
61 | 200: {
62 | type: 'object',
63 | properties: {
64 | foobar: {
65 | $ref: 'foobar#',
66 | },
67 | },
68 | additionalProperties: true,
69 | },
70 | },
71 | }
72 |
73 | function customParser(req, payload, done) {
74 | let data = ''
75 | payload.on('data', chunk => {
76 | data += chunk
77 | })
78 |
79 | payload.on('end', () => {
80 | done(null, data)
81 | })
82 | }
83 |
84 | module.exports = customService(async function clientGroups(service) {
85 | service.addValidatorSchema({
86 | $id: 'foobar',
87 | type: 'string',
88 | enum: ['foo1', 'bar2', 'taz3'],
89 | })
90 | const retrievedSchema = service.getValidatorSchema('foobar')
91 | service.assert.ok(retrievedSchema)
92 |
93 | service.register(fastifyRoutes)
94 | function handler(request, reply) {
95 | reply.send('Hello world!')
96 | }
97 |
98 | function handlePlatformValues(request, reply) {
99 | reply.send({
100 | userId: request.getUserId(),
101 | userGroups: request.getGroups(),
102 | userProperties: request.getUserProperties(),
103 | clientType: request.getClientType(),
104 | backoffice: request.isFromBackOffice(),
105 | })
106 | }
107 |
108 | function handleMiaHeaders(request, reply) {
109 | reply.send(request.getMiaHeaders())
110 | }
111 |
112 | function handlerRespondWithBody(request, reply) {
113 | reply.send(request.body)
114 | }
115 |
116 | function handlerStream(request, reply) {
117 | reply.header('Content-Type', 'application/octet-stream')
118 | reply.send(fs.createReadStream('./tests/services/plain-custom-service.js'))
119 | }
120 |
121 | service.addRawCustomPlugin('GET', '/', handler)
122 | service.addRawCustomPlugin('GET', '/platform-values', handlePlatformValues)
123 | service.addRawCustomPlugin('POST', '/', handlerRespondWithBody)
124 | service.addRawCustomPlugin('GET', '/stream', handlerStream)
125 | service.addRawCustomPlugin('GET', '/mia-headers', handleMiaHeaders)
126 | service.addRawCustomPlugin('POST', '/validation', handlerRespondWithBody, schema)
127 | service.addContentTypeParser('application/custom-type', customParser)
128 | service.addRawCustomPlugin('POST', '/customValidation', handlerRespondWithBody)
129 | })
130 |
131 | module.exports.healthinessHandler = async function healthinessHandler(fastify) {
132 | fastify.assert.ok(fastify.getHttpClient)
133 | fastify.assert.ok(fastify.routes)
134 | return {
135 | statusOk: true,
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/services/post-decorator.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | const USERID_HEADER_KEY = 'userid-header-key'
22 | const USER_PROPERTIES_HEADER_KEY = 'miauserproperties'
23 | const GROUPS_HEADER_KEY = 'groups-header-key'
24 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
25 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
26 |
27 | module.exports = customService(async function clientGroups(service) {
28 | function responseUnmodifiedHandler(request) {
29 | return request.leaveOriginalResponseUnmodified()
30 | }
31 | service.addPostDecorator('/response-unmodified', responseUnmodifiedHandler)
32 |
33 | function changeOriginalBodyResponseHandler(request) {
34 | return request.changeOriginalResponse()
35 | .setBody({ the: 'new-body' })
36 | .setStatusCode(201)
37 | }
38 | service.addPostDecorator('/change-original-body', changeOriginalBodyResponseHandler)
39 |
40 | function changeOriginalHeadersResponseHandler(request) {
41 | return request.changeOriginalResponse()
42 | .setHeaders({ the: 'new-header' })
43 | }
44 | service.addPostDecorator('/change-original-headers', changeOriginalHeadersResponseHandler)
45 |
46 | function badHookHandler() {
47 | return {
48 | not: 'using the decorated functions',
49 | }
50 | }
51 | service.addPostDecorator('/bad-hook', badHookHandler)
52 |
53 | function abortChainHandler(request) {
54 | return request.abortChain(406, { the: 'final body' })
55 | }
56 | service.addPostDecorator('/abort-chain', abortChainHandler)
57 |
58 | function accessDataHandler(request) {
59 | this.assert.equal(request.getUserId(), 'userid')
60 | this.assert.deepEqual(request.getUserProperties(), { prop1: 'value1' })
61 | this.assert.deepEqual(request.getGroups(), ['group-to-greet', 'group'])
62 | this.assert.equal(request.getClientType(), 'CMS')
63 | this.assert.equal(request.isFromBackOffice(), true)
64 |
65 | this.assert.deepEqual(request.getOriginalRequest(), {
66 | method: 'GET',
67 | path: '/the-original-request-path',
68 | query: { my: 'query' },
69 | body: { the: 'body' },
70 | headers: {
71 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
72 | [USERID_HEADER_KEY]: 'userid',
73 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
74 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
75 | [BACKOFFICE_HEADER_KEY]: '1',
76 | my: 'headers',
77 | },
78 | })
79 | this.assert.deepEqual(request.getOriginalRequestBody(), { the: 'body' })
80 | this.assert.deepEqual(request.getOriginalRequestHeaders(), {
81 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
82 | [USERID_HEADER_KEY]: 'userid',
83 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
84 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
85 | [BACKOFFICE_HEADER_KEY]: '1',
86 | my: 'headers',
87 | })
88 | this.assert.deepEqual(request.getOriginalRequestQuery(), { my: 'query' })
89 | this.assert.equal(request.getOriginalRequestMethod(), 'GET')
90 | this.assert.equal(request.getOriginalRequestPath(), '/the-original-request-path')
91 |
92 | this.assert.deepEqual(request.getOriginalResponse(), {
93 | body: { the: 'response body' },
94 | headers: { my: 'response headers' },
95 | statusCode: 200,
96 | })
97 | this.assert.deepEqual(request.getOriginalResponseBody(), { the: 'response body' })
98 | this.assert.deepEqual(request.getOriginalResponseHeaders(), { my: 'response headers' })
99 | this.assert.equal(request.getOriginalResponseStatusCode(), 200)
100 |
101 | this.assert.ok(this.config)
102 |
103 | return request.leaveOriginalResponseUnmodified()
104 | }
105 | service.addPostDecorator('/can-access-data', accessDataHandler)
106 |
107 | service
108 | .addPostDecorator('/chained1', responseUnmodifiedHandler)
109 | .addPostDecorator('/chained2', responseUnmodifiedHandler)
110 | })
111 |
--------------------------------------------------------------------------------
/tests/services/pre-decorator.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | const USERID_HEADER_KEY = 'userid-header-key'
22 | const USER_PROPERTIES_HEADER_KEY = 'miauserproperties'
23 | const GROUPS_HEADER_KEY = 'groups-header-key'
24 | const CLIENTTYPE_HEADER_KEY = 'clienttype-header-key'
25 | const BACKOFFICE_HEADER_KEY = 'backoffice-header-key'
26 |
27 | module.exports = customService(async function clientGroups(service) {
28 | function requestUnmodifiedHandler(request) {
29 | return request.leaveOriginalRequestUnmodified()
30 | }
31 | service.addPreDecorator('/response-unmodified', requestUnmodifiedHandler)
32 |
33 | function changeOriginalBodyRequestHandler(request) {
34 | return request.changeOriginalRequest()
35 | .setBody({ the: 'new-body' })
36 | .setQuery({ new: 'querystring' })
37 | }
38 | service.addPreDecorator('/change-original-request', changeOriginalBodyRequestHandler)
39 |
40 | function changeOriginalHeadersRequestHandler(request) {
41 | return request.changeOriginalRequest()
42 | .setBody({ the: 'new-body' })
43 | .setHeaders({ new: 'headers' })
44 | }
45 | service.addPreDecorator('/change-original-headers', changeOriginalHeadersRequestHandler)
46 |
47 | function badHookHandler() {
48 | return {
49 | not: 'using the decorated functions',
50 | }
51 | }
52 | service.addPreDecorator('/bad-hook', badHookHandler)
53 |
54 | function abortChainHandler(request) {
55 | return request.abortChain(406, { the: 'final body' })
56 | }
57 | service.addPreDecorator('/abort-chain', abortChainHandler)
58 |
59 | function accessDataHandler(request) {
60 | this.assert.equal(request.getUserId(), 'userid')
61 | this.assert.deepEqual(request.getUserProperties(), { prop1: 'value1' })
62 | this.assert.deepEqual(request.getGroups(), ['group-to-greet', 'group'])
63 | this.assert.equal(request.getClientType(), 'CMS')
64 | this.assert.equal(request.isFromBackOffice(), true)
65 |
66 | this.assert.deepEqual(request.getOriginalRequest(), {
67 | method: 'GET',
68 | path: '/the-original-request-path',
69 | query: { my: 'query' },
70 | body: { the: 'body' },
71 | headers: {
72 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
73 | [USERID_HEADER_KEY]: 'userid',
74 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
75 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
76 | [BACKOFFICE_HEADER_KEY]: '1',
77 | my: 'headers',
78 | },
79 | })
80 | this.assert.deepEqual(request.getOriginalRequestBody(), { the: 'body' })
81 | this.assert.deepEqual(request.getOriginalRequestHeaders(), {
82 | [CLIENTTYPE_HEADER_KEY]: 'CMS',
83 | [USERID_HEADER_KEY]: 'userid',
84 | [USER_PROPERTIES_HEADER_KEY]: JSON.stringify({ prop1: 'value1' }),
85 | [GROUPS_HEADER_KEY]: 'group-to-greet,group',
86 | [BACKOFFICE_HEADER_KEY]: '1',
87 | my: 'headers',
88 | })
89 | this.assert.deepEqual(request.getOriginalRequestQuery(), { my: 'query' })
90 | this.assert.equal(request.getOriginalRequestMethod(), 'GET')
91 | this.assert.equal(request.getOriginalRequestPath(), '/the-original-request-path')
92 | this.assert.ok(this.config)
93 |
94 | return request.leaveOriginalRequestUnmodified()
95 | }
96 | service.addPreDecorator('/can-access-data', accessDataHandler)
97 |
98 | service
99 | .addPreDecorator('/chained1', requestUnmodifiedHandler)
100 | .addPreDecorator('/chained2', requestUnmodifiedHandler)
101 | })
102 |
--------------------------------------------------------------------------------
/tests/services/service-with-formats.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | const customService = require('../../index')()
20 |
21 | const serviceOptions = {
22 | ajv: {
23 | plugins: {
24 | 'ajv-formats': { formats: ['date-time'] },
25 | },
26 | vocabulary: ['x-name'],
27 | },
28 | }
29 |
30 | module.exports = customService(async function clientGroups(service) {
31 | function handler(request, reply) {
32 | reply.send({ message: `hello there, it is ${request.body.someDate}` })
33 | }
34 |
35 | service.addRawCustomPlugin('POST', '/hello', handler, {
36 | body: {
37 | type: 'object',
38 | properties: {
39 | someDate: {
40 | type: 'string',
41 | 'x-name': 'My custom name in schema',
42 | format: 'date-time',
43 | description: 'some date description',
44 | },
45 | },
46 | },
47 | })
48 | }, serviceOptions)
49 |
--------------------------------------------------------------------------------
/tests/types/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Mia srl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict'
18 |
19 | import * as stream from 'stream'
20 |
21 | import * as cpl from '../../index'
22 |
23 | cpl({
24 | type: 'object',
25 | required: [ 'foo'],
26 | properties: {
27 | foo: { type: 'string' }
28 | }
29 | })
30 | cpl()
31 |
32 | const a = cpl()
33 | const { getHttpClient } = cpl
34 |
35 | async function invokeSomeApisServiceProxy(service: cpl.Service) {
36 | service.get('/path')
37 | service.get('/path', { query: 'params' })
38 | service.get('/path', { query: 'params' }, { returnAs: 'JSON' })
39 | service.get('/path', { query: 'params' }, { returnAs: 'BUFFER' })
40 | service.get('/path', { query: 'params' }, { returnAs: 'STREAM' })
41 |
42 | service.post('/path', {my: 'body'})
43 | service.post('/path', 'My body as string')
44 | service.post('/path', Buffer.from('My body as buffer'))
45 | service.post('/path', new stream.Readable())
46 | service.post('/path', {my: 'body'}, { query: 'params' })
47 | service.post('/path', {my: 'body'}, { query: 'params' }, { returnAs: 'STREAM' })
48 |
49 | service.put('/path', {my: 'body'})
50 | service.put('/path', 'My body as string')
51 | service.put('/path', Buffer.from('My body as buffer'))
52 | service.put('/path', new stream.Readable())
53 | service.put('/path', {my: 'body'}, { query: 'params' })
54 | service.put('/path', {my: 'body'}, { query: 'params' }, { returnAs: 'STREAM' })
55 |
56 | service.patch('/path', {my: 'body'})
57 | service.patch('/path', 'My body as string')
58 | service.patch('/path', Buffer.from('My body as buffer'))
59 | service.patch('/path', new stream.Readable())
60 | service.patch('/path', {my: 'body'}, { query: 'params' })
61 | service.patch('/path', {my: 'body'}, { query: 'params' }, { returnAs: 'STREAM' })
62 |
63 | service.delete('/path', {my: 'body'})
64 | service.delete('/path', 'My body as string')
65 | service.delete('/path', Buffer.from('My body as buffer'))
66 | service.delete('/path', new stream.Readable())
67 | service.delete('/path', {my: 'body'}, { query: 'params' })
68 | service.delete('/path', {my: 'body'}, { query: 'params' }, { returnAs: 'STREAM' })
69 |
70 | const response = await service.get('/path') as cpl.JSONServiceResponse
71 | console.log(response.statusCode)
72 | console.log(response.headers)
73 | console.log(response.payload)
74 |
75 | const responseAsBuffer = await service.get('/path') as cpl.BufferServiceResponse
76 | console.log(responseAsBuffer.statusCode)
77 | console.log(responseAsBuffer.headers)
78 | console.log(responseAsBuffer.payload)
79 |
80 | const responseAsStream = await service.get('/path', {}, { returnAs: 'STREAM' }) as cpl.StreamedServiceResponse
81 | let d: string = ''
82 | responseAsStream.on('data', data => { d += data.toString() })
83 | responseAsStream.on('end', () => console.log(d))
84 | }
85 |
86 | async function invokeSomeApis(service: cpl.HttpClient) {
87 | service.get('/path')
88 | service.get('/path', { query: { query: 'params'} })
89 | service.get('/path', { returnAs: 'JSON', query: { query: 'params' } })
90 | service.get('/path', { returnAs: 'BUFFER', query: { query: 'params' } })
91 | service.get('/path', { returnAs: 'STREAM', query: { query: 'params' } })
92 |
93 | service.post('/path', {my: 'body'})
94 | service.post('/path', 'My body as string')
95 | service.post('/path', Buffer.from('My body as buffer'))
96 | service.post('/path', new stream.Readable())
97 | service.post('/path', {my: 'body'}, { query: { query: 'params'} })
98 | service.post('/path', {my: 'body'}, { returnAs: 'STREAM', query: { query: 'params'} })
99 |
100 | service.put('/path', {my: 'body'})
101 | service.put('/path', 'My body as string')
102 | service.put('/path', Buffer.from('My body as buffer'))
103 | service.put('/path', new stream.Readable())
104 | service.put('/path', {my: 'body'}, { query: { query: 'params'} })
105 | service.put('/path', {my: 'body'}, { returnAs: 'STREAM', query: { query: 'params'} })
106 |
107 | service.patch('/path', {my: 'body'})
108 | service.patch('/path', 'My body as string')
109 | service.patch('/path', Buffer.from('My body as buffer'))
110 | service.patch('/path', new stream.Readable())
111 | service.patch('/path', {my: 'body'}, {query: { query: 'params'}})
112 | service.patch('/path', {my: 'body'}, { returnAs: 'STREAM', query: { query: 'params'} })
113 |
114 | service.delete('/path', {my: 'body'})
115 | service.delete('/path', 'My body as string')
116 | service.delete('/path', Buffer.from('My body as buffer'))
117 | service.delete('/path', new stream.Readable())
118 | service.delete('/path', {my: 'body'}, { query: { query: 'params'} })
119 | service.delete('/path', {my: 'body'}, { returnAs: 'STREAM', query: { query: 'params'} })
120 |
121 | interface Res {
122 | foo: string
123 | }
124 | const response = await service.get>('/path')
125 | console.log(response.statusCode)
126 | console.log(response.headers)
127 | console.log(response.payload.foo)
128 |
129 | const responseAsBuffer = await service.get('/path')
130 | console.log(responseAsBuffer.statusCode)
131 | console.log(responseAsBuffer.headers)
132 | console.log(responseAsBuffer.payload)
133 |
134 | const responseAsStream = await service.get('/path', { returnAs: 'STREAM' })
135 | let d: string = ''
136 | responseAsStream.headers
137 | responseAsStream.payload.on('data', data => { d += data.toString() })
138 | responseAsStream.payload.on('end', () => console.log(d))
139 | }
140 |
141 | a(async function (service) {
142 |
143 | console.log(service.config)
144 |
145 | service.addRawCustomPlugin('POST', '/path-attach-validation', function handlerPath1(request, reply) {
146 | reply.send({ hi: 'hi' })
147 | }, {
148 | body: {
149 | type: 'object',
150 | additionalProperties: false,
151 | required: ['foo'],
152 | properties: {
153 | foo: { type: 'number' },
154 | },
155 | }
156 | }, { attachValidation: true })
157 |
158 | type RequestGeneric = {
159 | Body: {body: string}
160 | Querystring: {querystring: string}
161 | Params: {params: string}
162 | Headers: {headers: string}
163 | }
164 |
165 | const httpClientFromService = service.getHttpClient('http://service-name')
166 | await invokeSomeApis(httpClientFromService)
167 |
168 | service
169 | .addRawCustomPlugin('GET', '/path1', function handlerPath1(request, reply) {
170 | console.log(this.config)
171 | if (request.getUserId() === null) {
172 | reply.send({})
173 | return
174 | }
175 | reply.send({ hi: request.getUserId() })
176 | })
177 | .addRawCustomPlugin('GET', '/', async function handler(request, reply) {
178 | console.log(this.config)
179 |
180 | const userId: string | null = request.getUserId()
181 | const groups: string[] = request.getGroups()
182 | const clientType: string | null = request.getClientType()
183 | const isFromBackOffice: boolean = request.isFromBackOffice()
184 |
185 | await invokeProxies()
186 |
187 | return { 'aa': 'boo' }
188 | }, {
189 | headers: {
190 | type:'object'
191 | }
192 | })
193 | .addRawCustomPlugin('POST', '/', async function handler(request, reply) {
194 | console.log(this.config)
195 |
196 | const userId: string | null = request.getUserId()
197 | const groups: string[] = request.getGroups()
198 | const clientType: string | null = request.getClientType()
199 | const isFromBackOffice: boolean = request.isFromBackOffice()
200 |
201 | const httpClient = request.getHttpClient('http://my-service')
202 | await invokeSomeApis(httpClient)
203 |
204 | const httpClientWithOptions = request.getHttpClient('http://my-service', {
205 | headers: {'accept': 'application/json'},
206 | })
207 | await invokeSomeApis(httpClientWithOptions)
208 |
209 | const httpClientFromService = service.getHttpClient('http://service-name')
210 | await invokeSomeApis(httpClientFromService)
211 |
212 | return { 'aa': 'boo' }
213 | }, {
214 | headers: {
215 | type:'object'
216 | }
217 | })
218 | .addRawCustomPlugin('GET', '/ts', async function handler(request, reply) {
219 | console.log(request.body.body)
220 | console.log(request.query.querystring)
221 | console.log(request.params.params)
222 | console.log(request.headers.headers)
223 |
224 | return { 'aa': 'boo' }
225 | })
226 |
227 | service
228 | .addPreDecorator('/decorators/my-pre1', async function myHandlerPreDecorator(request, reply) {
229 | const originalRequest : cpl.OriginalRequest = request.getOriginalRequest()
230 | console.log(originalRequest)
231 | console.log(originalRequest.body)
232 | console.log(originalRequest.headers)
233 | console.log(originalRequest.method)
234 | console.log(originalRequest.path)
235 | console.log(originalRequest.query)
236 | console.log(request.getOriginalRequestBody())
237 | console.log(request.getOriginalRequestHeaders())
238 | console.log(request.getOriginalRequestQuery())
239 | console.log(request.getOriginalRequestPath())
240 | console.log(request.getOriginalRequestMethod())
241 |
242 | console.log(request.getUserId())
243 | console.log(request.getGroups())
244 | console.log(request.getClientType())
245 | console.log(request.isFromBackOffice())
246 | console.log(request.getMiaHeaders())
247 |
248 | return request.leaveOriginalRequestUnmodified()
249 | })
250 | .addPreDecorator('/decorators/my-pre2', async function myHandlerPreDecorator(request, reply) {
251 | return request.changeOriginalRequest()
252 | .setBody({ new: 'body' })
253 | .setQuery({ rewrite: 'the querystring completely' })
254 | .setHeaders({ rewrite: 'the headers completely' })
255 | })
256 | service.addPreDecorator('/decorators/my-pre3', async function myHandlerPreDecorator(request, reply) {
257 | return request.abortChain(200, { final: 'body' })
258 | })
259 | service.addPreDecorator('/decorators/my-pre4', async function myHandlerPreDecorator(request, reply) {
260 | return request.abortChain(200, { final: 'body' }, { some: 'other headers' })
261 | })
262 | service.addPreDecorator('/decorators/my-pre5', async function myHandlerPreDecorator(request, response) {
263 | console.log(request.body.body)
264 | console.log(request.query.querystring)
265 | console.log(request.params.params)
266 | console.log(request.headers.headers)
267 |
268 | return { 'aa': 'boo' }
269 | })
270 |
271 | service
272 | .addPostDecorator('/decorators/my-post1', async function myHandlerPostDecorator(request, reply) {
273 | const originalRequest : cpl.OriginalRequest = request.getOriginalRequest()
274 | console.log(originalRequest)
275 | console.log(originalRequest.body)
276 | console.log(originalRequest.headers)
277 | console.log(originalRequest.method)
278 | console.log(originalRequest.path)
279 | console.log(originalRequest.query)
280 | console.log(request.getOriginalRequestBody())
281 | console.log(request.getOriginalRequestHeaders())
282 | console.log(request.getOriginalRequestQuery())
283 | console.log(request.getOriginalRequestPath())
284 | console.log(request.getOriginalRequestMethod())
285 |
286 | const originalResponse : cpl.OriginalResponse = request.getOriginalResponse()
287 | console.log(originalResponse)
288 | console.log(originalResponse.statusCode)
289 | console.log(originalResponse.headers)
290 | console.log(originalResponse.body)
291 | console.log(request.getOriginalResponseBody())
292 | console.log(request.getOriginalResponseHeaders())
293 | console.log(request.getOriginalResponseStatusCode())
294 |
295 | console.log(request.getUserId())
296 | console.log(request.getGroups())
297 | console.log(request.getClientType())
298 | console.log(request.isFromBackOffice())
299 | console.log(request.getMiaHeaders())
300 |
301 | return request.leaveOriginalResponseUnmodified()
302 | })
303 | .addPostDecorator('/decorators/my-post2', async function myHandlerPostDecorator(request, reply) {
304 | return request.changeOriginalResponse()
305 | .setBody({ new: 'body' })
306 | .setStatusCode(201)
307 | .setHeaders({ rewrite: 'the headers completely' })
308 | })
309 | service.addPostDecorator('/decorators/my-post3', async function myHandlerPostDecorator(request, reply) {
310 | return request.abortChain(200, { final: 'body' })
311 | })
312 | service.addPostDecorator('/decorators/my-post4', async function myHandlerPostDecorator(request, reply) {
313 | return request.abortChain(200, { final: 'body' }, { some: 'other headers' })
314 | })
315 | service.addPostDecorator('/decorators/my-post5', async function myHandlerPostDecorator(request, response) {
316 | console.log(request.body.body)
317 | console.log(request.query.querystring)
318 | console.log(request.params.params)
319 | console.log(request.headers.headers)
320 |
321 | return { 'aa': 'boo' }
322 | })
323 | })
324 |
325 | const b = cpl()
326 | b(async function (service) {}, {
327 | ajv: {
328 | plugins: {'ajv-formats': {formats: ['date-time']}}
329 | },
330 | vocabulary: ['my-keyword']
331 | })
332 |
333 | async function invokeProxies() {
334 | const httpClient = getHttpClient('http://service_name')
335 | const httpClientWithOptions = getHttpClient('http://service_name:3000', {
336 | timeout: 1000,
337 | })
338 | await httpClient.get('/path')
339 | await httpClientWithOptions.get('/path')
340 | }
341 |
--------------------------------------------------------------------------------
/tests/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "noEmit": true,
6 | "strict": true
7 | },
8 | "files": [
9 | "./index.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------