@pactflow/openapi-pact-comparator 2.1.0 → 2.3.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.
package/dist/index.mjs CHANGED
@@ -3912,8 +3912,23 @@ const splitPath = (path) => {
3912
3912
  }
3913
3913
  return path;
3914
3914
  };
3915
- const dereferenceOas = (schema, oas) => (schema.$ref ? get$1(oas, splitPath(schema.$ref)) : schema);
3916
- const dereferenceDoc = (schema, doc) => schema.$ref ? get$1(doc, splitPath(schema.$ref)) : schema;
3915
+ const followRefChain = (schema, doc, seen = new Set()) => {
3916
+ if (!schema.$ref)
3917
+ return { value: schema, ref: undefined };
3918
+ if (seen.has(schema.$ref))
3919
+ return { value: undefined, ref: undefined };
3920
+ const resolved = get$1(doc, splitPath(schema.$ref));
3921
+ if (resolved !== null && typeof resolved === "object" && "$ref" in resolved) {
3922
+ const next = followRefChain(resolved, doc, new Set([...seen, schema.$ref]));
3923
+ return next.value !== undefined
3924
+ ? { value: next.value, ref: next.ref ?? schema.$ref }
3925
+ : next;
3926
+ }
3927
+ return { value: resolved, ref: schema.$ref };
3928
+ };
3929
+ const dereferenceOas = (schema, oas) => (followRefChain(schema, oas).value ?? schema);
3930
+ const dereferenceDoc = (schema, doc) => followRefChain(schema, doc).value;
3931
+ const lastRefInChain = (schema, doc) => followRefChain(schema, doc).ref;
3917
3932
  const traverse = (schema, visitor) => {
3918
3933
  if (typeof schema === "boolean" || schema === undefined) {
3919
3934
  return;
@@ -3997,7 +4012,8 @@ function* iterateMessageList(messages, inlinePathBase, cache, doc) {
3997
4012
  const message = dereferenceDoc(ref, doc);
3998
4013
  if (!message)
3999
4014
  continue;
4000
- const path = "[root]." + ref.$ref.replace(/^#\//, "").replace(/\//g, ".");
4015
+ const finalRef = lastRefInChain(ref, doc) ?? ref.$ref;
4016
+ const path = "[root]." + finalRef.replace(/^#\//, "").replace(/\//g, ".");
4001
4017
  const result = { message, path };
4002
4018
  cache.set(ref.$ref, result);
4003
4019
  yield result;
@@ -4250,14 +4266,199 @@ function* compareMessageHeaders(ajv, message, content, interactionContext, metad
4250
4266
  }
4251
4267
  }
4252
4268
 
4253
- const canValidate$2 = (contentType) => {
4269
+ const PARAMETER_SEPARATOR = ";";
4270
+ const WILDCARD = "*";
4271
+ const TYPE_SUBTYPE_SEPARATOR = "/";
4272
+ const EXTENSION_SEPARATOR = "+";
4273
+ const quality = (a) => {
4274
+ const match = /.*;q=([01].?\d*)/.exec(a);
4275
+ return match ? parseFloat(match[1]) : 1.0;
4276
+ };
4277
+ const byQuality = (a, b) => {
4278
+ return quality(b) - quality(a);
4279
+ };
4280
+ const ignoreCasing = (t) => t.toLowerCase();
4281
+ const ignoreWhitespace = (t) => t.replace(/\s+/g, "");
4282
+ const ignoreParameters = (t) => t.split(PARAMETER_SEPARATOR)[0];
4283
+ const removeQuality = (t) => t.replace(/;q=[01].?\d*/, "");
4284
+ const type$a = (t) => t.split(TYPE_SUBTYPE_SEPARATOR)[0];
4285
+ const subtype = (t) => {
4286
+ const [_maintype, subtype] = t.split(TYPE_SUBTYPE_SEPARATOR);
4287
+ return subtype?.split(EXTENSION_SEPARATOR).pop();
4288
+ };
4289
+ function findMatchingType(requestType, responseTypes) {
4290
+ // exact match
4291
+ let accept = requestType
4292
+ .split(",")
4293
+ .map(ignoreWhitespace)
4294
+ .map(ignoreCasing)
4295
+ .sort(byQuality)
4296
+ .map(removeQuality);
4297
+ let available = responseTypes.map(ignoreWhitespace).map(ignoreCasing);
4298
+ for (const a of accept) {
4299
+ const matchExactly = (t) => t === a;
4300
+ const index = available.findIndex(matchExactly);
4301
+ if (index >= 0) {
4302
+ return responseTypes[index];
4303
+ }
4304
+ }
4305
+ // ignore additional parameters
4306
+ accept = accept.map(ignoreParameters);
4307
+ available = available.map(ignoreParameters);
4308
+ for (const a of accept) {
4309
+ const matchExactly = (t) => t === a;
4310
+ const index = available.findIndex(matchExactly);
4311
+ if (index >= 0) {
4312
+ return responseTypes[index];
4313
+ }
4314
+ }
4315
+ // ignore vendor extensions
4316
+ for (const a of accept) {
4317
+ const matchTypesAndSubtypes = (t) => type$a(t) === type$a(a) && subtype(t) === subtype(a);
4318
+ const index = available.findIndex(matchTypesAndSubtypes);
4319
+ if (index >= 0) {
4320
+ return responseTypes[index];
4321
+ }
4322
+ }
4323
+ // wildcards in responseTypes
4324
+ for (const a of accept) {
4325
+ const matchSubtype = (t) => subtype(t) === WILDCARD && type$a(t) === type$a(a);
4326
+ const index = available.findIndex(matchSubtype);
4327
+ if (index >= 0) {
4328
+ return responseTypes[index];
4329
+ }
4330
+ }
4331
+ if (available.includes(`${WILDCARD}/${WILDCARD}`)) {
4332
+ return `${WILDCARD}/${WILDCARD}`;
4333
+ }
4334
+ // wildcards in requestTypes
4335
+ for (const a of accept) {
4336
+ const matchSubtype = (t) => subtype(a) === WILDCARD && type$a(t) === type$a(a);
4337
+ const index = available.findIndex(matchSubtype);
4338
+ if (index >= 0) {
4339
+ return responseTypes[index];
4340
+ }
4341
+ }
4342
+ if (accept.includes(`${WILDCARD}/${WILDCARD}`)) {
4343
+ return responseTypes[0];
4344
+ }
4345
+ return undefined;
4346
+ }
4347
+ const getByContentType = (object = {}, contentType) => {
4348
+ const key = findMatchingType(contentType, Object.keys(object));
4349
+ return object[key];
4350
+ };
4351
+ const standardHttpRequestHeaders = [
4352
+ "accept",
4353
+ "accept-charset",
4354
+ "accept-datetime",
4355
+ "accept-encoding",
4356
+ "accept-language",
4357
+ "authorization",
4358
+ "cache-control",
4359
+ "connection",
4360
+ "content-length",
4361
+ "content-md5",
4362
+ "content-type",
4363
+ "cookie",
4364
+ "date",
4365
+ "expect",
4366
+ "forwarded",
4367
+ "from",
4368
+ "host",
4369
+ "if-match",
4370
+ "if-modified-since",
4371
+ "if-none-match",
4372
+ "if-range",
4373
+ "if-unmodified-since",
4374
+ "max-forwards",
4375
+ "origin",
4376
+ "pragma",
4377
+ "proxy-authorization",
4378
+ "range",
4379
+ "referer",
4380
+ "te",
4381
+ "upgrade",
4382
+ "user-agent",
4383
+ "via",
4384
+ "warning",
4385
+ ];
4386
+ const standardHttpResponseHeaders = [
4387
+ "access-control-allow-origin",
4388
+ "accept-patch",
4389
+ "accept-ranges",
4390
+ "age",
4391
+ "allow",
4392
+ "alt-svc",
4393
+ "cache-control",
4394
+ "connection",
4395
+ "content-disposition",
4396
+ "content-encoding",
4397
+ "content-language",
4398
+ "content-length",
4399
+ "content-location",
4400
+ "content-md5",
4401
+ "content-range",
4402
+ "date",
4403
+ "etag",
4404
+ "expires",
4405
+ "last-modified",
4406
+ "link",
4407
+ "location",
4408
+ "p3p",
4409
+ "pragma",
4410
+ "proxy-authenticate",
4411
+ "public-key-pins",
4412
+ "refresh",
4413
+ "retry-after",
4414
+ "server",
4415
+ "set-cookie",
4416
+ "status",
4417
+ "strict-transport-security",
4418
+ "trailer",
4419
+ "transfer-encoding",
4420
+ "tsv",
4421
+ "upgrade",
4422
+ "vary",
4423
+ "via",
4424
+ "warning",
4425
+ "www-authenticate",
4426
+ "x-frame-options",
4427
+ ];
4428
+
4429
+ const VALIDATABLE_CONTENT_TYPES = [
4430
+ "application/json",
4431
+ "application/x-www-form-urlencoded",
4432
+ "multipart/form-data",
4433
+ ];
4434
+ const bodyValidationStatus = (contentType, body, validatableTypes = [...VALIDATABLE_CONTENT_TYPES]) => {
4254
4435
  if (typeof contentType !== "string")
4255
- return false;
4256
- const normalized = contentType.split(";", 1)[0].trim().toLowerCase();
4257
- return normalized === "application/json" || normalized.endsWith("+json");
4436
+ return "skip";
4437
+ if (findMatchingType(contentType, validatableTypes))
4438
+ return "validate";
4439
+ return body !== undefined ? "warn" : "skip";
4258
4440
  };
4441
+
4259
4442
  function* compareMessagePayload(ajv, message, content, interactionContext, contentLocation, messagePath, direction) {
4260
- if (!canValidate$2(content.contentType))
4443
+ const status = bodyValidationStatus(content.contentType, content.payload);
4444
+ if (status === "warn") {
4445
+ yield {
4446
+ code: "message.payload.unvalidatable",
4447
+ message: `Body with content type '${content.contentType}' is not supported by the spec comparator`,
4448
+ mockDetails: {
4449
+ ...baseMockDetails(interactionContext),
4450
+ location: contentLocation,
4451
+ value: content.payload,
4452
+ },
4453
+ specDetails: {
4454
+ location: messagePath,
4455
+ value: undefined,
4456
+ },
4457
+ type: "warning",
4458
+ };
4459
+ return;
4460
+ }
4461
+ if (status === "skip")
4261
4462
  return;
4262
4463
  if (!message.payload) {
4263
4464
  if (content.payload !== undefined) {
@@ -4325,12 +4526,13 @@ function checkAsyncapiPreamble(asyncapi, interaction, index, interactionKind) {
4325
4526
  },
4326
4527
  };
4327
4528
  }
4529
+ const interactionKindTitleCase = `${interactionKind.charAt(0).toUpperCase()}${interactionKind.slice(1)}`;
4328
4530
  if (!interaction.asyncapiReferences) {
4329
4531
  return {
4330
4532
  ok: false,
4331
4533
  result: {
4332
4534
  code: "message.references.missing",
4333
- message: `${interactionKind.charAt(0).toUpperCase()}${interactionKind.slice(1)} interaction has no AsyncAPI references in comments.references.AsyncAPI`,
4535
+ message: `${interactionKindTitleCase} interaction has no AsyncAPI references in comments.references.AsyncAPI`,
4334
4536
  mockDetails: {
4335
4537
  ...baseMockDetails(interaction),
4336
4538
  location: `[root].interactions[${index}].comments.references.AsyncAPI`,
@@ -4342,6 +4544,22 @@ function checkAsyncapiPreamble(asyncapi, interaction, index, interactionKind) {
4342
4544
  };
4343
4545
  }
4344
4546
  const { operationId } = interaction.asyncapiReferences;
4547
+ if (!operationId) {
4548
+ return {
4549
+ ok: false,
4550
+ result: {
4551
+ code: "message.references.missing",
4552
+ message: `${interactionKindTitleCase} interaction has a malformed AsyncAPI reference in comments.references.AsyncAPI: expected an "operationId" property`,
4553
+ mockDetails: {
4554
+ ...baseMockDetails(interaction),
4555
+ location: `[root].interactions[${index}].comments.references.AsyncAPI`,
4556
+ value: interaction.asyncapiReferences,
4557
+ },
4558
+ specDetails: { location: "[root]", value: undefined },
4559
+ type: "error",
4560
+ },
4561
+ };
4562
+ }
4345
4563
  if (!asyncapi.operations?.[operationId]) {
4346
4564
  return {
4347
4565
  ok: false,
@@ -4720,16 +4938,16 @@ function requireMultipart () {
4720
4938
  var multipartExports = requireMultipart();
4721
4939
  var multipart = /*@__PURE__*/getDefaultExportFromCjs(multipartExports);
4722
4940
 
4723
- var type$a;
4941
+ var type$9;
4724
4942
  var hasRequiredType;
4725
4943
 
4726
4944
  function requireType () {
4727
- if (hasRequiredType) return type$a;
4945
+ if (hasRequiredType) return type$9;
4728
4946
  hasRequiredType = 1;
4729
4947
 
4730
4948
  /** @type {import('./type')} */
4731
- type$a = TypeError;
4732
- return type$a;
4949
+ type$9 = TypeError;
4950
+ return type$9;
4733
4951
  }
4734
4952
 
4735
4953
  var util_inspect;
@@ -6638,7 +6856,10 @@ function requireSideChannel () {
6638
6856
  var channel = {
6639
6857
  assert: function (key) {
6640
6858
  if (!channel.has(key)) {
6641
- throw new $TypeError('Side channel does not contain ' + inspect(key));
6859
+ var keyDesc = key && Object(key) === key
6860
+ ? 'the given object key'
6861
+ : inspect(key);
6862
+ throw new $TypeError('Side channel does not contain ' + keyDesc);
6642
6863
  }
6643
6864
  },
6644
6865
  'delete': function (key) {
@@ -6658,7 +6879,7 @@ function requireSideChannel () {
6658
6879
  $channelData.set(key, value);
6659
6880
  }
6660
6881
  };
6661
- // @ts-expect-error TODO: figure out why this is erroring
6882
+
6662
6883
  return channel;
6663
6884
  };
6664
6885
  return sideChannel;
@@ -6704,6 +6925,7 @@ function requireUtils$1 () {
6704
6925
 
6705
6926
  var formats = /*@__PURE__*/ requireFormats$1();
6706
6927
  var getSideChannel = requireSideChannel();
6928
+ var defineProperty = /*@__PURE__*/ requireEsDefineProperty();
6707
6929
 
6708
6930
  var has = Object.prototype.hasOwnProperty;
6709
6931
  var isArray = Array.isArray;
@@ -6768,6 +6990,19 @@ function requireUtils$1 () {
6768
6990
  return obj;
6769
6991
  };
6770
6992
 
6993
+ var setProperty = function setProperty(obj, key, value) {
6994
+ if (key === '__proto__' && defineProperty) {
6995
+ defineProperty(obj, key, {
6996
+ configurable: true,
6997
+ enumerable: true,
6998
+ value: value,
6999
+ writable: true
7000
+ });
7001
+ } else {
7002
+ obj[key] = value;
7003
+ }
7004
+ };
7005
+
6771
7006
  var merge = function merge(target, source, options) {
6772
7007
  /* eslint no-param-reassign: 0 */
6773
7008
  if (!source) {
@@ -6777,7 +7012,10 @@ function requireUtils$1 () {
6777
7012
  if (typeof source !== 'object' && typeof source !== 'function') {
6778
7013
  if (isArray(target)) {
6779
7014
  var nextIndex = target.length;
6780
- if (options && typeof options.arrayLimit === 'number' && nextIndex > options.arrayLimit) {
7015
+ if (options && typeof options.arrayLimit === 'number' && nextIndex >= options.arrayLimit) {
7016
+ if (options.throwOnLimitExceeded) {
7017
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7018
+ }
6781
7019
  return markOverflow(arrayToObject(target.concat(source), options), nextIndex);
6782
7020
  }
6783
7021
  target[nextIndex] = source;
@@ -6817,6 +7055,9 @@ function requireUtils$1 () {
6817
7055
  }
6818
7056
  var combined = [target].concat(source);
6819
7057
  if (options && typeof options.arrayLimit === 'number' && combined.length > options.arrayLimit) {
7058
+ if (options.throwOnLimitExceeded) {
7059
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7060
+ }
6820
7061
  return markOverflow(arrayToObject(combined, options), combined.length - 1);
6821
7062
  }
6822
7063
  return combined;
@@ -6840,6 +7081,12 @@ function requireUtils$1 () {
6840
7081
  target[i] = item;
6841
7082
  }
6842
7083
  });
7084
+ if (options && typeof options.arrayLimit === 'number' && target.length > options.arrayLimit) {
7085
+ if (options.throwOnLimitExceeded) {
7086
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7087
+ }
7088
+ return markOverflow(arrayToObject(target, options), target.length - 1);
7089
+ }
6843
7090
  return target;
6844
7091
  }
6845
7092
 
@@ -6847,9 +7094,9 @@ function requireUtils$1 () {
6847
7094
  var value = source[key];
6848
7095
 
6849
7096
  if (has.call(acc, key)) {
6850
- acc[key] = merge(acc[key], value, options);
7097
+ setProperty(acc, key, merge(acc[key], value, options));
6851
7098
  } else {
6852
- acc[key] = value;
7099
+ setProperty(acc, key, value);
6853
7100
  }
6854
7101
 
6855
7102
  if (isOverflow(source) && !isOverflow(acc)) {
@@ -6868,7 +7115,7 @@ function requireUtils$1 () {
6868
7115
 
6869
7116
  var assign = function assignSingleSource(target, source) {
6870
7117
  return Object.keys(source).reduce(function (acc, key) {
6871
- acc[key] = source[key];
7118
+ setProperty(acc, key, source[key]);
6872
7119
  return acc;
6873
7120
  }, target);
6874
7121
  };
@@ -6914,6 +7161,13 @@ function requireUtils$1 () {
6914
7161
  var out = '';
6915
7162
  for (var j = 0; j < string.length; j += limit) {
6916
7163
  var segment = string.length >= limit ? string.slice(j, j + limit) : string;
7164
+ if (j + limit < string.length) {
7165
+ var last = segment.charCodeAt(segment.length - 1);
7166
+ if (last >= 0xD800 && last <= 0xDBFF) {
7167
+ segment = segment.slice(0, -1);
7168
+ j -= 1;
7169
+ }
7170
+ }
6917
7171
  var arr = [];
6918
7172
 
6919
7173
  for (var i = 0; i < segment.length; ++i) {
@@ -6967,7 +7221,7 @@ function requireUtils$1 () {
6967
7221
 
6968
7222
  var compact = function compact(value) {
6969
7223
  var queue = [{ obj: { o: value }, prop: 'o' }];
6970
- var refs = [];
7224
+ var refs = getSideChannel();
6971
7225
 
6972
7226
  for (var i = 0; i < queue.length; ++i) {
6973
7227
  var item = queue[i];
@@ -6977,9 +7231,9 @@ function requireUtils$1 () {
6977
7231
  for (var j = 0; j < keys.length; ++j) {
6978
7232
  var key = keys[j];
6979
7233
  var val = obj[key];
6980
- if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
7234
+ if (typeof val === 'object' && val !== null && !refs.has(val)) {
6981
7235
  queue[queue.length] = { obj: obj, prop: key };
6982
- refs[refs.length] = val;
7236
+ refs.set(val, true);
6983
7237
  }
6984
7238
  }
6985
7239
  }
@@ -7001,9 +7255,12 @@ function requireUtils$1 () {
7001
7255
  return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
7002
7256
  };
7003
7257
 
7004
- var combine = function combine(a, b, arrayLimit, plainObjects) {
7258
+ var combine = function combine(a, b, arrayLimit, plainObjects, throwOnLimitExceeded) {
7005
7259
  // If 'a' is already an overflow object, add to it
7006
7260
  if (isOverflow(a)) {
7261
+ if (throwOnLimitExceeded) {
7262
+ throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7263
+ }
7007
7264
  var newIndex = getMaxIndex(a) + 1;
7008
7265
  a[newIndex] = b;
7009
7266
  setMaxIndex(a, newIndex);
@@ -7012,6 +7269,9 @@ function requireUtils$1 () {
7012
7269
 
7013
7270
  var result = [].concat(a, b);
7014
7271
  if (result.length > arrayLimit) {
7272
+ if (throwOnLimitExceeded) {
7273
+ throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7274
+ }
7015
7275
  return markOverflow(arrayToObject(result, { plainObjects: plainObjects }), result.length - 1);
7016
7276
  }
7017
7277
  return result;
@@ -7459,8 +7719,19 @@ function requireParse$1 () {
7459
7719
  });
7460
7720
  };
7461
7721
 
7462
- var parseArrayValue = function (val, options, currentArrayLength) {
7722
+ var parseArrayValue = function (val, options, currentArrayLength, isFlatArrayValue) {
7463
7723
  if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
7724
+ if (isFlatArrayValue && options.throwOnLimitExceeded) {
7725
+ var commaCount = 0;
7726
+ var commaIndex = val.indexOf(',');
7727
+ while (commaIndex > -1) {
7728
+ commaCount += 1;
7729
+ if (commaCount >= options.arrayLimit) {
7730
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7731
+ }
7732
+ commaIndex = val.indexOf(',', commaIndex + 1);
7733
+ }
7734
+ }
7464
7735
  return val.split(',');
7465
7736
  }
7466
7737
 
@@ -7537,7 +7808,8 @@ function requireParse$1 () {
7537
7808
  parseArrayValue(
7538
7809
  part.slice(pos + 1),
7539
7810
  options,
7540
- isArray(obj[key]) ? obj[key].length : 0
7811
+ isArray(obj[key]) ? obj[key].length : 0,
7812
+ part.indexOf('[]=') === -1
7541
7813
  ),
7542
7814
  function (encodedVal) {
7543
7815
  return options.decoder(encodedVal, defaults.decoder, charset, 'value');
@@ -7555,10 +7827,7 @@ function requireParse$1 () {
7555
7827
  }
7556
7828
 
7557
7829
  if (options.comma && isArray(val) && val.length > options.arrayLimit) {
7558
- if (options.throwOnLimitExceeded) {
7559
- throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
7560
- }
7561
- val = utils.combine([], val, options.arrayLimit, options.plainObjects);
7830
+ val = utils.combine([], val, options.arrayLimit, options.plainObjects, options.throwOnLimitExceeded);
7562
7831
  }
7563
7832
 
7564
7833
  if (key !== null) {
@@ -7568,7 +7837,8 @@ function requireParse$1 () {
7568
7837
  obj[key],
7569
7838
  val,
7570
7839
  options.arrayLimit,
7571
- options.plainObjects
7840
+ options.plainObjects,
7841
+ options.throwOnLimitExceeded
7572
7842
  );
7573
7843
  } else if (!existing || options.duplicates === 'last') {
7574
7844
  obj[key] = val;
@@ -7603,7 +7873,8 @@ function requireParse$1 () {
7603
7873
  [],
7604
7874
  leaf,
7605
7875
  options.arrayLimit,
7606
- options.plainObjects
7876
+ options.plainObjects,
7877
+ options.throwOnLimitExceeded
7607
7878
  );
7608
7879
  }
7609
7880
  } else {
@@ -7851,166 +8122,6 @@ var qs = /*@__PURE__*/getDefaultExportFromCjs(libExports);
7851
8122
 
7852
8123
  const isValidRequest = (interaction) => interaction.response.status < 400;
7853
8124
 
7854
- const PARAMETER_SEPARATOR = ";";
7855
- const WILDCARD = "*";
7856
- const TYPE_SUBTYPE_SEPARATOR = "/";
7857
- const EXTENSION_SEPARATOR = "+";
7858
- const quality = (a) => {
7859
- const match = /.*;q=([01].?\d*)/.exec(a);
7860
- return match ? parseFloat(match[1]) : 1.0;
7861
- };
7862
- const byQuality = (a, b) => {
7863
- return quality(b) - quality(a);
7864
- };
7865
- const ignoreCasing = (t) => t.toLowerCase();
7866
- const ignoreWhitespace = (t) => t.replace(/\s+/g, "");
7867
- const ignoreParameters = (t) => t.split(PARAMETER_SEPARATOR)[0];
7868
- const removeQuality = (t) => t.replace(/;q=[01].?\d*/, "");
7869
- const type$9 = (t) => t.split(TYPE_SUBTYPE_SEPARATOR)[0];
7870
- const subtype = (t) => {
7871
- const [_maintype, subtype] = t.split(TYPE_SUBTYPE_SEPARATOR);
7872
- return subtype?.split(EXTENSION_SEPARATOR).pop();
7873
- };
7874
- function findMatchingType(requestType, responseTypes) {
7875
- // exact match
7876
- let accept = requestType
7877
- .split(",")
7878
- .map(ignoreWhitespace)
7879
- .map(ignoreCasing)
7880
- .sort(byQuality)
7881
- .map(removeQuality);
7882
- let available = responseTypes.map(ignoreWhitespace).map(ignoreCasing);
7883
- for (const a of accept) {
7884
- const matchExactly = (t) => t === a;
7885
- const index = available.findIndex(matchExactly);
7886
- if (index >= 0) {
7887
- return responseTypes[index];
7888
- }
7889
- }
7890
- // ignore additional parameters
7891
- accept = accept.map(ignoreParameters);
7892
- available = available.map(ignoreParameters);
7893
- for (const a of accept) {
7894
- const matchExactly = (t) => t === a;
7895
- const index = available.findIndex(matchExactly);
7896
- if (index >= 0) {
7897
- return responseTypes[index];
7898
- }
7899
- }
7900
- // ignore vendor extensions
7901
- for (const a of accept) {
7902
- const matchTypesAndSubtypes = (t) => type$9(t) === type$9(a) && subtype(t) === subtype(a);
7903
- const index = available.findIndex(matchTypesAndSubtypes);
7904
- if (index >= 0) {
7905
- return responseTypes[index];
7906
- }
7907
- }
7908
- // wildcards in responseTypes
7909
- for (const a of accept) {
7910
- const matchSubtype = (t) => subtype(t) === WILDCARD && type$9(t) === type$9(a);
7911
- const index = available.findIndex(matchSubtype);
7912
- if (index >= 0) {
7913
- return responseTypes[index];
7914
- }
7915
- }
7916
- if (available.includes(`${WILDCARD}/${WILDCARD}`)) {
7917
- return `${WILDCARD}/${WILDCARD}`;
7918
- }
7919
- // wildcards in requestTypes
7920
- for (const a of accept) {
7921
- const matchSubtype = (t) => subtype(a) === WILDCARD && type$9(t) === type$9(a);
7922
- const index = available.findIndex(matchSubtype);
7923
- if (index >= 0) {
7924
- return responseTypes[index];
7925
- }
7926
- }
7927
- if (accept.includes(`${WILDCARD}/${WILDCARD}`)) {
7928
- return responseTypes[0];
7929
- }
7930
- return undefined;
7931
- }
7932
- const getByContentType = (object = {}, contentType) => {
7933
- const key = findMatchingType(contentType, Object.keys(object));
7934
- return object[key];
7935
- };
7936
- const standardHttpRequestHeaders = [
7937
- "accept",
7938
- "accept-charset",
7939
- "accept-datetime",
7940
- "accept-encoding",
7941
- "accept-language",
7942
- "authorization",
7943
- "cache-control",
7944
- "connection",
7945
- "content-length",
7946
- "content-md5",
7947
- "content-type",
7948
- "cookie",
7949
- "date",
7950
- "expect",
7951
- "forwarded",
7952
- "from",
7953
- "host",
7954
- "if-match",
7955
- "if-modified-since",
7956
- "if-none-match",
7957
- "if-range",
7958
- "if-unmodified-since",
7959
- "max-forwards",
7960
- "origin",
7961
- "pragma",
7962
- "proxy-authorization",
7963
- "range",
7964
- "referer",
7965
- "te",
7966
- "upgrade",
7967
- "user-agent",
7968
- "via",
7969
- "warning",
7970
- ];
7971
- const standardHttpResponseHeaders = [
7972
- "access-control-allow-origin",
7973
- "accept-patch",
7974
- "accept-ranges",
7975
- "age",
7976
- "allow",
7977
- "alt-svc",
7978
- "cache-control",
7979
- "connection",
7980
- "content-disposition",
7981
- "content-encoding",
7982
- "content-language",
7983
- "content-length",
7984
- "content-location",
7985
- "content-md5",
7986
- "content-range",
7987
- "date",
7988
- "etag",
7989
- "expires",
7990
- "last-modified",
7991
- "link",
7992
- "location",
7993
- "p3p",
7994
- "pragma",
7995
- "proxy-authenticate",
7996
- "public-key-pins",
7997
- "refresh",
7998
- "retry-after",
7999
- "server",
8000
- "set-cookie",
8001
- "status",
8002
- "strict-transport-security",
8003
- "trailer",
8004
- "transfer-encoding",
8005
- "tsv",
8006
- "upgrade",
8007
- "vary",
8008
- "via",
8009
- "warning",
8010
- "www-authenticate",
8011
- "x-frame-options",
8012
- ];
8013
-
8014
8125
  const parseBody = (body, contentType, legacyParser) => {
8015
8126
  if (contentType.includes("application/x-www-form-urlencoded") &&
8016
8127
  typeof body === "string") {
@@ -8034,13 +8145,6 @@ const parseBody = (body, contentType, legacyParser) => {
8034
8145
  }
8035
8146
  return body;
8036
8147
  };
8037
- const canValidate$1 = (contentType, disableMultipartFormdata) => {
8038
- return !!findMatchingType(contentType, [
8039
- "application/json",
8040
- "application/x-www-form-urlencoded",
8041
- disableMultipartFormdata ? "" : "multipart/form-data",
8042
- ].filter(Boolean));
8043
- };
8044
8148
  const DEFAULT_CONTENT_TYPE$1 = "application/json";
8045
8149
  function* compareReqBody(ajv, route, interaction, index, config) {
8046
8150
  const { method, oas, operation, path } = route.store;
@@ -8060,8 +8164,30 @@ function* compareReqBody(ajv, route, interaction, index, config) {
8060
8164
  if (!required && body === undefined) {
8061
8165
  return;
8062
8166
  }
8167
+ const bodyStatus = bodyValidationStatus(contentType, body, VALIDATABLE_CONTENT_TYPES.filter((t) => t !== "multipart/form-data" ||
8168
+ !config.get("disable-multipart-formdata")));
8169
+ if (bodyStatus === "skip")
8170
+ return;
8171
+ if (bodyStatus === "warn" && isValidRequest(interaction)) {
8172
+ yield {
8173
+ code: "request.body.unvalidatable",
8174
+ message: `Body with content type '${contentType}' is not supported by the spec comparator`,
8175
+ mockDetails: {
8176
+ ...baseMockDetails(interaction),
8177
+ location: `[root].interactions[${index}].request.body`,
8178
+ value: body,
8179
+ },
8180
+ specDetails: {
8181
+ location: `[root].paths.${path}.${method}.requestBody.content`,
8182
+ pathMethod: method,
8183
+ pathName: path,
8184
+ value: get$1(operation, "requestBody.content"),
8185
+ },
8186
+ type: "warning",
8187
+ };
8188
+ return;
8189
+ }
8063
8190
  if (schema &&
8064
- canValidate$1(contentType, config.get("disable-multipart-formdata")) &&
8065
8191
  isValidRequest(interaction) &&
8066
8192
  (config.get("no-validate-request-body-unless-application-json")
8067
8193
  ? !!findMatchingType("application/json", availableRequestContentTypes)
@@ -8153,7 +8279,8 @@ function* compareReqHeader(ajv, route, interaction, index, config) {
8153
8279
  Object.entries(operation.responses).reduce((acc, [_status, response]) => {
8154
8280
  return [
8155
8281
  ...acc,
8156
- ...Object.keys(dereferenceOas(response || {}, oas)?.content || {}),
8282
+ ...Object.keys(dereferenceOas(response || {}, oas)
8283
+ ?.content || {}),
8157
8284
  ];
8158
8285
  }, []);
8159
8286
  const availableResponseContentType = operation.produces ||
@@ -8700,9 +8827,6 @@ function* compareReqSecurity(ajv, route, interaction, index, config) {
8700
8827
 
8701
8828
  const patternedStatus = (status) => `${Math.floor(status / 100)}XX`;
8702
8829
 
8703
- const canValidate = (contentType = "") => {
8704
- return !!findMatchingType(contentType, ["application/json"]);
8705
- };
8706
8830
  const DEFAULT_CONTENT_TYPE = "application/json";
8707
8831
  function* compareResBody(ajv, route, interaction, index, config) {
8708
8832
  const { method, oas, operation, path } = route.store;
@@ -8757,14 +8881,15 @@ function* compareResBody(ajv, route, interaction, index, config) {
8757
8881
  type: "warning",
8758
8882
  };
8759
8883
  }
8760
- if (value && canValidate(contentType) && !schema) {
8884
+ const bodyStatus = bodyValidationStatus(contentType, value);
8885
+ if (bodyStatus === "warn") {
8761
8886
  yield {
8762
- code: "response.body.unknown",
8763
- message: "No matching schema found for response body",
8887
+ code: "response.body.unvalidatable",
8888
+ message: `Body with content type '${contentType}' is not supported by the spec comparator`,
8764
8889
  mockDetails: {
8765
8890
  ...baseMockDetails(interaction),
8766
8891
  location: `[root].interactions[${index}].response.body`,
8767
- value: value,
8892
+ value,
8768
8893
  },
8769
8894
  specDetails: {
8770
8895
  location: `[root].paths.${path}.${method}.responses.${status}.content`,
@@ -8772,32 +8897,52 @@ function* compareResBody(ajv, route, interaction, index, config) {
8772
8897
  pathName: path,
8773
8898
  value: response.content,
8774
8899
  },
8775
- type: "error",
8900
+ type: "warning",
8776
8901
  };
8777
8902
  }
8778
- if (value && canValidate(contentType) && schema) {
8779
- const schemaId = `[root].paths.${path}.${method}.responses.${status}.content.${contentType}`;
8780
- const validate = getValidateFunction(ajv, schemaId, () => transformReceivedSchema(minimumSchema(schema, oas), config.get("no-transform-non-nullable-response-schema")));
8781
- if (!validate(value)) {
8782
- for (const error of validate.errors) {
8783
- yield {
8784
- code: "response.body.incompatible",
8785
- message: `Response body is incompatible with the response body schema in the spec file: ${formatMessage(error)}`,
8786
- mockDetails: {
8787
- ...baseMockDetails(interaction),
8788
- location: `[root].interactions[${index}].response.body${formatInstancePath(error)}`,
8789
- value: error.instancePath
8790
- ? get$1(value, splitPath(error.instancePath))
8791
- : value,
8792
- },
8793
- specDetails: {
8794
- location: `${schemaId}.schema.${formatSchemaPath(error)}`,
8795
- pathMethod: method,
8796
- pathName: path,
8797
- value: get$1(validate.schema, splitPath(error.schemaPath)),
8798
- },
8799
- type: "error",
8800
- };
8903
+ else if (bodyStatus === "validate") {
8904
+ if (value && !schema) {
8905
+ yield {
8906
+ code: "response.body.unknown",
8907
+ message: "No matching schema found for response body",
8908
+ mockDetails: {
8909
+ ...baseMockDetails(interaction),
8910
+ location: `[root].interactions[${index}].response.body`,
8911
+ value: value,
8912
+ },
8913
+ specDetails: {
8914
+ location: `[root].paths.${path}.${method}.responses.${status}.content`,
8915
+ pathMethod: method,
8916
+ pathName: path,
8917
+ value: response.content,
8918
+ },
8919
+ type: "error",
8920
+ };
8921
+ }
8922
+ if (value && schema) {
8923
+ const schemaId = `[root].paths.${path}.${method}.responses.${status}.content.${contentType}`;
8924
+ const validate = getValidateFunction(ajv, schemaId, () => transformReceivedSchema(minimumSchema(schema, oas), config.get("no-transform-non-nullable-response-schema")));
8925
+ if (!validate(value)) {
8926
+ for (const error of validate.errors) {
8927
+ yield {
8928
+ code: "response.body.incompatible",
8929
+ message: `Response body is incompatible with the response body schema in the spec file: ${formatMessage(error)}`,
8930
+ mockDetails: {
8931
+ ...baseMockDetails(interaction),
8932
+ location: `[root].interactions[${index}].response.body${formatInstancePath(error)}`,
8933
+ value: error.instancePath
8934
+ ? get$1(value, splitPath(error.instancePath))
8935
+ : value,
8936
+ },
8937
+ specDetails: {
8938
+ location: `${schemaId}.schema.${formatSchemaPath(error)}`,
8939
+ pathMethod: method,
8940
+ pathName: path,
8941
+ value: get$1(validate.schema, splitPath(error.schemaPath)),
8942
+ },
8943
+ type: "error",
8944
+ };
8945
+ }
8801
8946
  }
8802
8947
  }
8803
8948
  }
@@ -8993,11 +9138,17 @@ function* compareHttpInteraction(ajvCoerce, ajvNocoerce, router, interaction, in
8993
9138
  return;
8994
9139
  }
8995
9140
  yield* compareReqSecurity(ajvCoerce, route, interaction, index, config);
8996
- yield* compareReqHeader(ajvCoerce, route, interaction, index, config);
9141
+ const reqHeaderResults = Array.from(compareReqHeader(ajvCoerce, route, interaction, index, config));
9142
+ yield* reqHeaderResults;
8997
9143
  yield* compareReqQuery(ajvCoerce, route, interaction, index, config);
8998
- yield* compareReqBody(ajvNocoerce, route, interaction, index, config);
8999
- yield* compareResHeader(ajvCoerce, route, interaction, index);
9000
- yield* compareResBody(ajvNocoerce, route, interaction, index, config);
9144
+ if (!reqHeaderResults.some((r) => r.code === "request.content-type.incompatible")) {
9145
+ yield* compareReqBody(ajvNocoerce, route, interaction, index, config);
9146
+ }
9147
+ const resHeaderResults = Array.from(compareResHeader(ajvCoerce, route, interaction, index));
9148
+ yield* resHeaderResults;
9149
+ if (!resHeaderResults.some((r) => r.code === "response.content-type.incompatible")) {
9150
+ yield* compareResBody(ajvNocoerce, route, interaction, index, config);
9151
+ }
9001
9152
  }
9002
9153
 
9003
9154
  const isSwagger2 = (oas) => Object.hasOwn(oas, "swagger") &&
@@ -17188,7 +17339,7 @@ function requireFastUri () {
17188
17339
  if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
17189
17340
  // convert Unicode IDN -> ASCII IDN
17190
17341
  try {
17191
- parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
17342
+ parsed.host = new URL('http://' + parsed.host).hostname;
17192
17343
  } catch (e) {
17193
17344
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
17194
17345
  }
@@ -20348,9 +20499,7 @@ const AsyncMessage = Type.Object({
20348
20499
  metadata: Type.Optional(Type.Record(Type.String(), Type.String())),
20349
20500
  comments: Type.Optional(Type.Object({
20350
20501
  references: Type.Optional(Type.Object({
20351
- AsyncAPI: Type.Optional(Type.Object({
20352
- operationId: Type.String(),
20353
- })),
20502
+ AsyncAPI: Type.Optional(Type.Unknown()),
20354
20503
  })),
20355
20504
  })),
20356
20505
  });
@@ -20370,9 +20519,7 @@ const SyncMessage = Type.Object({
20370
20519
  response: Type.Array(SyncMessageSide),
20371
20520
  comments: Type.Optional(Type.Object({
20372
20521
  references: Type.Optional(Type.Object({
20373
- AsyncAPI: Type.Optional(Type.Object({
20374
- operationId: Type.String(),
20375
- })),
20522
+ AsyncAPI: Type.Optional(Type.Unknown()),
20376
20523
  })),
20377
20524
  })),
20378
20525
  });
@@ -20462,29 +20609,32 @@ const interactionV4 = (i) => ({
20462
20609
  headers: flattenValues(i.response?.headers),
20463
20610
  },
20464
20611
  });
20612
+ const asAsyncapiReferences = (asyncapiRef) => {
20613
+ if (!asyncapiRef)
20614
+ return undefined;
20615
+ if (typeof asyncapiRef !== "object")
20616
+ return {};
20617
+ return { ...asyncapiRef };
20618
+ };
20465
20619
  const parseAsyncInteraction = (i) => {
20466
- const asyncapiRef = i.comments?.references?.AsyncAPI;
20620
+ const asyncapiRef = asAsyncapiReferences(i.comments?.references?.AsyncAPI);
20467
20621
  return {
20468
20622
  _kind: "async",
20469
20623
  description: i.description,
20470
20624
  providerState: i.providerState,
20471
- asyncapiReferences: asyncapiRef
20472
- ? { operationId: asyncapiRef.operationId }
20473
- : undefined,
20625
+ asyncapiReferences: asyncapiRef,
20474
20626
  payload: parseAsPactV4Body(i.contents),
20475
20627
  contentType: i.contents?.contentType,
20476
20628
  metadata: i.metadata,
20477
20629
  };
20478
20630
  };
20479
20631
  const parseSyncInteraction = (i) => {
20480
- const asyncapiRef = i.comments?.references?.AsyncAPI;
20632
+ const asyncapiRef = asAsyncapiReferences(i.comments?.references?.AsyncAPI);
20481
20633
  return {
20482
20634
  _kind: "sync",
20483
20635
  description: i.description,
20484
20636
  providerState: i.providerState,
20485
- asyncapiReferences: asyncapiRef
20486
- ? { operationId: asyncapiRef.operationId }
20487
- : undefined,
20637
+ asyncapiReferences: asyncapiRef,
20488
20638
  request: {
20489
20639
  payload: parseAsPactV4Body(i.request.contents),
20490
20640
  contentType: i.request.contents?.contentType,