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