@readme/oas-to-har 16.1.0 → 17.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/LICENSE +1 -1
- package/README.md +27 -11
- package/karma.conf.js +14 -0
- package/package.json +15 -10
- package/src/index.js +64 -39
- package/src/lib/style-formatting/index.js +90 -36
- package/src/lib/style-formatting/style-serializer.js +216 -124
- package/.github/dependabot.yml +0 -26
- package/.github/workflows/ci.yml +0 -24
- package/.github/workflows/codeql-analysis.yml +0 -35
- package/.husky/commit-msg +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## <small>17.0.2 (2022-04-25)</small>
|
|
2
|
+
|
|
3
|
+
* test: adding some clearer test coverage for raw string payloads ([a47ebc1](https://github.com/readmeio/oas-to-har/commit/a47ebc1))
|
|
4
|
+
* feat: adding support for RAW_BODY payloads when not on JSON-like content types (#81) ([ca2d395](https://github.com/readmeio/oas-to-har/commit/ca2d395)), closes [#81](https://github.com/readmeio/oas-to-har/issues/81)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## <small>17.0.1 (2022-04-22)</small>
|
|
9
|
+
|
|
10
|
+
* fix: issue where a lowercase `Accept` header may be duplicated (#80) ([cb9e1b4](https://github.com/readmeio/oas-to-har/commit/cb9e1b4)), closes [#80](https://github.com/readmeio/oas-to-har/issues/80)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 17.0.0 (2022-04-22)
|
|
15
|
+
|
|
16
|
+
> **BREAKING RELEASE**
|
|
17
|
+
>
|
|
18
|
+
> Node 12 is no longer supported.
|
|
19
|
+
|
|
20
|
+
* chore: adding some more exclusions into the npmignore list ([25b3121](https://github.com/readmeio/oas-to-har/commit/25b3121))
|
|
21
|
+
* chore(deps-dev): bump @readme/oas-examples from 4.5.0 to 5.0.0 (#75) ([845535b](https://github.com/readmeio/oas-to-har/commit/845535b)), closes [#75](https://github.com/readmeio/oas-to-har/issues/75)
|
|
22
|
+
* chore(deps-dev): bump eslint from 8.11.0 to 8.12.0 (#73) ([c26d37c](https://github.com/readmeio/oas-to-har/commit/c26d37c)), closes [#73](https://github.com/readmeio/oas-to-har/issues/73)
|
|
23
|
+
* chore(deps-dev): bump prettier from 2.6.0 to 2.6.1 (#72) ([24613fc](https://github.com/readmeio/oas-to-har/commit/24613fc)), closes [#72](https://github.com/readmeio/oas-to-har/issues/72)
|
|
24
|
+
* chore(deps): bump actions/checkout from 2.4.0 to 3 (#71) ([38c66a7](https://github.com/readmeio/oas-to-har/commit/38c66a7)), closes [#71](https://github.com/readmeio/oas-to-har/issues/71)
|
|
25
|
+
* chore(deps): bump oas from 18.0.6 to 18.1.0 (#74) ([93c2bc4](https://github.com/readmeio/oas-to-har/commit/93c2bc4)), closes [#74](https://github.com/readmeio/oas-to-har/issues/74)
|
|
26
|
+
* fix: code cleanup (#79) ([245dcbb](https://github.com/readmeio/oas-to-har/commit/245dcbb)), closes [#79](https://github.com/readmeio/oas-to-har/issues/79)
|
|
27
|
+
* fix: support for allowReserved (#76) ([f609297](https://github.com/readmeio/oas-to-har/commit/f609297)), closes [#76](https://github.com/readmeio/oas-to-har/issues/76)
|
|
28
|
+
* fix: support for multi-level deepObjects (#77) ([b287f68](https://github.com/readmeio/oas-to-har/commit/b287f68)), closes [#77](https://github.com/readmeio/oas-to-har/issues/77)
|
|
29
|
+
* feat: browser testing! (#78) ([5ebe4d4](https://github.com/readmeio/oas-to-har/commit/5ebe4d4)), closes [#78](https://github.com/readmeio/oas-to-har/issues/78)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
1
33
|
## 16.1.0 (2022-03-23)
|
|
2
34
|
|
|
3
35
|
* chore: removing some docs from the repo as they're in our .github/ repo now ([f3d81c3](https://github.com/readmeio/oas-to-har/commit/f3d81c3))
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -15,14 +15,37 @@ npm install --save @readme/oas-to-har
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
|
-
const Oas = require('oas');
|
|
18
|
+
const Oas = require('oas').default;
|
|
19
19
|
const oasToHar = require('@readme/oas-to-har');
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const petstore = require('./petstore.json');
|
|
22
|
+
|
|
23
|
+
const spec = new Oas(petstore);
|
|
24
|
+
console.log(oasToHar(spec, spec.operation('/pets', 'post')));
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
log: {
|
|
30
|
+
entries: [
|
|
31
|
+
{
|
|
32
|
+
request: {
|
|
33
|
+
cookies: [],
|
|
34
|
+
headers: [],
|
|
35
|
+
headersSize: 0,
|
|
36
|
+
queryString: [],
|
|
37
|
+
bodySize: 0,
|
|
38
|
+
method: 'POST',
|
|
39
|
+
url: 'http://petstore.swagger.io/v2/pets',
|
|
40
|
+
httpVersion: 'HTTP/1.1'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
23
46
|
```
|
|
24
47
|
|
|
25
|
-
### `oasToHar(
|
|
48
|
+
### `oasToHar(oas, operationSchema, values, auth, opts) => Object`
|
|
26
49
|
|
|
27
50
|
- `oas` *{Oas}*: Instance of our [oas/tooling](https://npm.im/oas) class.
|
|
28
51
|
- `operationSchema` *{Object\|Operation}*: Can either be an object with `path` and `method` properties (that exist in the supplied OAS), or an instance of our `Operation` class from [oas/tooling](https://npm.im/oas) - accessed through `new Oas(spec).operation(path, method)`.
|
|
@@ -36,10 +59,3 @@ console.log(oasToHar(spec, { path: '/pets', method: 'post'}));
|
|
|
36
59
|
- `server` — If the supplied OAS has multiple severs or server variables you can use this to set which server and variables to use. Shape of it should be: `{ selected: Integer, variables: { ...key-values }}`. `selected` should coorespond to index of the `servers` array in your OAS.
|
|
37
60
|
- `auth` *{Object}*: Authentication information for the request.
|
|
38
61
|
- `opts.proxyUrl` *{Boolean}*: Boolean to toggle if composed HAR objects should have their `url` be sent through our CORS-friendly proxy. Defaults to `false`.
|
|
39
|
-
|
|
40
|
-
## Credits
|
|
41
|
-
[Jon Ursenbach](https://github.com/erunion)
|
|
42
|
-
|
|
43
|
-
## License
|
|
44
|
-
|
|
45
|
-
ISC
|
package/karma.conf.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { karmaConfig } = require('@jsdevtools/karma-config');
|
|
2
|
+
const { host } = require('@jsdevtools/host-environment');
|
|
3
|
+
|
|
4
|
+
module.exports = karmaConfig({
|
|
5
|
+
sourceDir: '.',
|
|
6
|
+
fixtures: ['test/__datasets__/*.json'],
|
|
7
|
+
browsers: {
|
|
8
|
+
chrome: true,
|
|
9
|
+
firefox: true,
|
|
10
|
+
safari: host.os.mac,
|
|
11
|
+
edge: false,
|
|
12
|
+
ie: false,
|
|
13
|
+
},
|
|
14
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readme/oas-to-har",
|
|
3
3
|
"description": "Utility to transform an OAS operation into a HAR representation",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "17.0.2",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "Jon Ursenbach <jon@ursenba.ch>",
|
|
7
7
|
"license": "ISC",
|
|
@@ -10,34 +10,39 @@
|
|
|
10
10
|
"url": "git://github.com/readmeio/oas-to-har.git"
|
|
11
11
|
},
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": "
|
|
13
|
+
"node": ">=14"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"lint": "eslint .",
|
|
17
|
-
"lint:docs": "alex . .github/",
|
|
18
17
|
"prepare": "husky install",
|
|
19
|
-
"pretest": "npm run lint
|
|
18
|
+
"pretest": "npm run lint",
|
|
20
19
|
"prettier": "prettier --list-different --write \"./**/**.{js}\"",
|
|
21
20
|
"release": "npx conventional-changelog-cli -i CHANGELOG.md -s && git add CHANGELOG.md",
|
|
22
|
-
"test": "
|
|
21
|
+
"test:browser": "karma start --single-run",
|
|
22
|
+
"test:browser:chrome": "karma start --browsers=Chrome --single-run=false",
|
|
23
|
+
"test:browser:debug": "karma start --single-run=false",
|
|
24
|
+
"test": "nyc mocha"
|
|
23
25
|
},
|
|
24
26
|
"dependencies": {
|
|
25
27
|
"@readme/oas-extensions": "^14.2.0",
|
|
26
28
|
"oas": "^18.0.6",
|
|
27
29
|
"parse-data-url": "^4.0.1",
|
|
30
|
+
"qs": "^6.10.3",
|
|
28
31
|
"remove-undefined-objects": "^1.1.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@commitlint/cli": "^16.2.3",
|
|
32
35
|
"@commitlint/config-conventional": "^16.0.0",
|
|
36
|
+
"@jsdevtools/host-environment": "^2.1.2",
|
|
37
|
+
"@jsdevtools/karma-config": "^3.1.7",
|
|
33
38
|
"@readme/eslint-config": "^8.5.1",
|
|
34
|
-
"@readme/oas-examples": "^
|
|
35
|
-
"
|
|
36
|
-
"datauri": "^4.1.0",
|
|
39
|
+
"@readme/oas-examples": "^5.1.1",
|
|
40
|
+
"chai": "^4.3.6",
|
|
37
41
|
"eslint": "^8.11.0",
|
|
42
|
+
"har-validator": "^5.1.5",
|
|
38
43
|
"husky": "^7.0.4",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
44
|
+
"mocha": "^9.2.2",
|
|
45
|
+
"nyc": "^15.1.0",
|
|
41
46
|
"prettier": "^2.6.0"
|
|
42
47
|
},
|
|
43
48
|
"prettier": "@readme/eslint-config/prettier",
|
package/src/index.js
CHANGED
|
@@ -11,8 +11,8 @@ const { jsonSchemaTypes } = utils;
|
|
|
11
11
|
function formatter(values, param, type, onlyIfExists) {
|
|
12
12
|
if (param.style) {
|
|
13
13
|
const value = values[type][param.name];
|
|
14
|
-
// Note: Technically we could send everything through the format style and choose the proper
|
|
15
|
-
//
|
|
14
|
+
// Note: Technically we could send everything through the format style and choose the proper
|
|
15
|
+
// default for each `in` type (e.g. query defaults to form).
|
|
16
16
|
return formatStyle(value, param);
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -26,18 +26,20 @@ function formatter(values, param, type, onlyIfExists) {
|
|
|
26
26
|
} else if (param.required && param.schema && param.schema.default) {
|
|
27
27
|
value = param.schema.default;
|
|
28
28
|
} else if (type === 'path') {
|
|
29
|
-
// If we don't have any values for the path parameter, just use the name of the parameter as the
|
|
30
|
-
// try try to build a URL to something like `https://example.com/undefined`.
|
|
29
|
+
// If we don't have any values for the path parameter, just use the name of the parameter as the
|
|
30
|
+
// value so we don't try try to build a URL to something like `https://example.com/undefined`.
|
|
31
31
|
return param.name;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Handle file uploads. Specifically arrays of file uploads which need to be formatted very
|
|
34
|
+
// Handle file uploads. Specifically arrays of file uploads which need to be formatted very
|
|
35
|
+
// specifically.
|
|
35
36
|
if (param.schema && param.schema.type === 'array' && param.schema.items && param.schema.items.format === 'binary') {
|
|
36
37
|
return JSON.stringify(value);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
if (value !== undefined) {
|
|
40
|
-
// Query params should always be formatted, even if they don't have a `style` serialization
|
|
41
|
+
// Query params should always be formatted, even if they don't have a `style` serialization
|
|
42
|
+
// configured.
|
|
41
43
|
if (type === 'query') {
|
|
42
44
|
return formatStyle(value, param);
|
|
43
45
|
}
|
|
@@ -77,7 +79,8 @@ function multipartBodyToFormatterParams(multipartBody, oasMediaTypeObject) {
|
|
|
77
79
|
.filter(Boolean);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// Pretty sure that we'll never have anything but an object for multipart bodies, so returning
|
|
82
|
+
// Pretty sure that we'll never have anything but an object for multipart bodies, so returning
|
|
83
|
+
// empty array if we get anything else.
|
|
81
84
|
return [];
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -118,12 +121,14 @@ function appendHarValue(harParam, name, value, addtlData = {}) {
|
|
|
118
121
|
if (typeof value === 'undefined') return;
|
|
119
122
|
|
|
120
123
|
if (Array.isArray(value)) {
|
|
121
|
-
// If the formatter gives us an array, we're expected to add each array value as a new
|
|
124
|
+
// If the formatter gives us an array, we're expected to add each array value as a new
|
|
125
|
+
// parameter item with the same parameter name
|
|
122
126
|
value.forEach(singleValue => {
|
|
123
127
|
appendHarValue(harParam, name, singleValue);
|
|
124
128
|
});
|
|
125
129
|
} else if (typeof value === 'object' && value !== null) {
|
|
126
|
-
// If the formatter gives us an object, we're expected to add each property value as a new
|
|
130
|
+
// If the formatter gives us an object, we're expected to add each property value as a new
|
|
131
|
+
// parameter item, each with the name of the property
|
|
127
132
|
Object.keys(value).forEach(key => {
|
|
128
133
|
appendHarValue(harParam, key, value[key]);
|
|
129
134
|
});
|
|
@@ -143,8 +148,8 @@ module.exports = (
|
|
|
143
148
|
values = {},
|
|
144
149
|
auth = {},
|
|
145
150
|
opts = {
|
|
146
|
-
// If true, the operation URL will be rewritten and prefixed with https://try.readme.io/ in
|
|
147
|
-
// through our CORS-friendly proxy.
|
|
151
|
+
// If true, the operation URL will be rewritten and prefixed with https://try.readme.io/ in
|
|
152
|
+
// order to funnel requests through our CORS-friendly proxy.
|
|
148
153
|
proxyUrl: false,
|
|
149
154
|
}
|
|
150
155
|
) => {
|
|
@@ -196,7 +201,8 @@ module.exports = (
|
|
|
196
201
|
// Find the path parameter or set a default value if it does not exist
|
|
197
202
|
const parameter = parameters.find(param => param.name === key) || { name: key };
|
|
198
203
|
|
|
199
|
-
// The library that handles our style processing already encodes uri elements. For everything
|
|
204
|
+
// The library that handles our style processing already encodes uri elements. For everything
|
|
205
|
+
// else we need to handle it here.
|
|
200
206
|
if (!parameter.style) {
|
|
201
207
|
return encodeURIComponent(formatter(formData, parameter, 'path'));
|
|
202
208
|
}
|
|
@@ -226,8 +232,9 @@ module.exports = (
|
|
|
226
232
|
Object.keys(operation.schema.responses).some(response => {
|
|
227
233
|
if (!operation.schema.responses[response].content) return false;
|
|
228
234
|
|
|
229
|
-
//
|
|
230
|
-
|
|
235
|
+
// If there's no `Accept` header present we should add one so their eventual code snippet
|
|
236
|
+
// follows best practices.
|
|
237
|
+
if (Object.keys(formData.header).find(h => h.toLowerCase() === 'accept')) return true;
|
|
231
238
|
|
|
232
239
|
har.headers.push({
|
|
233
240
|
name: 'Accept',
|
|
@@ -272,11 +279,14 @@ module.exports = (
|
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
// Do we have an `Accept` header set up in the form, but it hasn't been added yet?
|
|
275
|
-
if (formData.header
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
282
|
+
if (formData.header) {
|
|
283
|
+
const acceptHeader = Object.keys(formData.header).find(h => h.toLowerCase() === 'accept');
|
|
284
|
+
if (acceptHeader && !har.headers.find(hdr => hdr.name.toLowerCase() === 'accept')) {
|
|
285
|
+
har.headers.push({
|
|
286
|
+
name: 'Accept',
|
|
287
|
+
value: String(formData.header[acceptHeader]),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
let requestBody = false;
|
|
@@ -316,8 +326,9 @@ module.exports = (
|
|
|
316
326
|
har.postData.mimeType = 'multipart/form-data';
|
|
317
327
|
har.postData.params = [];
|
|
318
328
|
|
|
319
|
-
// Discover all `{ type: string, format: binary }` properties the schema. If there are
|
|
320
|
-
// that we're dealing with a `multipart/form-data` request and
|
|
329
|
+
// Discover all `{ type: string, format: binary }` properties the schema. If there are
|
|
330
|
+
// any, then that means that we're dealing with a `multipart/form-data` request and
|
|
331
|
+
// need to treat the payload as `postData.params`.
|
|
321
332
|
const binaryTypes = Object.keys(requestBody.schema.properties).filter(
|
|
322
333
|
key => requestBody.schema.properties[key].format === 'binary'
|
|
323
334
|
);
|
|
@@ -333,9 +344,10 @@ module.exports = (
|
|
|
333
344
|
|
|
334
345
|
const value = formatter(formData, param, 'body', true);
|
|
335
346
|
|
|
336
|
-
// If we're dealing with a binary type, and the value is a valid data URL we should
|
|
337
|
-
// available filename and content type to send along with the
|
|
338
|
-
// can make sense of it and send a usable
|
|
347
|
+
// If we're dealing with a binary type, and the value is a valid data URL we should
|
|
348
|
+
// parse out any available filename and content type to send along with the
|
|
349
|
+
// parameter to interpreters like `fetch-har` can make sense of it and send a usable
|
|
350
|
+
// payload.
|
|
339
351
|
const addtlData = {};
|
|
340
352
|
|
|
341
353
|
if (binaryTypes.includes(name)) {
|
|
@@ -355,17 +367,21 @@ module.exports = (
|
|
|
355
367
|
har.postData.mimeType = contentType;
|
|
356
368
|
|
|
357
369
|
// Handle arbitrary JSON input via a string.
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
// In the UI this is represented by an arbitrary text input
|
|
361
|
-
//
|
|
370
|
+
//
|
|
371
|
+
// In OAS you usually find this in an `application/json` content type with a schema
|
|
372
|
+
// `type=string, format=json`. In the UI this is represented by an arbitrary text input.
|
|
373
|
+
//
|
|
374
|
+
// This ensures we remove any newlines or tabs and use a clean JSON block in the
|
|
375
|
+
// example.
|
|
362
376
|
if (requestBody.schema.type === 'string') {
|
|
363
377
|
har.postData.text = JSON.stringify(JSON.parse(cleanBody));
|
|
364
378
|
} else {
|
|
365
|
-
// Handle formatted JSON objects that have properties that accept arbitrary JSON
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
379
|
+
// Handle formatted JSON objects that have properties that accept arbitrary JSON.
|
|
380
|
+
//
|
|
381
|
+
// Find all `{ type: string, format: json }` properties in the schema because we need
|
|
382
|
+
// to manually `JSON.parse` them before submit, otherwise they'll be escaped instead
|
|
383
|
+
// of actual objects. We also only want values that the user has entered, so we drop
|
|
384
|
+
// any `undefined` `cleanBody` keys
|
|
369
385
|
const jsonTypes = Object.keys(requestBody.schema.properties).filter(
|
|
370
386
|
key => requestBody.schema.properties[key].format === 'json' && cleanBody[key] !== undefined
|
|
371
387
|
);
|
|
@@ -380,7 +396,8 @@ module.exports = (
|
|
|
380
396
|
}
|
|
381
397
|
});
|
|
382
398
|
|
|
383
|
-
// `RAW_BODY` is a ReadMe-specific thing where we'll interpret
|
|
399
|
+
// `RAW_BODY` is a ReadMe-specific thing where we'll interpret the entire payload
|
|
400
|
+
// as a raw string. https://docs.readme.com/docs/raw-body-content
|
|
384
401
|
if (typeof cleanBody.RAW_BODY !== 'undefined') {
|
|
385
402
|
cleanBody = cleanBody.RAW_BODY;
|
|
386
403
|
}
|
|
@@ -395,16 +412,23 @@ module.exports = (
|
|
|
395
412
|
}
|
|
396
413
|
}
|
|
397
414
|
} catch (e) {
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
// If anything above fails for whatever reason, assume that whatever we had is invalid JSON and just treat it
|
|
401
|
-
// as raw text.
|
|
415
|
+
// If anything above fails for whatever reason, assume that whatever we had is invalid
|
|
416
|
+
// JSON and just treat it as raw text.
|
|
402
417
|
har.postData.text = stringify(formData.body);
|
|
403
418
|
}
|
|
404
419
|
} else {
|
|
405
420
|
har.postData.mimeType = contentType;
|
|
406
421
|
if (isPrimitive(formData.body)) {
|
|
407
422
|
har.postData.text = formData.body;
|
|
423
|
+
} else if (
|
|
424
|
+
typeof formData.body === 'object' &&
|
|
425
|
+
formData.body !== null &&
|
|
426
|
+
!Array.isArray(formData.body) &&
|
|
427
|
+
typeof formData.body.RAW_BODY !== 'undefined'
|
|
428
|
+
) {
|
|
429
|
+
// `RAW_BODY` is a ReadMe-specific thing where we'll interpret the entire payload as a
|
|
430
|
+
// raw string. https://docs.readme.com/docs/raw-body-content
|
|
431
|
+
har.postData.text = formData.body.RAW_BODY;
|
|
408
432
|
} else {
|
|
409
433
|
har.postData.text = stringify(formData.body);
|
|
410
434
|
}
|
|
@@ -412,8 +436,9 @@ module.exports = (
|
|
|
412
436
|
}
|
|
413
437
|
}
|
|
414
438
|
|
|
415
|
-
// Add a `Content-Type` header if there are any body values setup above or if there is a schema
|
|
416
|
-
// so if we don't already have a `Content-Type` present as it's impossible
|
|
439
|
+
// Add a `Content-Type` header if there are any body values setup above or if there is a schema
|
|
440
|
+
// defined, but only do so if we don't already have a `Content-Type` present as it's impossible
|
|
441
|
+
// for a request to have multiple.
|
|
417
442
|
if ((har.postData.text || (requestBody && Object.keys(requestBody.schema).length)) && !hasContentType) {
|
|
418
443
|
har.headers.push({
|
|
419
444
|
name: 'Content-Type',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
const qs = require('qs');
|
|
1
2
|
const stylize = require('./style-serializer');
|
|
2
3
|
|
|
3
|
-
// Certain styles don't support empty values
|
|
4
|
+
// Certain styles don't support empty values.
|
|
4
5
|
function shouldNotStyleEmptyValues(parameter) {
|
|
5
6
|
return ['simple', 'spaceDelimited', 'pipeDelimited', 'deepObject'].includes(parameter.style);
|
|
6
7
|
}
|
|
@@ -9,9 +10,12 @@ function shouldNotStyleReservedHeader(parameter) {
|
|
|
9
10
|
return ['accept', 'authorization', 'content-type'].includes(parameter.name.toLowerCase());
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Note: This isn't necessarily part of the spec. Behavior for the value 'undefined' is, well,
|
|
15
|
+
* undefined. This code makes our system look better. If we wanted to be more accurate, we might
|
|
16
|
+
* want to remove this, restore the un-fixed behavior for undefined and have our UI pass in empty
|
|
17
|
+
* string instead of undefined.
|
|
18
|
+
*/
|
|
15
19
|
function removeUndefinedForPath(value) {
|
|
16
20
|
let finalValue = value;
|
|
17
21
|
|
|
@@ -41,35 +45,43 @@ function stylizeValue(value, parameter) {
|
|
|
41
45
|
|
|
42
46
|
// Some styles don't work with empty values. We catch those there
|
|
43
47
|
if (shouldNotStyleEmptyValues(parameter) && (typeof finalValue === 'undefined' || finalValue === '')) {
|
|
44
|
-
// Paths need return an unstyled empty string instead of undefined so it's ignored in the final
|
|
48
|
+
// Paths need return an unstyled empty string instead of undefined so it's ignored in the final
|
|
49
|
+
// path string.
|
|
45
50
|
if (parameter.in === 'path') {
|
|
46
51
|
return '';
|
|
47
52
|
}
|
|
48
|
-
|
|
53
|
+
|
|
54
|
+
// Everything but path should return undefined when unstyled so it's ignored in the final
|
|
55
|
+
// parameter array.
|
|
49
56
|
return undefined;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
// Every style that adds their style to empty values should use emptystring for path parameters
|
|
59
|
+
// Every style that adds their style to empty values should use emptystring for path parameters
|
|
60
|
+
// instead of undefined to avoid the string `undefined`.
|
|
53
61
|
if (parameter.in === 'path') {
|
|
54
62
|
finalValue = removeUndefinedForPath(finalValue);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Eventhough `Accept`, `Authorization`, and `Content-Type` headers can be defined as parameters,
|
|
67
|
+
* they should be completely ignored when it comes to serialization.
|
|
68
|
+
*
|
|
69
|
+
* > If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the
|
|
70
|
+
* > parameter definition SHALL be ignored.
|
|
71
|
+
*
|
|
72
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-10}
|
|
73
|
+
*/
|
|
64
74
|
if (parameter.in === 'header' && shouldNotStyleReservedHeader(parameter)) {
|
|
65
75
|
return value;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
/**
|
|
79
|
+
* All parameter types have a default `style` format so if they don't have one prescribed we
|
|
80
|
+
* should still conform to what the spec defines.
|
|
81
|
+
*
|
|
82
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-parameterstyle}
|
|
83
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parameterstyle}
|
|
84
|
+
*/
|
|
73
85
|
let style = parameter.style;
|
|
74
86
|
if (!style) {
|
|
75
87
|
if (parameter.in === 'query') {
|
|
@@ -85,10 +97,12 @@ function stylizeValue(value, parameter) {
|
|
|
85
97
|
|
|
86
98
|
let explode = parameter.explode;
|
|
87
99
|
if (explode === undefined && style === 'form') {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Per the spec if no `explode` is present but `style` is `form` then `explode` should default to `true`.
|
|
102
|
+
*
|
|
103
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-parameterexplode}
|
|
104
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parameterexplode}
|
|
105
|
+
*/
|
|
92
106
|
explode = true;
|
|
93
107
|
}
|
|
94
108
|
|
|
@@ -98,15 +112,49 @@ function stylizeValue(value, parameter) {
|
|
|
98
112
|
key: parameter.name,
|
|
99
113
|
style,
|
|
100
114
|
explode,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
/**
|
|
116
|
+
* @todo this parameter is optional to stylize. It defaults to false, and can accept falsy, truthy, or "unsafe".
|
|
117
|
+
* I do not know if it is correct for query to use this. See style-serializer for more info
|
|
118
|
+
*/
|
|
105
119
|
escape: true,
|
|
120
|
+
...(parameter.in === 'query' ? { isAllowedReserved: parameter.allowReserved || false } : {}),
|
|
106
121
|
});
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
|
|
124
|
+
function handleDeepObject(value, parameter) {
|
|
125
|
+
return qs
|
|
126
|
+
.stringify(value, {
|
|
127
|
+
// eslint-disable-next-line consistent-return
|
|
128
|
+
encoder(str, defaultEncoder, charset, type) {
|
|
129
|
+
if (type === 'key') {
|
|
130
|
+
// `str` will be here as `dog[treats][0]` but because the `qs` library doesn't have any
|
|
131
|
+
// awareness of our OpenAPI parameters we need to rewrite it to slap the `parameter.name`
|
|
132
|
+
// to the top, like `pets[dog][treats][0]`.
|
|
133
|
+
const prefixedKey = str
|
|
134
|
+
.split(/[[\]]/g)
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.map(k => `[${k}]`)
|
|
137
|
+
.join('');
|
|
138
|
+
|
|
139
|
+
return `${parameter.name}${prefixedKey}`;
|
|
140
|
+
} else if (type === 'value') {
|
|
141
|
+
return stylizeValue(str, parameter);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
.split('&')
|
|
146
|
+
.map(item => {
|
|
147
|
+
const split = item.split('=');
|
|
148
|
+
return {
|
|
149
|
+
label: split[0],
|
|
150
|
+
// `qs` will coerce null values into being `undefined` string but we want to preserve them.
|
|
151
|
+
value: split[1] === 'undefined' ? null : split[1],
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Explode is handled on its own, because style-serializer doesn't return what we expect for proper
|
|
157
|
+
// HAR output.
|
|
110
158
|
function handleExplode(value, parameter) {
|
|
111
159
|
if (Array.isArray(value)) {
|
|
112
160
|
return value.map(val => {
|
|
@@ -118,12 +166,13 @@ function handleExplode(value, parameter) {
|
|
|
118
166
|
const newObj = {};
|
|
119
167
|
|
|
120
168
|
Object.keys(value).forEach(key => {
|
|
121
|
-
const stylizedValue = stylizeValue(value[key], parameter);
|
|
122
|
-
|
|
123
169
|
if (parameter.style === 'deepObject') {
|
|
124
|
-
|
|
170
|
+
const deepObjs = handleDeepObject(value, parameter);
|
|
171
|
+
deepObjs.forEach(obj => {
|
|
172
|
+
newObj[obj.label] = obj.value;
|
|
173
|
+
});
|
|
125
174
|
} else {
|
|
126
|
-
newObj[key] =
|
|
175
|
+
newObj[key] = stylizeValue(value[key], parameter);
|
|
127
176
|
}
|
|
128
177
|
});
|
|
129
178
|
|
|
@@ -148,10 +197,15 @@ module.exports = function formatStyle(value, parameter) {
|
|
|
148
197
|
return undefined;
|
|
149
198
|
}
|
|
150
199
|
|
|
151
|
-
// This custom explode logic allows us to bubble up arrays and objects to be handled differently
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
200
|
+
// This custom explode logic allows us to bubble up arrays and objects to be handled differently
|
|
201
|
+
// by our HAR transformer. We need this because the `stylizeValue` function assumes we're building
|
|
202
|
+
// strings, not richer data types.
|
|
203
|
+
//
|
|
204
|
+
// The first part of this conditional checks if `explode` is enabled. Explode is disabled for
|
|
205
|
+
// everything by default except for forms.
|
|
206
|
+
//
|
|
207
|
+
// The second part of this conditional bypasses the custom explode logic for headers, because they
|
|
208
|
+
// work differently, and `stylizeValue` is accurate.
|
|
155
209
|
if (shouldExplode(parameter)) {
|
|
156
210
|
return handleExplode(value, parameter);
|
|
157
211
|
}
|
|
@@ -16,8 +16,8 @@ function isURIEncoded(value) {
|
|
|
16
16
|
try {
|
|
17
17
|
return decodeURIComponent(value) !== value;
|
|
18
18
|
} catch (err) {
|
|
19
|
-
// `decodeURIComponent` will throw an exception if a string that has an un-encoded percent sign
|
|
20
|
-
//
|
|
19
|
+
// `decodeURIComponent` will throw an exception if a string that has an un-encoded percent sign
|
|
20
|
+
// in it (like 20%), o if it's throwing we can just assume that the value hasn't been encoded.
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -36,7 +36,7 @@ module.exports = function stylize(config) {
|
|
|
36
36
|
|
|
37
37
|
module.exports.encodeDisallowedCharacters = function encodeDisallowedCharacters(
|
|
38
38
|
str,
|
|
39
|
-
{ escape, returnIfEncoded = false } = {}, // eslint-disable-line default-param-last
|
|
39
|
+
{ escape, returnIfEncoded = false, isAllowedReserved } = {}, // eslint-disable-line default-param-last
|
|
40
40
|
parse
|
|
41
41
|
) {
|
|
42
42
|
if (typeof str === 'number') {
|
|
@@ -61,17 +61,16 @@ module.exports.encodeDisallowedCharacters = function encodeDisallowedCharacters(
|
|
|
61
61
|
return JSON.parse(str);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// In ES6 you can do this quite easily by using the new ... spread operator.
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// code points rather than UCS-2/UTF-16 code units.
|
|
64
|
+
// In ES6 you can do this quite easily by using the new ... spread operator. This causes the
|
|
65
|
+
// string iterator (another new ES6 feature) to be used internally, and because that iterator is
|
|
66
|
+
// designed to deal with code points rather than UCS-2/UTF-16 code units.
|
|
68
67
|
return [...str]
|
|
69
68
|
.map(char => {
|
|
70
69
|
if (isRfc3986Unreserved(char)) {
|
|
71
70
|
return char;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
if (isRfc3986Reserved(char) && escape === 'unsafe') {
|
|
73
|
+
if (isRfc3986Reserved(char) && (escape === 'unsafe' || isAllowedReserved)) {
|
|
75
74
|
return char;
|
|
76
75
|
}
|
|
77
76
|
|
|
@@ -86,162 +85,255 @@ module.exports.encodeDisallowedCharacters = function encodeDisallowedCharacters(
|
|
|
86
85
|
.join('');
|
|
87
86
|
};
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
/**
|
|
89
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-examples}
|
|
90
|
+
*/
|
|
91
|
+
function encodeArray({ location, key, value, style, explode, escape, isAllowedReserved = false }) {
|
|
90
92
|
const valueEncoder = str =>
|
|
91
93
|
module.exports.encodeDisallowedCharacters(str, {
|
|
92
94
|
escape,
|
|
93
95
|
returnIfEncoded: location === 'query',
|
|
96
|
+
isAllowedReserved,
|
|
94
97
|
});
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
99
|
+
switch (style) {
|
|
100
|
+
/**
|
|
101
|
+
* @example <caption>`style: simple`</caption>
|
|
102
|
+
* `["blue","black","brown"]` → `blue,black,brown`
|
|
103
|
+
*/
|
|
104
|
+
case 'simple':
|
|
105
|
+
return value.map(val => valueEncoder(val)).join(',');
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @example <caption>`style: label`</caption>
|
|
109
|
+
* `["blue","black","brown"]` → `.blue.black.brown`
|
|
110
|
+
*/
|
|
111
|
+
case 'label':
|
|
112
|
+
return `.${value.map(val => valueEncoder(val)).join('.')}`;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @example <caption>`style: matrix` + `explode: true`</caption>
|
|
116
|
+
* `["blue","black","brown"]` → `;color=blue;color=black;color=brown`
|
|
117
|
+
*
|
|
118
|
+
* @example <caption>`style: matrix` + `explode: false` (the default behavior)</caption>
|
|
119
|
+
* `["blue","black","brown"]` → `;color=blue,black,brown `
|
|
120
|
+
*/
|
|
121
|
+
case 'matrix':
|
|
122
|
+
return value
|
|
123
|
+
.map(val => valueEncoder(val))
|
|
124
|
+
.reduce((prev, curr) => {
|
|
125
|
+
if (!prev || explode) {
|
|
126
|
+
return `${prev || ''};${key}=${curr}`;
|
|
127
|
+
}
|
|
128
|
+
return `${prev},${curr}`;
|
|
129
|
+
}, '');
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @example <caption>`style: form` + `explode: true`</caption>
|
|
133
|
+
* `["blue","black","brown"]` → `color=blue&color=black&color=brown`
|
|
134
|
+
*
|
|
135
|
+
* @example <caption>`style: form` + `explode: false` (the default behavior)</caption>
|
|
136
|
+
* `["blue","black","brown"]` → `ccolor=blue,black,brown`
|
|
137
|
+
*/
|
|
138
|
+
case 'form':
|
|
139
|
+
return value.map(val => valueEncoder(val)).join(explode ? `&${key}=` : ',');
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @example <caption>`style: spaceDelimited`</caption>
|
|
143
|
+
* `["blue","black","brown"]` → `blue%20black%20brown`
|
|
144
|
+
*/
|
|
145
|
+
case 'spaceDelimited':
|
|
146
|
+
return value.map(val => valueEncoder(val)).join(` ${explode ? `${key}=` : ''}`);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @example <caption>`style: pipeDelimited`</caption>
|
|
150
|
+
* `["blue","black","brown"]` → `blue|black|brown`
|
|
151
|
+
*/
|
|
152
|
+
case 'pipeDelimited':
|
|
153
|
+
return value.map(val => valueEncoder(val)).join(`|${explode ? `${key}=` : ''}`);
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
return undefined;
|
|
123
157
|
}
|
|
124
|
-
|
|
125
|
-
if (style === 'pipeDelimited') {
|
|
126
|
-
const after = explode ? `${key}=` : '';
|
|
127
|
-
return value.map(val => valueEncoder(val)).join(`|${after}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return undefined;
|
|
131
158
|
}
|
|
132
159
|
|
|
133
|
-
|
|
160
|
+
/**
|
|
161
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-examples}
|
|
162
|
+
*/
|
|
163
|
+
function encodeObject({ location, key, value, style, explode, escape, isAllowedReserved = false }) {
|
|
134
164
|
const valueEncoder = str =>
|
|
135
165
|
module.exports.encodeDisallowedCharacters(str, {
|
|
136
166
|
escape,
|
|
137
167
|
returnIfEncoded: location === 'query',
|
|
168
|
+
isAllowedReserved,
|
|
138
169
|
});
|
|
139
170
|
|
|
140
171
|
const valueKeys = Object.keys(value);
|
|
141
172
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
switch (style) {
|
|
174
|
+
/**
|
|
175
|
+
* @example <caption>`style: simple` + `explode: true`</caption>
|
|
176
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `R=100,G=200,B=150`
|
|
177
|
+
*
|
|
178
|
+
* @example <caption>`style: simple` + `explode: false` (the default behavior)</caption>
|
|
179
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `R,100,G,200,B,150`
|
|
180
|
+
*/
|
|
181
|
+
case 'simple':
|
|
182
|
+
return valueKeys.reduce((prev, curr) => {
|
|
183
|
+
const val = valueEncoder(value[curr]);
|
|
184
|
+
const middleChar = explode ? '=' : ',';
|
|
185
|
+
const prefix = prev ? `${prev},` : '';
|
|
186
|
+
|
|
187
|
+
return `${prefix}${curr}${middleChar}${val}`;
|
|
188
|
+
}, '');
|
|
157
189
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
190
|
+
/**
|
|
191
|
+
* @example <caption>`style: label` + `explode: true`</caption>
|
|
192
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `.R=100.G=200.B=150`
|
|
193
|
+
*
|
|
194
|
+
* @example <caption>`style: label` + `explode: false` (the default behavior)</caption>
|
|
195
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `.R.100.G.200.B.150`
|
|
196
|
+
*/
|
|
197
|
+
case 'label':
|
|
198
|
+
return valueKeys.reduce((prev, curr) => {
|
|
199
|
+
const val = valueEncoder(value[curr]);
|
|
200
|
+
const middleChar = explode ? '=' : '.';
|
|
201
|
+
const prefix = prev ? `${prev}.` : '.';
|
|
202
|
+
|
|
203
|
+
return `${prefix}${curr}${middleChar}${val}`;
|
|
204
|
+
}, '');
|
|
161
205
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
206
|
+
/**
|
|
207
|
+
* @example <caption>`style: matrix` + `explode: true`</caption>
|
|
208
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `;R=100;G=200;B=150`
|
|
209
|
+
*
|
|
210
|
+
* @example <caption>`style: matrix` + `explode: false` (the default behavior)</caption>
|
|
211
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `;color=R,100,G,200,B,150`
|
|
212
|
+
*/
|
|
213
|
+
case 'matrix':
|
|
214
|
+
if (explode) {
|
|
215
|
+
return valueKeys.reduce((prev, curr) => {
|
|
216
|
+
const val = valueEncoder(value[curr]);
|
|
217
|
+
const prefix = prev ? `${prev};` : ';';
|
|
218
|
+
|
|
219
|
+
return `${prefix}${curr}=${val}`;
|
|
220
|
+
}, '');
|
|
221
|
+
}
|
|
166
222
|
|
|
167
|
-
return
|
|
168
|
-
|
|
169
|
-
|
|
223
|
+
return valueKeys.reduce((prev, curr) => {
|
|
224
|
+
const val = valueEncoder(value[curr]);
|
|
225
|
+
const prefix = prev ? `${prev},` : `;${key}=`;
|
|
170
226
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return valueKeys.reduce((prev, curr) => {
|
|
174
|
-
const val = valueEncoder(value[curr]);
|
|
175
|
-
const prefix = prev ? `${prev},` : `;${key}=`;
|
|
227
|
+
return `${prefix}${curr},${val}`;
|
|
228
|
+
}, '');
|
|
176
229
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
230
|
+
/**
|
|
231
|
+
* @example <caption>`style: form` + `explode: true`</caption>
|
|
232
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `R=100&G=200&B=150`
|
|
233
|
+
*
|
|
234
|
+
* @example <caption>`style: form` + `explode: false` (the default behavior)</caption>
|
|
235
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `color=R,100,G,200,B,150`
|
|
236
|
+
*/
|
|
237
|
+
case 'form':
|
|
238
|
+
return valueKeys.reduce((prev, curr) => {
|
|
239
|
+
const val = valueEncoder(value[curr]);
|
|
240
|
+
const prefix = prev ? `${prev}${explode ? '&' : ','}` : '';
|
|
241
|
+
const separator = explode ? '=' : ',';
|
|
242
|
+
|
|
243
|
+
return `${prefix}${curr}${separator}${val}`;
|
|
244
|
+
}, '');
|
|
180
245
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
246
|
+
/**
|
|
247
|
+
* @example <caption>`style: spaceDelimited`</caption>
|
|
248
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `R%20100%20G%20200%20B%20150`
|
|
249
|
+
*/
|
|
250
|
+
case 'spaceDelimited':
|
|
251
|
+
return valueKeys.reduce((prev, curr) => {
|
|
252
|
+
const val = valueEncoder(value[curr]);
|
|
253
|
+
const prefix = prev ? `${prev} ` : '';
|
|
186
254
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
255
|
+
return `${prefix}${curr} ${val}`;
|
|
256
|
+
}, '');
|
|
190
257
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
258
|
+
/**
|
|
259
|
+
* @example <caption>`style: pipeDelimited`</caption>
|
|
260
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `R|100|G|200|B|150`
|
|
261
|
+
*/
|
|
262
|
+
case 'pipeDelimited':
|
|
263
|
+
return valueKeys.reduce((prev, curr) => {
|
|
264
|
+
const val = valueEncoder(value[curr]);
|
|
265
|
+
const prefix = prev ? `${prev}|` : '';
|
|
196
266
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
267
|
+
return `${prefix}${curr}|${val}`;
|
|
268
|
+
}, '');
|
|
200
269
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
270
|
+
/**
|
|
271
|
+
* @example <caption>`style: deepObject`</caption>
|
|
272
|
+
* `{ "R": 100, "G": 200, "B": 150 }` → `color[R]=100&color[G]=200&color[B]=150`
|
|
273
|
+
*/
|
|
274
|
+
case 'deepObject':
|
|
275
|
+
return valueKeys.reduce(curr => {
|
|
276
|
+
const val = valueEncoder(value[curr], {}, true);
|
|
277
|
+
return `${val}`;
|
|
278
|
+
}, '');
|
|
206
279
|
|
|
207
|
-
|
|
208
|
-
|
|
280
|
+
default:
|
|
281
|
+
return undefined;
|
|
209
282
|
}
|
|
210
|
-
|
|
211
|
-
return undefined;
|
|
212
283
|
}
|
|
213
284
|
|
|
214
|
-
|
|
285
|
+
/**
|
|
286
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-examples}
|
|
287
|
+
*/
|
|
288
|
+
function encodePrimitive({ location, key, value, style, escape, isAllowedReserved = false }) {
|
|
215
289
|
const valueEncoder = str =>
|
|
216
290
|
module.exports.encodeDisallowedCharacters(str, {
|
|
217
291
|
escape,
|
|
218
292
|
returnIfEncoded: location === 'query' || location === 'body',
|
|
293
|
+
isAllowedReserved,
|
|
219
294
|
});
|
|
220
295
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
296
|
+
switch (style) {
|
|
297
|
+
/**
|
|
298
|
+
* @example <caption>`style: simple`</caption>
|
|
299
|
+
* `blue` → `blue`
|
|
300
|
+
*/
|
|
301
|
+
case 'simple':
|
|
302
|
+
return valueEncoder(value);
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @example <caption>`style: label`</caption>
|
|
306
|
+
* `blue` → `.blue`
|
|
307
|
+
*/
|
|
308
|
+
case 'label':
|
|
309
|
+
return `.${valueEncoder(value)}`;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @example <caption>`style: matrix`</caption>
|
|
313
|
+
* `blue` → `;color=blue`
|
|
314
|
+
*/
|
|
315
|
+
case 'matrix':
|
|
316
|
+
if (value === '') {
|
|
317
|
+
return `;${key}`;
|
|
318
|
+
}
|
|
228
319
|
|
|
229
|
-
|
|
230
|
-
// This conditional added by Aaron to be more accurate to the spec
|
|
231
|
-
if (value === '') {
|
|
232
|
-
return `;${key}`;
|
|
233
|
-
}
|
|
320
|
+
return `;${key}=${valueEncoder(value)}`;
|
|
234
321
|
|
|
235
|
-
|
|
236
|
-
|
|
322
|
+
/**
|
|
323
|
+
* @example <caption>`style: form`</caption>
|
|
324
|
+
* `blue` → `color=blue`
|
|
325
|
+
*/
|
|
326
|
+
case 'form':
|
|
327
|
+
return valueEncoder(value);
|
|
237
328
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
329
|
+
/**
|
|
330
|
+
* @example <caption>`style: deepObject`</caption>
|
|
331
|
+
* `blue` → n/a
|
|
332
|
+
*/
|
|
333
|
+
case 'deepObject':
|
|
334
|
+
return valueEncoder(value, {}, true);
|
|
241
335
|
|
|
242
|
-
|
|
243
|
-
|
|
336
|
+
default:
|
|
337
|
+
return undefined;
|
|
244
338
|
}
|
|
245
|
-
|
|
246
|
-
return undefined;
|
|
247
339
|
}
|
package/.github/dependabot.yml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
- package-ecosystem: github-actions
|
|
4
|
-
directory: "/"
|
|
5
|
-
schedule:
|
|
6
|
-
interval: monthly
|
|
7
|
-
reviewers:
|
|
8
|
-
- erunion
|
|
9
|
-
labels:
|
|
10
|
-
- dependencies
|
|
11
|
-
commit-message:
|
|
12
|
-
prefix: chore(deps)
|
|
13
|
-
prefix-development: chore(deps-dev)
|
|
14
|
-
|
|
15
|
-
- package-ecosystem: npm
|
|
16
|
-
directory: "/"
|
|
17
|
-
schedule:
|
|
18
|
-
interval: monthly
|
|
19
|
-
open-pull-requests-limit: 10
|
|
20
|
-
reviewers:
|
|
21
|
-
- erunion
|
|
22
|
-
labels:
|
|
23
|
-
- dependencies
|
|
24
|
-
commit-message:
|
|
25
|
-
prefix: chore(deps)
|
|
26
|
-
prefix-development: chore(deps-dev)
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on: [push]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
strategy:
|
|
9
|
-
matrix:
|
|
10
|
-
node-version: [12.x, 14.x, 16.x]
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v2.4.0
|
|
14
|
-
|
|
15
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
-
uses: actions/setup-node@v3
|
|
17
|
-
with:
|
|
18
|
-
node-version: ${{ matrix.node-version }}
|
|
19
|
-
|
|
20
|
-
- name: Install deps
|
|
21
|
-
run: npm ci
|
|
22
|
-
|
|
23
|
-
- name: Run tests
|
|
24
|
-
run: npm test
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: "CodeQL"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ main ]
|
|
8
|
-
schedule:
|
|
9
|
-
- cron: '0 0 1 * *'
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
analyze:
|
|
13
|
-
name: Analyze
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
permissions:
|
|
16
|
-
actions: read
|
|
17
|
-
contents: read
|
|
18
|
-
security-events: write
|
|
19
|
-
|
|
20
|
-
strategy:
|
|
21
|
-
fail-fast: false
|
|
22
|
-
matrix:
|
|
23
|
-
language: [ 'javascript' ]
|
|
24
|
-
|
|
25
|
-
steps:
|
|
26
|
-
- name: Checkout repository
|
|
27
|
-
uses: actions/checkout@v2.4.0
|
|
28
|
-
|
|
29
|
-
- name: Initialize CodeQL
|
|
30
|
-
uses: github/codeql-action/init@v1
|
|
31
|
-
with:
|
|
32
|
-
languages: ${{ matrix.language }}
|
|
33
|
-
|
|
34
|
-
- name: Perform CodeQL Analysis
|
|
35
|
-
uses: github/codeql-action/analyze@v1
|
package/.husky/commit-msg
DELETED