@readme/oas-to-har 20.1.0 → 21.0.0

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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
3
+ "editor.codeActionsOnSave": {
4
+ "source.fixAll": true
5
+ }
6
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 21.0.0 (2023-06-26)
2
+
3
+ * fix: quirk with multipart/form-data schema payloads being handled as `text` (#212) ([aac989f](https://github.com/readmeio/oas-to-har/commit/aac989f)), closes [#212](https://github.com/readmeio/oas-to-har/issues/212)
4
+ * feat: dropping support for node 14 (#211) ([30e9e2f](https://github.com/readmeio/oas-to-har/commit/30e9e2f)), closes [#211](https://github.com/readmeio/oas-to-har/issues/211)
5
+ * chore(deps-dev): bump typescript from 4.9.5 to 5.0.4 (#186) ([df15481](https://github.com/readmeio/oas-to-har/commit/df15481)), closes [#186](https://github.com/readmeio/oas-to-har/issues/186)
6
+ * chore(deps-dev): bumping out of date deps ([fb710c7](https://github.com/readmeio/oas-to-har/commit/fb710c7))
7
+ * chore(deps): bump socket.io-parser from 4.2.1 to 4.2.3 (#193) ([f4c55ba](https://github.com/readmeio/oas-to-har/commit/f4c55ba)), closes [#193](https://github.com/readmeio/oas-to-har/issues/193)
8
+ * chore(deps): bumping out of date deps ([92f7623](https://github.com/readmeio/oas-to-har/commit/92f7623))
9
+ * chore(deps): bumping out of date deps ([b1a1e85](https://github.com/readmeio/oas-to-har/commit/b1a1e85))
10
+ * chore(deps): bumping out of date deps ([7f63b39](https://github.com/readmeio/oas-to-har/commit/7f63b39))
11
+ * refactor: moving our test suite back to jest (#202) ([ee3eb98](https://github.com/readmeio/oas-to-har/commit/ee3eb98)), closes [#202](https://github.com/readmeio/oas-to-har/issues/202)
12
+ * ci: run tests against node 20 ([b3353df](https://github.com/readmeio/oas-to-har/commit/b3353df))
13
+
14
+
15
+
16
+ ## <small>20.1.1 (2023-03-02)</small>
17
+
18
+ * chore: bumping out of date deps ([864ed47](https://github.com/readmeio/oas-to-har/commit/864ed47))
19
+ * build: 20.1.0 release ([91e2319](https://github.com/readmeio/oas-to-har/commit/91e2319))
20
+
21
+
22
+
1
23
  ## 20.1.0 (2023-03-01)
2
24
 
3
25
  * feat: add support for arrays of objects in querystrings (#170) ([b1d6f51](https://github.com/readmeio/oas-to-har/commit/b1d6f51)), closes [#170](https://github.com/readmeio/oas-to-har/issues/170) [/github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-10](https://github.com//github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md/issues/fixed-fields-10)
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ var __read = (this && this.__read) || function (o, n) {
52
52
  var __importDefault = (this && this.__importDefault) || function (mod) {
53
53
  return (mod && mod.__esModule) ? mod : { "default": mod };
54
54
  };
55
- exports.__esModule = true;
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
56
  var data_urls_1 = require("@readme/data-urls");
57
57
  var extensions = __importStar(require("@readme/oas-extensions"));
58
58
  var oas_1 = require("oas");
@@ -67,7 +67,7 @@ function formatter(values, param, type, onlyIfExists) {
67
67
  var value_1 = values[type][param.name];
68
68
  // Note: Technically we could send everything through the format style and choose the proper
69
69
  // default for each `in` type (e.g. query defaults to form).
70
- return (0, style_formatting_1["default"])(value_1, param);
70
+ return (0, style_formatting_1.default)(value_1, param);
71
71
  }
72
72
  var value;
73
73
  // Handle missing values
@@ -77,8 +77,8 @@ function formatter(values, param, type, onlyIfExists) {
77
77
  else if (onlyIfExists && !param.required) {
78
78
  value = undefined;
79
79
  }
80
- else if (param.required && param.schema && !(0, rmoas_types_1.isRef)(param.schema) && param.schema["default"]) {
81
- value = param.schema["default"];
80
+ else if (param.required && param.schema && !(0, rmoas_types_1.isRef)(param.schema) && param.schema.default) {
81
+ value = param.schema.default;
82
82
  }
83
83
  else if (type === 'path') {
84
84
  // If we don't have any values for the path parameter, just use the name of the parameter as the
@@ -105,17 +105,16 @@ function formatter(values, param, type, onlyIfExists) {
105
105
  // Query params should always be formatted, even if they don't have a `style` serialization
106
106
  // configured.
107
107
  if (type === 'query') {
108
- return (0, style_formatting_1["default"])(value, param);
108
+ return (0, style_formatting_1.default)(value, param);
109
109
  }
110
110
  return value;
111
111
  }
112
112
  return undefined;
113
113
  }
114
- function multipartBodyToFormatterParams(multipartBody, oasMediaTypeObject) {
115
- var schema = oasMediaTypeObject.schema;
114
+ function multipartBodyToFormatterParams(payload, oasMediaTypeObject, schema) {
116
115
  var encoding = oasMediaTypeObject.encoding;
117
- if (typeof multipartBody === 'object' && multipartBody !== null) {
118
- return Object.keys(multipartBody)
116
+ if (typeof payload === 'object' && payload !== null) {
117
+ return Object.keys(payload)
119
118
  .map(function (key) {
120
119
  // If we have an incoming parameter, but it's not in the schema ignore it.
121
120
  if (!schema.properties[key]) {
@@ -131,7 +130,7 @@ function multipartBodyToFormatterParams(multipartBody, oasMediaTypeObject) {
131
130
  required: (schema.required && typeof schema.required === 'boolean' && Boolean(schema.required)) ||
132
131
  (Array.isArray(schema.required) && schema.required.includes(key)),
133
132
  schema: schema.properties[key],
134
- "in": 'body'
133
+ in: 'body',
135
134
  };
136
135
  })
137
136
  .filter(Boolean);
@@ -140,6 +139,25 @@ function multipartBodyToFormatterParams(multipartBody, oasMediaTypeObject) {
140
139
  // empty array if we get anything else.
141
140
  return [];
142
141
  }
142
+ /**
143
+ * Because some request body schema shapes might not always be a top-level `properties`, instead
144
+ * nesting it in an `oneOf` or `anyOf` we need to extract the first usable schema that we have. If
145
+ * we don't do this then these non-conventional request body schema payloads may not be properly
146
+ * represented in the HAR that we generate.
147
+ *
148
+ */
149
+ function getSafeRequestBody(obj) {
150
+ if ('properties' in obj) {
151
+ return obj;
152
+ }
153
+ else if ('oneOf' in obj) {
154
+ return getSafeRequestBody(obj.oneOf[0]);
155
+ }
156
+ else if ('anyOf' in obj) {
157
+ return getSafeRequestBody(obj.anyOf[0]);
158
+ }
159
+ return {};
160
+ }
143
161
  var defaultFormDataTypes = Object.keys(jsonSchemaTypes).reduce(function (prev, curr) {
144
162
  var _a;
145
163
  return Object.assign(prev, (_a = {}, _a[curr] = {}, _a));
@@ -162,7 +180,7 @@ function isPrimitive(val) {
162
180
  return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean';
163
181
  }
164
182
  function stringify(json) {
165
- return JSON.stringify((0, remove_undefined_objects_1["default"])(typeof json.RAW_BODY !== 'undefined' ? json.RAW_BODY : json));
183
+ return JSON.stringify((0, remove_undefined_objects_1.default)(typeof json.RAW_BODY !== 'undefined' ? json.RAW_BODY : json));
166
184
  }
167
185
  function stringifyParameter(param) {
168
186
  if (param === null || isPrimitive(param)) {
@@ -220,7 +238,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
220
238
  if (opts === void 0) { opts = {
221
239
  // If true, the operation URL will be rewritten and prefixed with https://try.readme.io/ in
222
240
  // order to funnel requests through our CORS-friendly proxy.
223
- proxyUrl: false
241
+ proxyUrl: false,
224
242
  }; }
225
243
  var operation;
226
244
  if (!operationSchema || typeof operationSchema.getParameters !== 'function') {
@@ -241,7 +259,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
241
259
  var apiDefinition = oas.getDefinition();
242
260
  var formData = __assign(__assign(__assign({}, defaultFormDataTypes), { server: {
243
261
  selected: 0,
244
- variables: oas.defaultVariables(0)
262
+ variables: oas.defaultVariables(0),
245
263
  } }), values);
246
264
  // If the incoming `server.variables` is missing variables let's pad it out with defaults.
247
265
  formData.server.variables = __assign(__assign({}, oas.defaultVariables(formData.server.selected)), (formData.server.variables ? formData.server.variables : {}));
@@ -255,7 +273,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
255
273
  bodySize: 0,
256
274
  method: operation.method.toUpperCase(),
257
275
  url: "".concat(oas.url(formData.server.selected, formData.server.variables)).concat(operation.path).replace(/\s/g, '%20'),
258
- httpVersion: 'HTTP/1.1'
276
+ httpVersion: 'HTTP/1.1',
259
277
  };
260
278
  if (opts.proxyUrl) {
261
279
  if (extensions.getExtension(extensions.PROXY_ENABLED, oas, operation)) {
@@ -275,7 +293,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
275
293
  }
276
294
  return formatter(formData, parameter, 'path');
277
295
  });
278
- var queryStrings = parameters && parameters.filter(function (param) { return param["in"] === 'query'; });
296
+ var queryStrings = parameters && parameters.filter(function (param) { return param.in === 'query'; });
279
297
  if (queryStrings && queryStrings.length) {
280
298
  queryStrings.forEach(function (queryString) {
281
299
  var value = formatter(formData, queryString, 'query', true);
@@ -283,7 +301,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
283
301
  });
284
302
  }
285
303
  // Do we have any `cookie` parameters on the operation?
286
- var cookies = parameters && parameters.filter(function (param) { return param["in"] === 'cookie'; });
304
+ var cookies = parameters && parameters.filter(function (param) { return param.in === 'cookie'; });
287
305
  if (cookies && cookies.length) {
288
306
  cookies.forEach(function (cookie) {
289
307
  var value = formatter(formData, cookie, 'cookie', true);
@@ -304,7 +322,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
304
322
  return true;
305
323
  har.headers.push({
306
324
  name: 'accept',
307
- value: getResponseContentType(content)
325
+ value: getResponseContentType(content),
308
326
  });
309
327
  return true;
310
328
  });
@@ -312,7 +330,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
312
330
  // Do we have any `header` parameters on the operation?
313
331
  var hasContentType = false;
314
332
  var contentType = operation.getContentType();
315
- var headers = parameters && parameters.filter(function (param) { return param["in"] === 'header'; });
333
+ var headers = parameters && parameters.filter(function (param) { return param.in === 'header'; });
316
334
  if (headers && headers.length) {
317
335
  headers.forEach(function (header) {
318
336
  var value = formatter(formData, header, 'header', true);
@@ -335,7 +353,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
335
353
  }
336
354
  har.headers.push({
337
355
  name: String(header.key),
338
- value: String(header.value)
356
+ value: String(header.value),
339
357
  });
340
358
  });
341
359
  }
@@ -345,7 +363,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
345
363
  if (acceptHeader && !har.headers.find(function (hdr) { return hdr.name.toLowerCase() === 'accept'; })) {
346
364
  har.headers.push({
347
365
  name: 'accept',
348
- value: String(formData.header[acceptHeader])
366
+ value: String(formData.header[acceptHeader]),
349
367
  });
350
368
  }
351
369
  // Do we have a manually-defined `authorization` header set up in the form data?
@@ -353,7 +371,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
353
371
  if (authorizationHeader && !har.headers.find(function (hdr) { return hdr.name.toLowerCase() === 'authorization'; })) {
354
372
  har.headers.push({
355
373
  name: 'authorization',
356
- value: String(formData.header[authorizationHeader])
374
+ value: String(formData.header[authorizationHeader]),
357
375
  });
358
376
  }
359
377
  }
@@ -366,14 +384,14 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
366
384
  var requestBodySchema_1 = requestBody.schema;
367
385
  if (operation.isFormUrlEncoded()) {
368
386
  if (Object.keys(formData.formData).length) {
369
- var cleanFormData_1 = (0, remove_undefined_objects_1["default"])(JSON.parse(JSON.stringify(formData.formData)));
387
+ var cleanFormData_1 = (0, remove_undefined_objects_1.default)(JSON.parse(JSON.stringify(formData.formData)));
370
388
  if (cleanFormData_1 !== undefined) {
371
389
  har.postData.params = [];
372
390
  har.postData.mimeType = 'application/x-www-form-urlencoded';
373
391
  Object.keys(cleanFormData_1).forEach(function (name) {
374
392
  har.postData.params.push({
375
393
  name: name,
376
- value: stringifyParameter(cleanFormData_1[name])
394
+ value: stringifyParameter(cleanFormData_1[name]),
377
395
  });
378
396
  });
379
397
  }
@@ -386,10 +404,15 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
386
404
  var isJSON = operation.isJson();
387
405
  if (isMultipart || isJSON) {
388
406
  try {
389
- var cleanBody_1 = (0, remove_undefined_objects_1["default"])(JSON.parse(JSON.stringify(formData.body)));
407
+ var cleanBody_1 = (0, remove_undefined_objects_1.default)(JSON.parse(JSON.stringify(formData.body)));
390
408
  if (isMultipart) {
391
409
  har.postData.mimeType = 'multipart/form-data';
392
410
  har.postData.params = [];
411
+ // Because some request body schema shapes might not always be a top-level `properties`,
412
+ // instead nesting it in an `oneOf` or `anyOf` we need to extract the first usable
413
+ // schema that we have in order to process this multipart payload.
414
+ var requestBodyJSONSchema = operation.getParametersAsJSONSchema().find(function (js) { return js.type === 'body'; });
415
+ var safeBodySchema_1 = getSafeRequestBody((requestBodyJSONSchema === null || requestBodyJSONSchema === void 0 ? void 0 : requestBodyJSONSchema.schema) || {});
393
416
  /**
394
417
  * Discover all `{ type: string, format: binary }` properties, or arrays containing the
395
418
  * same, within the request body. If there are any, then that means that we're dealing
@@ -400,8 +423,8 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
400
423
  * @example `{ type: string, format: binary }`
401
424
  * @example `{ type: array, items: { type: string, format: binary } }`
402
425
  */
403
- var binaryTypes_1 = Object.keys(requestBodySchema_1.properties).filter(function (key) {
404
- var propData = requestBodySchema_1.properties[key];
426
+ var binaryTypes_1 = Object.keys(safeBodySchema_1.properties).filter(function (key) {
427
+ var propData = safeBodySchema_1.properties[key];
405
428
  if (propData.format === 'binary') {
406
429
  return true;
407
430
  }
@@ -415,31 +438,33 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
415
438
  return false;
416
439
  });
417
440
  if (cleanBody_1 !== undefined) {
418
- var multipartParams_1 = multipartBodyToFormatterParams(formData.body, operation.schema.requestBody.content['multipart/form-data']);
419
- Object.keys(cleanBody_1).forEach(function (name) {
420
- var param = multipartParams_1.find(function (multipartParam) { return multipartParam.name === name; });
421
- // If we're dealing with a binary type, and the value is a valid data URL we should
422
- // parse out any available filename and content type to send along with the
423
- // parameter to interpreters like `fetch-har` can make sense of it and send a usable
424
- // payload.
425
- var addtlData = {};
426
- var value = formatter(formData, param, 'body', true);
427
- if (!Array.isArray(value)) {
428
- value = [value];
429
- }
430
- value.forEach(function (val) {
431
- if (binaryTypes_1.includes(name)) {
432
- var parsed = (0, data_urls_1.parse)(val);
433
- if (parsed) {
434
- addtlData.fileName = 'name' in parsed ? parsed.name : 'unknown';
435
- if ('contentType' in parsed) {
436
- addtlData.contentType = parsed.contentType;
441
+ var multipartParams_1 = multipartBodyToFormatterParams(formData.body, operation.schema.requestBody.content['multipart/form-data'], safeBodySchema_1);
442
+ if (multipartParams_1.length) {
443
+ Object.keys(cleanBody_1).forEach(function (name) {
444
+ var param = multipartParams_1.find(function (multipartParam) { return multipartParam.name === name; });
445
+ // If we're dealing with a binary type, and the value is a valid data URL we should
446
+ // parse out any available filename and content type to send along with the
447
+ // parameter to interpreters like `fetch-har` can make sense of it and send a usable
448
+ // payload.
449
+ var addtlData = {};
450
+ var value = formatter(formData, param, 'body', true);
451
+ if (!Array.isArray(value)) {
452
+ value = [value];
453
+ }
454
+ value.forEach(function (val) {
455
+ if (binaryTypes_1.includes(name)) {
456
+ var parsed = (0, data_urls_1.parse)(val);
457
+ if (parsed) {
458
+ addtlData.fileName = 'name' in parsed ? parsed.name : 'unknown';
459
+ if ('contentType' in parsed) {
460
+ addtlData.contentType = parsed.contentType;
461
+ }
437
462
  }
438
463
  }
439
- }
440
- appendHarValue(har.postData.params, name, val, addtlData);
464
+ appendHarValue(har.postData.params, name, val, addtlData);
465
+ });
441
466
  });
442
- });
467
+ }
443
468
  }
444
469
  }
445
470
  else {
@@ -515,7 +540,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
515
540
  !hasContentType) {
516
541
  har.headers.push({
517
542
  name: 'content-type',
518
- value: contentType
543
+ value: contentType,
519
544
  });
520
545
  }
521
546
  var securityRequirements = operation.getSecurity();
@@ -523,7 +548,7 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
523
548
  // TODO pass these values through the formatter?
524
549
  securityRequirements.forEach(function (schemes) {
525
550
  Object.keys(schemes).forEach(function (security) {
526
- var securityValue = (0, configure_security_1["default"])(apiDefinition, auth, security);
551
+ var securityValue = (0, configure_security_1.default)(apiDefinition, auth, security);
527
552
  if (!securityValue) {
528
553
  return;
529
554
  }
@@ -550,10 +575,10 @@ function oasToHar(oas, operationSchema, values, auth, opts) {
550
575
  log: {
551
576
  entries: [
552
577
  {
553
- request: har
578
+ request: har,
554
579
  },
555
- ]
556
- }
580
+ ],
581
+ },
557
582
  };
558
583
  }
559
- exports["default"] = oasToHar;
584
+ exports.default = oasToHar;
@@ -1,7 +1,7 @@
1
1
  import type { OASDocument } from 'oas/dist/rmoas.types';
2
2
  export type AuthForHAR = Record<string, string | number | {
3
- user?: string;
4
3
  pass?: string;
4
+ user?: string;
5
5
  }>;
6
6
  export default function configureSecurity(apiDefinition: OASDocument, values: AuthForHAR, scheme: string): false | {
7
7
  type: "cookies" | "headers" | "queryString";
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- exports.__esModule = true;
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  var rmoas_types_1 = require("oas/dist/rmoas.types");
4
4
  function harValue(type, value) {
5
5
  if (!value.value)
@@ -39,27 +39,27 @@ function configureSecurity(apiDefinition, values, scheme) {
39
39
  }
40
40
  return harValue('headers', {
41
41
  name: 'authorization',
42
- value: "Basic ".concat(Buffer.from("".concat(user, ":").concat(pass)).toString('base64'))
42
+ value: "Basic ".concat(Buffer.from("".concat(user, ":").concat(pass)).toString('base64')),
43
43
  });
44
44
  }
45
45
  else if (security.scheme === 'bearer') {
46
46
  return harValue('headers', {
47
47
  name: 'authorization',
48
- value: "Bearer ".concat(values[scheme])
48
+ value: "Bearer ".concat(values[scheme]),
49
49
  });
50
50
  }
51
51
  }
52
52
  if (security.type === 'apiKey') {
53
- if (security["in"] === 'query') {
53
+ if (security.in === 'query') {
54
54
  return harValue('queryString', {
55
55
  name: security.name,
56
- value: String(values[scheme])
56
+ value: String(values[scheme]),
57
57
  });
58
58
  }
59
- else if (security["in"] === 'header') {
59
+ else if (security.in === 'header') {
60
60
  var header = {
61
61
  name: security.name,
62
- value: String(values[scheme])
62
+ value: String(values[scheme]),
63
63
  };
64
64
  if (security['x-bearer-format']) {
65
65
  // Uppercase: token -> Token
@@ -69,19 +69,19 @@ function configureSecurity(apiDefinition, values, scheme) {
69
69
  }
70
70
  return harValue('headers', header);
71
71
  }
72
- else if (security["in"] === 'cookie') {
72
+ else if (security.in === 'cookie') {
73
73
  return harValue('cookies', {
74
74
  name: security.name,
75
- value: String(values[scheme])
75
+ value: String(values[scheme]),
76
76
  });
77
77
  }
78
78
  }
79
79
  if (security.type === 'oauth2') {
80
80
  return harValue('headers', {
81
81
  name: 'authorization',
82
- value: "Bearer ".concat(values[scheme])
82
+ value: "Bearer ".concat(values[scheme]),
83
83
  });
84
84
  }
85
85
  return undefined;
86
86
  }
87
- exports["default"] = configureSecurity;
87
+ exports.default = configureSecurity;
@@ -13,7 +13,7 @@ var __assign = (this && this.__assign) || function () {
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- exports.__esModule = true;
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  var qs_1 = __importDefault(require("qs"));
18
18
  var style_serializer_1 = __importDefault(require("./style-serializer"));
19
19
  // Certain styles don't support empty values.
@@ -53,7 +53,7 @@ function stylizeValue(value, parameter) {
53
53
  if (shouldNotStyleEmptyValues(parameter) && (typeof finalValue === 'undefined' || finalValue === '')) {
54
54
  // Paths need return an unstyled empty string instead of undefined so it's ignored in the final
55
55
  // path string.
56
- if (parameter["in"] === 'path') {
56
+ if (parameter.in === 'path') {
57
57
  return '';
58
58
  }
59
59
  // Everything but path should return undefined when unstyled so it's ignored in the final
@@ -62,7 +62,7 @@ function stylizeValue(value, parameter) {
62
62
  }
63
63
  // Every style that adds their style to empty values should use emptystring for path parameters
64
64
  // instead of undefined to avoid the string `undefined`.
65
- if (parameter["in"] === 'path') {
65
+ if (parameter.in === 'path') {
66
66
  finalValue = removeUndefinedForPath(finalValue);
67
67
  }
68
68
  /**
@@ -74,7 +74,7 @@ function stylizeValue(value, parameter) {
74
74
  *
75
75
  * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-10}
76
76
  */
77
- if (parameter["in"] === 'header' && shouldNotStyleReservedHeader(parameter)) {
77
+ if (parameter.in === 'header' && shouldNotStyleReservedHeader(parameter)) {
78
78
  return value;
79
79
  }
80
80
  /**
@@ -86,16 +86,16 @@ function stylizeValue(value, parameter) {
86
86
  */
87
87
  var style = parameter.style;
88
88
  if (!style) {
89
- if (parameter["in"] === 'query') {
89
+ if (parameter.in === 'query') {
90
90
  style = 'form';
91
91
  }
92
- else if (parameter["in"] === 'path') {
92
+ else if (parameter.in === 'path') {
93
93
  style = 'simple';
94
94
  }
95
- else if (parameter["in"] === 'header') {
95
+ else if (parameter.in === 'header') {
96
96
  style = 'simple';
97
97
  }
98
- else if (parameter["in"] === 'cookie') {
98
+ else if (parameter.in === 'cookie') {
99
99
  style = 'form';
100
100
  }
101
101
  }
@@ -109,15 +109,15 @@ function stylizeValue(value, parameter) {
109
109
  */
110
110
  explode = true;
111
111
  }
112
- return (0, style_serializer_1["default"])(__assign({ location: parameter["in"], value: finalValue, key: parameter.name, style: style, explode: explode,
112
+ return (0, style_serializer_1.default)(__assign({ location: parameter.in, value: finalValue, key: parameter.name, style: style, explode: explode,
113
113
  /**
114
114
  * @todo this parameter is optional to stylize. It defaults to false, and can accept falsy, truthy, or "unsafe".
115
115
  * I do not know if it is correct for query to use this. See style-serializer for more info
116
116
  */
117
- escape: true }, (parameter["in"] === 'query' ? { isAllowedReserved: parameter.allowReserved || false } : {})));
117
+ escape: true }, (parameter.in === 'query' ? { isAllowedReserved: parameter.allowReserved || false } : {})));
118
118
  }
119
119
  function handleDeepObject(value, parameter) {
120
- return qs_1["default"]
120
+ return qs_1.default
121
121
  .stringify(value, {
122
122
  // eslint-disable-next-line consistent-return
123
123
  encoder: function (str, defaultEncoder, charset, type) {
@@ -135,7 +135,7 @@ function handleDeepObject(value, parameter) {
135
135
  else if (type === 'value') {
136
136
  return stylizeValue(str, parameter);
137
137
  }
138
- }
138
+ },
139
139
  })
140
140
  .split('&')
141
141
  .map(function (item) {
@@ -143,7 +143,7 @@ function handleDeepObject(value, parameter) {
143
143
  return {
144
144
  label: split[0],
145
145
  // `qs` will coerce null values into being `undefined` string but we want to preserve them.
146
- value: split[1] === 'undefined' ? null : split[1]
146
+ value: split[1] === 'undefined' ? null : split[1],
147
147
  };
148
148
  });
149
149
  }
@@ -200,8 +200,8 @@ function handleExplode(value, parameter) {
200
200
  function shouldExplode(parameter) {
201
201
  return ((parameter.explode || (parameter.explode !== false && parameter.style === 'form')) &&
202
202
  // header and path doesn't explode into separate parameters like query and cookie do
203
- parameter["in"] !== 'header' &&
204
- parameter["in"] !== 'path');
203
+ parameter.in !== 'header' &&
204
+ parameter.in !== 'path');
205
205
  }
206
206
  function formatStyle(value, parameter) {
207
207
  // Deep object style only works on objects and arrays, and only works with explode=true.
@@ -222,4 +222,4 @@ function formatStyle(value, parameter) {
222
222
  }
223
223
  return stylizeValue(value, parameter);
224
224
  }
225
- exports["default"] = formatStyle;
225
+ exports.default = formatStyle;
@@ -1,15 +1,15 @@
1
1
  export interface StylizerConfig {
2
- location: 'body' | 'query';
3
- key: string;
4
- value: any;
5
- style: 'deepObject' | 'form' | 'label' | 'matrix' | 'pipeDelimited' | 'simple' | 'spaceDelimited';
6
- explode: boolean;
7
2
  escape: boolean | 'unsafe';
3
+ explode: boolean;
8
4
  isAllowedReserved?: boolean;
5
+ key: string;
6
+ location: 'body' | 'query';
7
+ style: 'deepObject' | 'form' | 'label' | 'matrix' | 'pipeDelimited' | 'simple' | 'spaceDelimited';
8
+ value: any;
9
9
  }
10
10
  export default function stylize(config: StylizerConfig): any;
11
11
  export declare function encodeDisallowedCharacters(str: string, { escape, returnIfEncoded, isAllowedReserved, }: {
12
12
  escape?: boolean | 'unsafe';
13
- returnIfEncoded?: boolean;
14
13
  isAllowedReserved?: boolean;
14
+ returnIfEncoded?: boolean;
15
15
  }, parse: boolean): any;
@@ -24,7 +24,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
24
24
  }
25
25
  return to.concat(ar || Array.prototype.slice.call(from));
26
26
  };
27
- exports.__esModule = true;
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
28
  exports.encodeDisallowedCharacters = void 0;
29
29
  /**
30
30
  * This file has been extracted and modified from `swagger-client`.
@@ -58,7 +58,7 @@ function stylize(config) {
58
58
  }
59
59
  return encodePrimitive(config);
60
60
  }
61
- exports["default"] = stylize;
61
+ exports.default = stylize;
62
62
  function encodeDisallowedCharacters(str,
63
63
  // eslint-disable-next-line @typescript-eslint/default-param-last
64
64
  _a, parse) {
@@ -111,7 +111,7 @@ function encodeArray(_a) {
111
111
  return module.exports.encodeDisallowedCharacters(str, {
112
112
  escape: escape,
113
113
  returnIfEncoded: location === 'query',
114
- isAllowedReserved: isAllowedReserved
114
+ isAllowedReserved: isAllowedReserved,
115
115
  });
116
116
  };
117
117
  switch (style) {
@@ -177,7 +177,7 @@ function encodeObject(_a) {
177
177
  return module.exports.encodeDisallowedCharacters(str, {
178
178
  escape: escape,
179
179
  returnIfEncoded: location === 'query',
180
- isAllowedReserved: isAllowedReserved
180
+ isAllowedReserved: isAllowedReserved,
181
181
  });
182
182
  };
183
183
  var valueKeys = Object.keys(value);
@@ -286,7 +286,7 @@ function encodePrimitive(_a) {
286
286
  return module.exports.encodeDisallowedCharacters(str, {
287
287
  escape: escape,
288
288
  returnIfEncoded: location === 'query' || location === 'body',
289
- isAllowedReserved: isAllowedReserved
289
+ isAllowedReserved: isAllowedReserved,
290
290
  });
291
291
  };
292
292
  switch (style) {
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": "20.1.0",
4
+ "version": "21.0.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "author": "Jon Ursenbach <jon@ursenba.ch>",
@@ -11,7 +11,7 @@
11
11
  "url": "git://github.com/readmeio/oas-to-har.git"
12
12
  },
13
13
  "engines": {
14
- "node": ">=14"
14
+ "node": ">=16"
15
15
  },
16
16
  "scripts": {
17
17
  "build": "tsc",
@@ -22,41 +22,32 @@
22
22
  "pretest": "npm run lint",
23
23
  "prettier": "prettier --list-different --write \"./**/**.{js,ts}\"",
24
24
  "release": "npx conventional-changelog-cli -i CHANGELOG.md -s && git add CHANGELOG.md",
25
- "test": "nyc mocha \"test/**/*.test.ts\"",
26
- "test:browser": "karma start --single-run",
27
- "test:browser:chrome": "karma start --browsers=Chrome --single-run=false",
28
- "test:browser:debug": "karma start --single-run=false"
25
+ "test": "jest --coverage"
29
26
  },
30
27
  "dependencies": {
31
28
  "@readme/data-urls": "^1.0.1",
32
29
  "@readme/oas-extensions": "^17.0.1",
33
- "oas": "^20.4.0",
34
- "qs": "^6.10.5",
35
- "remove-undefined-objects": "^2.0.2"
30
+ "oas": "^20.8.4",
31
+ "qs": "^6.11.2",
32
+ "remove-undefined-objects": "^3.0.0"
36
33
  },
37
34
  "devDependencies": {
38
- "@commitlint/cli": "^17.4.2",
39
- "@commitlint/config-conventional": "^17.4.2",
40
- "@jsdevtools/host-environment": "^2.1.2",
41
- "@jsdevtools/karma-config": "^3.1.7",
42
- "@readme/eslint-config": "^10.5.0",
43
- "@readme/oas-examples": "^5.7.1",
44
- "@types/chai": "^4.3.1",
45
- "@types/har-format": "^1.2.10",
46
- "@types/mocha": "^10.0.0",
35
+ "@commitlint/cli": "^17.6.6",
36
+ "@commitlint/config-conventional": "^17.6.6",
37
+ "@readme/eslint-config": "^10.6.0",
38
+ "@readme/oas-examples": "^5.11.1",
39
+ "@types/har-format": "^1.2.11",
40
+ "@types/jest": "^29.5.2",
47
41
  "@types/qs": "^6.9.7",
48
- "chai": "^4.3.6",
49
- "eslint": "^8.33.0",
42
+ "eslint": "^8.43.0",
50
43
  "har-validator": "^5.1.5",
51
44
  "husky": "^8.0.3",
52
- "mocha": "^10.2.0",
53
- "nyc": "^15.1.0",
54
- "prettier": "^2.8.3",
55
- "ts-loader": "^8.4.0",
56
- "ts-node": "^10.9.1",
57
- "type-fest": "^3.5.4",
58
- "typescript": "^4.9.5",
59
- "webpack": "^4.46.0"
45
+ "jest": "^29.5.0",
46
+ "jest-expect-har": "^6.0.0",
47
+ "prettier": "^2.8.8",
48
+ "ts-jest": "^29.1.0",
49
+ "type-fest": "^3.12.0",
50
+ "typescript": "^5.1.3"
60
51
  },
61
52
  "browserslist": [
62
53
  "last 2 versions"
package/src/index.ts CHANGED
@@ -100,12 +100,11 @@ function formatter(
100
100
  return undefined;
101
101
  }
102
102
 
103
- function multipartBodyToFormatterParams(multipartBody: unknown, oasMediaTypeObject: MediaTypeObject) {
104
- const schema = oasMediaTypeObject.schema as SchemaObject;
103
+ function multipartBodyToFormatterParams(payload: unknown, oasMediaTypeObject: MediaTypeObject, schema: SchemaObject) {
105
104
  const encoding = oasMediaTypeObject.encoding;
106
105
 
107
- if (typeof multipartBody === 'object' && multipartBody !== null) {
108
- return Object.keys(multipartBody)
106
+ if (typeof payload === 'object' && payload !== null) {
107
+ return Object.keys(payload)
109
108
  .map(key => {
110
109
  // If we have an incoming parameter, but it's not in the schema ignore it.
111
110
  if (!schema.properties[key]) {
@@ -135,6 +134,25 @@ function multipartBodyToFormatterParams(multipartBody: unknown, oasMediaTypeObje
135
134
  return [];
136
135
  }
137
136
 
137
+ /**
138
+ * Because some request body schema shapes might not always be a top-level `properties`, instead
139
+ * nesting it in an `oneOf` or `anyOf` we need to extract the first usable schema that we have. If
140
+ * we don't do this then these non-conventional request body schema payloads may not be properly
141
+ * represented in the HAR that we generate.
142
+ *
143
+ */
144
+ function getSafeRequestBody(obj: any) {
145
+ if ('properties' in obj) {
146
+ return obj;
147
+ } else if ('oneOf' in obj) {
148
+ return getSafeRequestBody(obj.oneOf[0]);
149
+ } else if ('anyOf' in obj) {
150
+ return getSafeRequestBody(obj.anyOf[0]);
151
+ }
152
+
153
+ return {};
154
+ }
155
+
138
156
  const defaultFormDataTypes = Object.keys(jsonSchemaTypes).reduce((prev, curr) => {
139
157
  return Object.assign(prev, { [curr]: {} });
140
158
  }, {});
@@ -180,8 +198,8 @@ function appendHarValue(
180
198
  name: string,
181
199
  value: any,
182
200
  addtlData: {
183
- fileName?: string;
184
201
  contentType?: string;
202
+ fileName?: string;
185
203
  } = {}
186
204
  ) {
187
205
  if (typeof value === 'undefined') return;
@@ -452,6 +470,12 @@ export default function oasToHar(
452
470
  har.postData.mimeType = 'multipart/form-data';
453
471
  har.postData.params = [];
454
472
 
473
+ // Because some request body schema shapes might not always be a top-level `properties`,
474
+ // instead nesting it in an `oneOf` or `anyOf` we need to extract the first usable
475
+ // schema that we have in order to process this multipart payload.
476
+ const requestBodyJSONSchema = operation.getParametersAsJSONSchema().find(js => js.type === 'body');
477
+ const safeBodySchema = getSafeRequestBody(requestBodyJSONSchema?.schema || {});
478
+
455
479
  /**
456
480
  * Discover all `{ type: string, format: binary }` properties, or arrays containing the
457
481
  * same, within the request body. If there are any, then that means that we're dealing
@@ -462,8 +486,8 @@ export default function oasToHar(
462
486
  * @example `{ type: string, format: binary }`
463
487
  * @example `{ type: array, items: { type: string, format: binary } }`
464
488
  */
465
- const binaryTypes = Object.keys(requestBodySchema.properties).filter(key => {
466
- const propData = requestBodySchema.properties[key] as JSONSchema;
489
+ const binaryTypes = Object.keys(safeBodySchema.properties).filter(key => {
490
+ const propData = safeBodySchema.properties[key] as JSONSchema;
467
491
  if (propData.format === 'binary') {
468
492
  return true;
469
493
  } else if (
@@ -482,37 +506,40 @@ export default function oasToHar(
482
506
  if (cleanBody !== undefined) {
483
507
  const multipartParams = multipartBodyToFormatterParams(
484
508
  formData.body,
485
- (operation.schema.requestBody as RequestBodyObject).content['multipart/form-data']
509
+ (operation.schema.requestBody as RequestBodyObject).content['multipart/form-data'],
510
+ safeBodySchema
486
511
  );
487
512
 
488
- Object.keys(cleanBody).forEach(name => {
489
- const param = multipartParams.find(multipartParam => multipartParam.name === name);
513
+ if (multipartParams.length) {
514
+ Object.keys(cleanBody).forEach(name => {
515
+ const param = multipartParams.find(multipartParam => multipartParam.name === name);
490
516
 
491
- // If we're dealing with a binary type, and the value is a valid data URL we should
492
- // parse out any available filename and content type to send along with the
493
- // parameter to interpreters like `fetch-har` can make sense of it and send a usable
494
- // payload.
495
- const addtlData: { fileName?: string; contentType?: string } = {};
517
+ // If we're dealing with a binary type, and the value is a valid data URL we should
518
+ // parse out any available filename and content type to send along with the
519
+ // parameter to interpreters like `fetch-har` can make sense of it and send a usable
520
+ // payload.
521
+ const addtlData: { contentType?: string; fileName?: string } = {};
496
522
 
497
- let value = formatter(formData, param, 'body', true);
498
- if (!Array.isArray(value)) {
499
- value = [value];
500
- }
523
+ let value = formatter(formData, param, 'body', true);
524
+ if (!Array.isArray(value)) {
525
+ value = [value];
526
+ }
501
527
 
502
- value.forEach((val: string) => {
503
- if (binaryTypes.includes(name)) {
504
- const parsed = parseDataUrl(val);
505
- if (parsed) {
506
- addtlData.fileName = 'name' in parsed ? parsed.name : 'unknown';
507
- if ('contentType' in parsed) {
508
- addtlData.contentType = parsed.contentType;
528
+ value.forEach((val: string) => {
529
+ if (binaryTypes.includes(name)) {
530
+ const parsed = parseDataUrl(val);
531
+ if (parsed) {
532
+ addtlData.fileName = 'name' in parsed ? parsed.name : 'unknown';
533
+ if ('contentType' in parsed) {
534
+ addtlData.contentType = parsed.contentType;
535
+ }
509
536
  }
510
537
  }
511
- }
512
538
 
513
- appendHarValue(har.postData.params, name, val, addtlData);
539
+ appendHarValue(har.postData.params, name, val, addtlData);
540
+ });
514
541
  });
515
- });
542
+ }
516
543
  }
517
544
  } else {
518
545
  har.postData.mimeType = contentType;
@@ -2,7 +2,7 @@ import type { OASDocument, SecuritySchemeObject } from 'oas/dist/rmoas.types';
2
2
 
3
3
  import { isRef } from 'oas/dist/rmoas.types';
4
4
 
5
- export type AuthForHAR = Record<string, string | number | { user?: string; pass?: string }>;
5
+ export type AuthForHAR = Record<string, string | number | { pass?: string; user?: string }>;
6
6
 
7
7
  function harValue(type: 'cookies' | 'headers' | 'queryString', value: { name: string; value: string }) {
8
8
  if (!value.value) return undefined;
@@ -29,13 +29,13 @@ function isObject(value: unknown) {
29
29
  }
30
30
 
31
31
  export interface StylizerConfig {
32
- location: 'body' | 'query';
33
- key: string;
34
- value: any;
35
- style: 'deepObject' | 'form' | 'label' | 'matrix' | 'pipeDelimited' | 'simple' | 'spaceDelimited';
36
- explode: boolean;
37
32
  escape: boolean | 'unsafe';
33
+ explode: boolean;
38
34
  isAllowedReserved?: boolean;
35
+ key: string;
36
+ location: 'body' | 'query';
37
+ style: 'deepObject' | 'form' | 'label' | 'matrix' | 'pipeDelimited' | 'simple' | 'spaceDelimited';
38
+ value: any;
39
39
  }
40
40
 
41
41
  export default function stylize(config: StylizerConfig) {
@@ -61,8 +61,8 @@ export function encodeDisallowedCharacters(
61
61
  isAllowedReserved,
62
62
  }: {
63
63
  escape?: boolean | 'unsafe';
64
- returnIfEncoded?: boolean;
65
64
  isAllowedReserved?: boolean;
65
+ returnIfEncoded?: boolean;
66
66
  } = {},
67
67
  parse: boolean
68
68
  ) {
package/karma.conf.js DELETED
@@ -1,27 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- const { host } = require('@jsdevtools/host-environment');
3
- const { karmaConfig } = require('@jsdevtools/karma-config');
4
-
5
- module.exports = karmaConfig({
6
- sourceDir: '.',
7
- fixtures: ['test/__datasets__/*.json'],
8
- browsers: {
9
- chrome: true,
10
- firefox: true,
11
- safari: host.os.mac,
12
- edge: false,
13
- ie: false,
14
- },
15
- tests: ['test/*.ts'],
16
- config: {
17
- webpack: {
18
- resolve: {
19
- extensions: ['.js', '.ts'],
20
- },
21
- mode: 'production',
22
- module: {
23
- rules: [{ test: /\.ts$/, use: 'ts-loader' }],
24
- },
25
- },
26
- },
27
- });