@postman-cse/onboarding-bootstrap 1.0.0 → 1.1.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/README.md +21 -4
- package/dist/action.cjs +806 -104
- package/dist/cli.cjs +806 -104
- package/dist/index.cjs +806 -104
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -47930,8 +47930,6 @@ var COMPATIBLE_SCHEMA_URIS = /* @__PURE__ */ new Set([
|
|
|
47930
47930
|
`${DRAFT_2020_12_SCHEMA_URI}#`
|
|
47931
47931
|
]);
|
|
47932
47932
|
var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
47933
|
-
"$defs",
|
|
47934
|
-
"$id",
|
|
47935
47933
|
"$ref",
|
|
47936
47934
|
"$schema",
|
|
47937
47935
|
"additionalItems",
|
|
@@ -47976,6 +47974,10 @@ var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
47976
47974
|
]);
|
|
47977
47975
|
var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
47978
47976
|
"title",
|
|
47977
|
+
"$comment",
|
|
47978
|
+
"$defs",
|
|
47979
|
+
"definitions",
|
|
47980
|
+
"$id",
|
|
47979
47981
|
"description",
|
|
47980
47982
|
"default",
|
|
47981
47983
|
"example",
|
|
@@ -47986,8 +47988,32 @@ var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
47986
47988
|
"xml",
|
|
47987
47989
|
"externalDocs",
|
|
47988
47990
|
"discriminator",
|
|
47989
|
-
"
|
|
47991
|
+
"contentEncoding",
|
|
47992
|
+
"contentMediaType",
|
|
47993
|
+
"contentSchema"
|
|
47994
|
+
]);
|
|
47995
|
+
var ASSERTED_FORMATS = /* @__PURE__ */ new Set([
|
|
47996
|
+
"date-time",
|
|
47997
|
+
"date",
|
|
47998
|
+
"time",
|
|
47999
|
+
"duration",
|
|
48000
|
+
"email",
|
|
48001
|
+
"hostname",
|
|
48002
|
+
"ipv4",
|
|
48003
|
+
"ipv6",
|
|
48004
|
+
"uri",
|
|
48005
|
+
"uri-reference",
|
|
48006
|
+
"uri-template",
|
|
48007
|
+
"uuid",
|
|
48008
|
+
"json-pointer",
|
|
48009
|
+
"relative-json-pointer",
|
|
48010
|
+
"regex"
|
|
47990
48011
|
]);
|
|
48012
|
+
var INT32_MIN = -2147483648;
|
|
48013
|
+
var INT32_MAX = 2147483647;
|
|
48014
|
+
var MAX_REFERENCED_SCHEMAS = 400;
|
|
48015
|
+
var DRAFT_2020_12_ONLY_KEYS = /* @__PURE__ */ new Set(["prefixItems", "dependentRequired", "dependentSchemas", "minContains", "maxContains", "unevaluatedItems", "unevaluatedProperties"]);
|
|
48016
|
+
var DRAFT_07_ONLY_KEYS = /* @__PURE__ */ new Set(["dependencies", "additionalItems"]);
|
|
47991
48017
|
function asRecord3(value) {
|
|
47992
48018
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
47993
48019
|
return value;
|
|
@@ -48005,8 +48031,8 @@ function resolvePointer(root, ref) {
|
|
|
48005
48031
|
function unsupported(message) {
|
|
48006
48032
|
return { unsupported: message };
|
|
48007
48033
|
}
|
|
48008
|
-
function
|
|
48009
|
-
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !
|
|
48034
|
+
function mergeRequiredWithoutStripped(required, strippedProperties) {
|
|
48035
|
+
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !strippedProperties.has(entry));
|
|
48010
48036
|
return values.length > 0 ? values : void 0;
|
|
48011
48037
|
}
|
|
48012
48038
|
function hasUnsupported(child) {
|
|
@@ -48022,11 +48048,31 @@ function rootDialect(root, version, schemaRecord) {
|
|
|
48022
48048
|
if (declared.includes("draft-07")) return DRAFT_07_SCHEMA_URI;
|
|
48023
48049
|
return DRAFT_2020_12_SCHEMA_URI;
|
|
48024
48050
|
}
|
|
48025
|
-
function
|
|
48026
|
-
const
|
|
48027
|
-
if (
|
|
48051
|
+
function registerDef(ctx, ref) {
|
|
48052
|
+
const existing = ctx.defs.get(ref);
|
|
48053
|
+
if (existing) return existing;
|
|
48054
|
+
if (ctx.defs.size >= MAX_REFERENCED_SCHEMAS) {
|
|
48055
|
+
const entry2 = { name: "overflow", unsupported: `OpenAPI schema reference graph exceeded ${MAX_REFERENCED_SCHEMAS} referenced schemas` };
|
|
48056
|
+
return entry2;
|
|
48057
|
+
}
|
|
48058
|
+
const entry = { name: `d${ctx.defs.size}` };
|
|
48059
|
+
ctx.defs.set(ref, entry);
|
|
48060
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
48061
|
+
if (resolved === void 0) {
|
|
48062
|
+
entry.unsupported = `Unresolved OpenAPI $ref: ${ref}`;
|
|
48063
|
+
return entry;
|
|
48064
|
+
}
|
|
48065
|
+
const normalized = normalizeSchema(ctx, resolved, { depth: 0, rootSchema: false });
|
|
48066
|
+
const bad = hasUnsupported(normalized);
|
|
48067
|
+
if (bad) entry.unsupported = bad;
|
|
48068
|
+
else entry.schema = normalized;
|
|
48069
|
+
return entry;
|
|
48070
|
+
}
|
|
48071
|
+
function normalizeSchema(ctx, schema2, options) {
|
|
48072
|
+
const depth = options.depth;
|
|
48073
|
+
if (depth > 50) return unsupported("OpenAPI schema nesting depth exceeded 50");
|
|
48028
48074
|
if (Array.isArray(schema2)) {
|
|
48029
|
-
const normalized2 = schema2.map((entry) => normalizeSchema(
|
|
48075
|
+
const normalized2 = schema2.map((entry) => normalizeSchema(ctx, entry, { depth: depth + 1, rootSchema: false }));
|
|
48030
48076
|
const bad = normalized2.map(hasUnsupported).find(Boolean);
|
|
48031
48077
|
return bad ? unsupported(bad) : normalized2;
|
|
48032
48078
|
}
|
|
@@ -48035,40 +48081,42 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48035
48081
|
const ref = typeof record.$ref === "string" ? record.$ref : "";
|
|
48036
48082
|
if (ref) {
|
|
48037
48083
|
if (!ref.startsWith("#/")) return unsupported(`External OpenAPI $ref remained unresolved: ${ref}`);
|
|
48038
|
-
const
|
|
48039
|
-
|
|
48040
|
-
|
|
48041
|
-
|
|
48042
|
-
|
|
48043
|
-
|
|
48044
|
-
|
|
48045
|
-
|
|
48046
|
-
|
|
48047
|
-
|
|
48048
|
-
|
|
48049
|
-
const
|
|
48050
|
-
if (
|
|
48051
|
-
|
|
48052
|
-
|
|
48053
|
-
|
|
48054
|
-
|
|
48055
|
-
rootSchema: false,
|
|
48056
|
-
stack: [...stack, ref]
|
|
48057
|
-
});
|
|
48084
|
+
const siblingEntries = ctx.version === "3.1" ? Object.entries(record).filter(([key]) => key !== "$ref") : [];
|
|
48085
|
+
const inlineStack = options.inlineStack ?? [];
|
|
48086
|
+
if (options.rootSchema && siblingEntries.length === 0 && !inlineStack.includes(ref)) {
|
|
48087
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
48088
|
+
if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
|
|
48089
|
+
const inlined = normalizeSchema(ctx, resolved, { depth, rootSchema: true, inlineStack: [...inlineStack, ref] });
|
|
48090
|
+
const bad = hasUnsupported(inlined);
|
|
48091
|
+
return bad ? unsupported(bad) : inlined;
|
|
48092
|
+
}
|
|
48093
|
+
const entry = registerDef(ctx, ref);
|
|
48094
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
48095
|
+
const refNode = { $ref: `#/$defs/${entry.name}` };
|
|
48096
|
+
if (siblingEntries.length === 0) {
|
|
48097
|
+
if (options.rootSchema) refNode.$schema = ctx.dialect;
|
|
48098
|
+
return refNode;
|
|
48099
|
+
}
|
|
48100
|
+
const siblingSchema = normalizeSchema(ctx, Object.fromEntries(siblingEntries), { depth: depth + 1, rootSchema: false });
|
|
48058
48101
|
const siblingUnsupported = hasUnsupported(siblingSchema);
|
|
48059
48102
|
if (siblingUnsupported) return unsupported(siblingUnsupported);
|
|
48060
|
-
const wrapper = { allOf: [
|
|
48061
|
-
if (options.rootSchema) wrapper.$schema =
|
|
48103
|
+
const wrapper = { allOf: [refNode, siblingSchema] };
|
|
48104
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
48062
48105
|
return wrapper;
|
|
48063
48106
|
}
|
|
48064
48107
|
const sourceSchema = { ...record };
|
|
48065
|
-
const nullable = sourceSchema.nullable === true &&
|
|
48108
|
+
const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
|
|
48066
48109
|
delete sourceSchema.nullable;
|
|
48067
|
-
const
|
|
48110
|
+
const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
|
|
48111
|
+
const strippedProperties = /* @__PURE__ */ new Set();
|
|
48068
48112
|
const rawProperties = asRecord3(sourceSchema.properties);
|
|
48069
48113
|
if (rawProperties) {
|
|
48070
48114
|
for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
|
|
48071
|
-
|
|
48115
|
+
let flagSource = asRecord3(propertySchema);
|
|
48116
|
+
if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
|
|
48117
|
+
flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
|
|
48118
|
+
}
|
|
48119
|
+
if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
|
|
48072
48120
|
}
|
|
48073
48121
|
}
|
|
48074
48122
|
const normalized = {};
|
|
@@ -48095,17 +48143,69 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48095
48143
|
delete normalized.maximum;
|
|
48096
48144
|
continue;
|
|
48097
48145
|
}
|
|
48146
|
+
if (key === "format") {
|
|
48147
|
+
if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
|
|
48148
|
+
continue;
|
|
48149
|
+
}
|
|
48098
48150
|
if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
|
|
48099
|
-
if (
|
|
48151
|
+
if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
|
|
48152
|
+
return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
|
|
48153
|
+
}
|
|
48154
|
+
if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
|
|
48155
|
+
return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
|
|
48156
|
+
}
|
|
48157
|
+
if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
|
|
48100
48158
|
return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
|
|
48101
48159
|
}
|
|
48160
|
+
if (key === "patternProperties" || key === "dependentSchemas") {
|
|
48161
|
+
const map2 = asRecord3(value);
|
|
48162
|
+
if (!map2) continue;
|
|
48163
|
+
const next = {};
|
|
48164
|
+
for (const [mapKey, mapSchema] of Object.entries(map2)) {
|
|
48165
|
+
const child2 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
|
|
48166
|
+
const bad2 = hasUnsupported(child2);
|
|
48167
|
+
if (bad2) return unsupported(bad2);
|
|
48168
|
+
next[mapKey] = child2;
|
|
48169
|
+
}
|
|
48170
|
+
normalized[key] = next;
|
|
48171
|
+
continue;
|
|
48172
|
+
}
|
|
48173
|
+
if (key === "dependentRequired") {
|
|
48174
|
+
const map2 = asRecord3(value);
|
|
48175
|
+
if (!map2) continue;
|
|
48176
|
+
for (const names of Object.values(map2)) {
|
|
48177
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
|
|
48178
|
+
return unsupported("dependentRequired requires string array values");
|
|
48179
|
+
}
|
|
48180
|
+
}
|
|
48181
|
+
normalized.dependentRequired = value;
|
|
48182
|
+
continue;
|
|
48183
|
+
}
|
|
48184
|
+
if (key === "dependencies") {
|
|
48185
|
+
const map2 = asRecord3(value);
|
|
48186
|
+
if (!map2) continue;
|
|
48187
|
+
const next = {};
|
|
48188
|
+
for (const [mapKey, dependency] of Object.entries(map2)) {
|
|
48189
|
+
if (Array.isArray(dependency)) {
|
|
48190
|
+
if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
|
|
48191
|
+
next[mapKey] = dependency;
|
|
48192
|
+
continue;
|
|
48193
|
+
}
|
|
48194
|
+
const child2 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
|
|
48195
|
+
const bad2 = hasUnsupported(child2);
|
|
48196
|
+
if (bad2) return unsupported(bad2);
|
|
48197
|
+
next[mapKey] = child2;
|
|
48198
|
+
}
|
|
48199
|
+
normalized.dependencies = next;
|
|
48200
|
+
continue;
|
|
48201
|
+
}
|
|
48102
48202
|
if (key === "properties") {
|
|
48103
48203
|
const properties = asRecord3(value);
|
|
48104
48204
|
if (!properties) continue;
|
|
48105
48205
|
const nextProperties = {};
|
|
48106
48206
|
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
48107
|
-
if (
|
|
48108
|
-
const child2 = normalizeSchema(
|
|
48207
|
+
if (strippedProperties.has(propertyName)) continue;
|
|
48208
|
+
const child2 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
|
|
48109
48209
|
const bad2 = hasUnsupported(child2);
|
|
48110
48210
|
if (bad2) return unsupported(bad2);
|
|
48111
48211
|
nextProperties[propertyName] = child2;
|
|
@@ -48114,16 +48214,46 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48114
48214
|
continue;
|
|
48115
48215
|
}
|
|
48116
48216
|
if (key === "required") {
|
|
48117
|
-
const required =
|
|
48217
|
+
const required = mergeRequiredWithoutStripped(value, strippedProperties);
|
|
48118
48218
|
if (required) normalized.required = required;
|
|
48119
48219
|
continue;
|
|
48120
48220
|
}
|
|
48121
|
-
const child = normalizeSchema(
|
|
48221
|
+
const child = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
|
|
48122
48222
|
const bad = hasUnsupported(child);
|
|
48123
48223
|
if (bad) return unsupported(bad);
|
|
48124
48224
|
normalized[key] = child;
|
|
48125
48225
|
}
|
|
48126
|
-
if (
|
|
48226
|
+
if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
|
|
48227
|
+
if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
|
|
48228
|
+
else delete normalized.exclusiveMinimum;
|
|
48229
|
+
}
|
|
48230
|
+
if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
|
|
48231
|
+
if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
|
|
48232
|
+
else delete normalized.exclusiveMaximum;
|
|
48233
|
+
}
|
|
48234
|
+
if (sourceSchema.format === "int32") {
|
|
48235
|
+
const type2 = normalized.type;
|
|
48236
|
+
const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
|
|
48237
|
+
if (isInteger2) {
|
|
48238
|
+
if (typeof normalized.exclusiveMinimum === "number") {
|
|
48239
|
+
if (normalized.exclusiveMinimum < INT32_MIN) {
|
|
48240
|
+
delete normalized.exclusiveMinimum;
|
|
48241
|
+
normalized.minimum = INT32_MIN;
|
|
48242
|
+
}
|
|
48243
|
+
} else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
|
|
48244
|
+
normalized.minimum = INT32_MIN;
|
|
48245
|
+
}
|
|
48246
|
+
if (typeof normalized.exclusiveMaximum === "number") {
|
|
48247
|
+
if (normalized.exclusiveMaximum > INT32_MAX) {
|
|
48248
|
+
delete normalized.exclusiveMaximum;
|
|
48249
|
+
normalized.maximum = INT32_MAX;
|
|
48250
|
+
}
|
|
48251
|
+
} else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
|
|
48252
|
+
normalized.maximum = INT32_MAX;
|
|
48253
|
+
}
|
|
48254
|
+
}
|
|
48255
|
+
}
|
|
48256
|
+
if (options.rootSchema) normalized.$schema = ctx.dialect;
|
|
48127
48257
|
if (nullable) {
|
|
48128
48258
|
if (typeof normalized.type === "string") {
|
|
48129
48259
|
normalized.type = [normalized.type, "null"];
|
|
@@ -48135,23 +48265,106 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48135
48265
|
const wrappedSchema = { ...normalized };
|
|
48136
48266
|
delete wrappedSchema.$schema;
|
|
48137
48267
|
const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
|
|
48138
|
-
if (options.rootSchema) wrapper.$schema =
|
|
48268
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
48139
48269
|
return wrapper;
|
|
48140
48270
|
}
|
|
48141
48271
|
}
|
|
48142
48272
|
return normalized;
|
|
48143
48273
|
}
|
|
48144
|
-
function
|
|
48274
|
+
function isSchemaGraphOverflow(packed) {
|
|
48275
|
+
return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
|
|
48276
|
+
}
|
|
48277
|
+
function packSchema(root, schema2, version, direction = "response") {
|
|
48145
48278
|
try {
|
|
48279
|
+
if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
|
|
48280
|
+
if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
|
|
48146
48281
|
const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
|
|
48147
|
-
const
|
|
48282
|
+
const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map() };
|
|
48283
|
+
const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
|
|
48148
48284
|
const message = hasUnsupported(normalized);
|
|
48149
|
-
|
|
48285
|
+
if (message) return unsupported(message);
|
|
48286
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
48287
|
+
for (const entry of ctx.defs.values()) {
|
|
48288
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
48289
|
+
const entrySchema = asRecord3(entry.schema);
|
|
48290
|
+
const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
|
|
48291
|
+
if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
|
|
48292
|
+
}
|
|
48293
|
+
for (const start of aliasTargets.keys()) {
|
|
48294
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48295
|
+
let current = start;
|
|
48296
|
+
while (current !== void 0 && aliasTargets.has(current)) {
|
|
48297
|
+
if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
|
|
48298
|
+
seen.add(current);
|
|
48299
|
+
current = aliasTargets.get(current);
|
|
48300
|
+
}
|
|
48301
|
+
}
|
|
48302
|
+
if (ctx.defs.size > 0) {
|
|
48303
|
+
const normalizedRecord = asRecord3(normalized);
|
|
48304
|
+
if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
|
|
48305
|
+
normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
|
|
48306
|
+
}
|
|
48307
|
+
return { schema: normalized };
|
|
48150
48308
|
} catch (error) {
|
|
48151
48309
|
return unsupported(error instanceof Error ? error.message : String(error));
|
|
48152
48310
|
}
|
|
48153
48311
|
}
|
|
48154
48312
|
|
|
48313
|
+
// src/lib/spec/schema-validator-code.ts
|
|
48314
|
+
var import_schemasafe = __toESM(require_src(), 1);
|
|
48315
|
+
function compileSchemaValidator(schema2) {
|
|
48316
|
+
try {
|
|
48317
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
48318
|
+
includeErrors: false,
|
|
48319
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
48320
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
48321
|
+
// Plaid, and DigitalOcean public specs).
|
|
48322
|
+
allowUnusedKeywords: true,
|
|
48323
|
+
contentValidation: false,
|
|
48324
|
+
formatAssertion: true,
|
|
48325
|
+
isJSON: true,
|
|
48326
|
+
mode: "default",
|
|
48327
|
+
removeAdditional: false,
|
|
48328
|
+
requireSchema: true,
|
|
48329
|
+
requireStringValidation: false,
|
|
48330
|
+
useDefaults: false
|
|
48331
|
+
});
|
|
48332
|
+
return (value) => validate2(value);
|
|
48333
|
+
} catch {
|
|
48334
|
+
return null;
|
|
48335
|
+
}
|
|
48336
|
+
}
|
|
48337
|
+
function compileSchemaValidatorCode(schema2) {
|
|
48338
|
+
try {
|
|
48339
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
48340
|
+
includeErrors: true,
|
|
48341
|
+
allErrors: true,
|
|
48342
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
48343
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
48344
|
+
// Plaid, and DigitalOcean public specs).
|
|
48345
|
+
allowUnusedKeywords: true,
|
|
48346
|
+
contentValidation: false,
|
|
48347
|
+
formatAssertion: true,
|
|
48348
|
+
isJSON: true,
|
|
48349
|
+
mode: "default",
|
|
48350
|
+
removeAdditional: false,
|
|
48351
|
+
requireSchema: true,
|
|
48352
|
+
requireStringValidation: false,
|
|
48353
|
+
useDefaults: false
|
|
48354
|
+
});
|
|
48355
|
+
const source = validate2.toModule();
|
|
48356
|
+
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
48357
|
+
throw new Error("schemasafe generated forbidden dynamic code");
|
|
48358
|
+
}
|
|
48359
|
+
return source;
|
|
48360
|
+
} catch (error) {
|
|
48361
|
+
throw new Error(
|
|
48362
|
+
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error instanceof Error ? error.message : String(error)}`,
|
|
48363
|
+
{ cause: error }
|
|
48364
|
+
);
|
|
48365
|
+
}
|
|
48366
|
+
}
|
|
48367
|
+
|
|
48155
48368
|
// src/lib/spec/contract-index.ts
|
|
48156
48369
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
48157
48370
|
function asRecord4(value) {
|
|
@@ -48227,7 +48440,7 @@ function collectSecurityApiKeys(root, operation) {
|
|
|
48227
48440
|
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
48228
48441
|
for (const schemeName of Object.keys(requirement)) {
|
|
48229
48442
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
48230
|
-
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
|
|
48443
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
|
|
48231
48444
|
names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
|
|
48232
48445
|
}
|
|
48233
48446
|
}
|
|
@@ -48249,12 +48462,130 @@ function collectSecuritySchemeWarnings(root, operation) {
|
|
|
48249
48462
|
for (const schemeName of Object.keys(requirement)) {
|
|
48250
48463
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
48251
48464
|
warnings.add(
|
|
48252
|
-
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
|
|
48465
|
+
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
|
|
48253
48466
|
);
|
|
48254
48467
|
}
|
|
48255
48468
|
}
|
|
48256
48469
|
return [...warnings];
|
|
48257
48470
|
}
|
|
48471
|
+
function securityCheckFor(schemeName, scheme) {
|
|
48472
|
+
const kind = securitySchemeKind(scheme);
|
|
48473
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
|
|
48474
|
+
return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
|
|
48475
|
+
}
|
|
48476
|
+
if (scheme?.type === "http") {
|
|
48477
|
+
const httpScheme = String(scheme.scheme || "").toLowerCase();
|
|
48478
|
+
if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
|
|
48479
|
+
if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
|
|
48480
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
48481
|
+
}
|
|
48482
|
+
if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
|
|
48483
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
48484
|
+
}
|
|
48485
|
+
return { scheme: schemeName, kind, checkable: false };
|
|
48486
|
+
}
|
|
48487
|
+
function collectSecurityRuntimeChecks(root, operation) {
|
|
48488
|
+
const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
|
|
48489
|
+
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
48490
|
+
const alternatives = [];
|
|
48491
|
+
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
48492
|
+
const schemeNames = Object.keys(requirement);
|
|
48493
|
+
if (schemeNames.length === 0) return void 0;
|
|
48494
|
+
alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
|
|
48495
|
+
}
|
|
48496
|
+
if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
|
|
48497
|
+
return alternatives.length > 0 ? alternatives : void 0;
|
|
48498
|
+
}
|
|
48499
|
+
function resolvedParameters(root, pathItem, operation) {
|
|
48500
|
+
return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
|
|
48501
|
+
try {
|
|
48502
|
+
return resolveInternalRef(root, rawParam);
|
|
48503
|
+
} catch {
|
|
48504
|
+
return null;
|
|
48505
|
+
}
|
|
48506
|
+
}).filter((param) => Boolean(param));
|
|
48507
|
+
}
|
|
48508
|
+
var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
|
|
48509
|
+
var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
|
|
48510
|
+
function isIgnoredParameter(location2, name) {
|
|
48511
|
+
return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
|
|
48512
|
+
}
|
|
48513
|
+
function collectSerializationWarnings(root, pathItem, operation) {
|
|
48514
|
+
const warnings = [];
|
|
48515
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
48516
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
48517
|
+
const name = String(param.name || "");
|
|
48518
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
48519
|
+
if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
|
|
48520
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
48521
|
+
const defaultExplode = style === "form";
|
|
48522
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
48523
|
+
if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || param.content !== void 0) {
|
|
48524
|
+
warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
|
|
48525
|
+
}
|
|
48526
|
+
}
|
|
48527
|
+
return warnings;
|
|
48528
|
+
}
|
|
48529
|
+
var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
48530
|
+
function packedScalarSchema(packed) {
|
|
48531
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
48532
|
+
const record = asRecord4(packed.schema);
|
|
48533
|
+
if (!record) return void 0;
|
|
48534
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
48535
|
+
if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
48536
|
+
return packed.schema;
|
|
48537
|
+
}
|
|
48538
|
+
function collectParameterChecks(root, pathItem, operation, version, operationId, warnings) {
|
|
48539
|
+
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
48540
|
+
const checks = [];
|
|
48541
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48542
|
+
const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
|
|
48543
|
+
try {
|
|
48544
|
+
return resolveInternalRef(root, rawParam);
|
|
48545
|
+
} catch {
|
|
48546
|
+
return null;
|
|
48547
|
+
}
|
|
48548
|
+
}).filter((param) => Boolean(param));
|
|
48549
|
+
for (const param of orderedParams) {
|
|
48550
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
48551
|
+
if (location2 !== "query" && location2 !== "header") continue;
|
|
48552
|
+
const name = String(param.name || "");
|
|
48553
|
+
if (!name || isIgnoredParameter(location2, name)) continue;
|
|
48554
|
+
const key = `${location2}:${name.toLowerCase()}`;
|
|
48555
|
+
if (seen.has(key)) continue;
|
|
48556
|
+
seen.add(key);
|
|
48557
|
+
if (securityKeys.has(key)) continue;
|
|
48558
|
+
if (param.content !== void 0 || param.schema === void 0) continue;
|
|
48559
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
48560
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
48561
|
+
const defaultExplode = style === "form";
|
|
48562
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
48563
|
+
if (style !== defaultStyle || explode !== defaultExplode) continue;
|
|
48564
|
+
const packed = packSchema(root, param.schema, version);
|
|
48565
|
+
if (packed.unsupported) {
|
|
48566
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
|
|
48567
|
+
continue;
|
|
48568
|
+
}
|
|
48569
|
+
const schema2 = packedScalarSchema(packed);
|
|
48570
|
+
if (schema2 === void 0) continue;
|
|
48571
|
+
const check = { in: location2, name, required: param.required === true, schema: schema2 };
|
|
48572
|
+
if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
|
|
48573
|
+
checks.push(check);
|
|
48574
|
+
}
|
|
48575
|
+
return checks.length > 0 ? checks : void 0;
|
|
48576
|
+
}
|
|
48577
|
+
function collectDeclaredQueryParameters(root, pathItem, operation) {
|
|
48578
|
+
const names = /* @__PURE__ */ new Set();
|
|
48579
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
48580
|
+
if (String(param.in || "").toLowerCase() !== "query") continue;
|
|
48581
|
+
const name = String(param.name || "");
|
|
48582
|
+
if (name) names.add(name.toLowerCase());
|
|
48583
|
+
}
|
|
48584
|
+
for (const key of collectSecurityApiKeys(root, operation)) {
|
|
48585
|
+
if (key.startsWith("query:")) names.add(key.slice("query:".length));
|
|
48586
|
+
}
|
|
48587
|
+
return [...names];
|
|
48588
|
+
}
|
|
48258
48589
|
function collectParameters(root, pathItem, operation) {
|
|
48259
48590
|
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
48260
48591
|
const requirements = [];
|
|
@@ -48264,9 +48595,9 @@ function collectParameters(root, pathItem, operation) {
|
|
|
48264
48595
|
const param = resolveInternalRef(root, rawParam);
|
|
48265
48596
|
if (!param) continue;
|
|
48266
48597
|
const location2 = String(param.in || "").toLowerCase();
|
|
48267
|
-
if (!["path", "query", "header"].includes(location2)) continue;
|
|
48598
|
+
if (!["path", "query", "header", "cookie"].includes(location2)) continue;
|
|
48268
48599
|
const name = String(param.name || "");
|
|
48269
|
-
if (!name || param.required !== true) continue;
|
|
48600
|
+
if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
|
|
48270
48601
|
const key = `${location2}:${name.toLowerCase()}`;
|
|
48271
48602
|
if (seen.has(key)) continue;
|
|
48272
48603
|
seen.add(key);
|
|
@@ -48278,36 +48609,226 @@ function collectParameters(root, pathItem, operation) {
|
|
|
48278
48609
|
}
|
|
48279
48610
|
return requirements;
|
|
48280
48611
|
}
|
|
48281
|
-
|
|
48612
|
+
var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
|
|
48613
|
+
function isJsonBaseType(base) {
|
|
48614
|
+
return base === "application/json" || /\+json$/.test(base);
|
|
48615
|
+
}
|
|
48616
|
+
function mergeObjectSchema(root, rawSchema, depth) {
|
|
48617
|
+
if (depth > 10) return null;
|
|
48618
|
+
let schema2;
|
|
48619
|
+
try {
|
|
48620
|
+
schema2 = resolveInternalRef(root, rawSchema);
|
|
48621
|
+
} catch {
|
|
48622
|
+
return null;
|
|
48623
|
+
}
|
|
48624
|
+
if (!schema2) return null;
|
|
48625
|
+
const merged = {
|
|
48626
|
+
required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
|
|
48627
|
+
properties: { ...asRecord4(schema2.properties) ?? {} }
|
|
48628
|
+
};
|
|
48629
|
+
for (const member of asArray2(schema2.allOf)) {
|
|
48630
|
+
const child = mergeObjectSchema(root, member, depth + 1);
|
|
48631
|
+
if (!child) continue;
|
|
48632
|
+
merged.required.push(...child.required);
|
|
48633
|
+
merged.properties = { ...merged.properties, ...child.properties };
|
|
48634
|
+
}
|
|
48635
|
+
merged.required = [...new Set(merged.required)];
|
|
48636
|
+
return merged;
|
|
48637
|
+
}
|
|
48638
|
+
function propertyIsReadOnly(root, properties, name) {
|
|
48639
|
+
try {
|
|
48640
|
+
return resolveInternalRef(root, properties[name])?.readOnly === true;
|
|
48641
|
+
} catch {
|
|
48642
|
+
return false;
|
|
48643
|
+
}
|
|
48644
|
+
}
|
|
48645
|
+
function propertyIsBinary(root, properties, name) {
|
|
48646
|
+
let schema2;
|
|
48647
|
+
try {
|
|
48648
|
+
schema2 = resolveInternalRef(root, properties[name]);
|
|
48649
|
+
} catch {
|
|
48650
|
+
return false;
|
|
48651
|
+
}
|
|
48652
|
+
if (!schema2) return false;
|
|
48653
|
+
if (schema2.format === "binary") return true;
|
|
48654
|
+
return typeof schema2.contentMediaType === "string" && schema2.contentMediaType.toLowerCase().startsWith("application/octet-stream");
|
|
48655
|
+
}
|
|
48656
|
+
function fieldEncodings(root, base, mediaObject, properties) {
|
|
48657
|
+
const declared = asRecord4(mediaObject?.encoding);
|
|
48658
|
+
const encodings = {};
|
|
48659
|
+
for (const name of Object.keys(properties)) {
|
|
48660
|
+
if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
|
|
48661
|
+
encodings[name] = { ...encodings[name], binary: true };
|
|
48662
|
+
}
|
|
48663
|
+
}
|
|
48664
|
+
if (declared) {
|
|
48665
|
+
for (const [name, rawEncoding] of Object.entries(declared)) {
|
|
48666
|
+
const encoding = asRecord4(rawEncoding);
|
|
48667
|
+
if (!encoding) continue;
|
|
48668
|
+
const entry = { ...encodings[name] };
|
|
48669
|
+
if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
|
|
48670
|
+
entry.contentType = encoding.contentType.toLowerCase();
|
|
48671
|
+
}
|
|
48672
|
+
if (base === "application/x-www-form-urlencoded") {
|
|
48673
|
+
const style = typeof encoding.style === "string" ? encoding.style : "form";
|
|
48674
|
+
const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
|
|
48675
|
+
if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
|
|
48676
|
+
entry.nonDefaultSerialization = true;
|
|
48677
|
+
}
|
|
48678
|
+
}
|
|
48679
|
+
if (Object.keys(entry).length > 0) encodings[name] = entry;
|
|
48680
|
+
}
|
|
48681
|
+
}
|
|
48682
|
+
return Object.keys(encodings).length > 0 ? encodings : void 0;
|
|
48683
|
+
}
|
|
48684
|
+
function requestBodyFieldRules(root, content) {
|
|
48685
|
+
const rules = {};
|
|
48686
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48687
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48688
|
+
if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
|
|
48689
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48690
|
+
const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
|
|
48691
|
+
if (!merged) continue;
|
|
48692
|
+
const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
|
|
48693
|
+
const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
|
|
48694
|
+
const encodings = BODY_FIELD_RULE_TYPES.has(base) ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
|
|
48695
|
+
if (required.length > 0 || readOnly.length > 0 || encodings) {
|
|
48696
|
+
const rule = { required, readOnly };
|
|
48697
|
+
if (encodings) rule.encodings = encodings;
|
|
48698
|
+
rules[base] = rule;
|
|
48699
|
+
}
|
|
48700
|
+
}
|
|
48701
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
48702
|
+
}
|
|
48703
|
+
function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
|
|
48704
|
+
const schemas = {};
|
|
48705
|
+
const exampleWarnings = /* @__PURE__ */ new Set();
|
|
48706
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48707
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48708
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48709
|
+
const schema2 = mediaRecord?.schema;
|
|
48710
|
+
if (!isJsonBaseType(base)) {
|
|
48711
|
+
if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
|
|
48712
|
+
warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
|
|
48713
|
+
}
|
|
48714
|
+
continue;
|
|
48715
|
+
}
|
|
48716
|
+
if (schema2 === void 0) continue;
|
|
48717
|
+
const packed = packSchema(root, schema2, version, "request");
|
|
48718
|
+
if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
|
|
48719
|
+
if (packed.unsupported) {
|
|
48720
|
+
warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
|
|
48721
|
+
continue;
|
|
48722
|
+
}
|
|
48723
|
+
if (packed.schema !== void 0) schemas[base] = packed.schema;
|
|
48724
|
+
}
|
|
48725
|
+
warnings.push(...exampleWarnings);
|
|
48726
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
48727
|
+
}
|
|
48728
|
+
function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
48282
48729
|
const body = resolveInternalRef(root, operation.requestBody);
|
|
48283
|
-
if (!body
|
|
48730
|
+
if (!body) return void 0;
|
|
48284
48731
|
const content = asRecord4(body.content);
|
|
48732
|
+
const fieldRules = content ? requestBodyFieldRules(root, content) : void 0;
|
|
48733
|
+
if (fieldRules) {
|
|
48734
|
+
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
48735
|
+
for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
|
|
48736
|
+
if (encoding.nonDefaultSerialization) {
|
|
48737
|
+
warnings.push(
|
|
48738
|
+
`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares non-default encoding style, explode, or allowReserved and its serialization is not validated`
|
|
48739
|
+
);
|
|
48740
|
+
}
|
|
48741
|
+
}
|
|
48742
|
+
}
|
|
48743
|
+
}
|
|
48285
48744
|
return {
|
|
48286
|
-
required: true,
|
|
48287
|
-
contentTypes: content ? Object.keys(content) : []
|
|
48745
|
+
required: body.required === true,
|
|
48746
|
+
contentTypes: content ? Object.keys(content) : [],
|
|
48747
|
+
fieldRules,
|
|
48748
|
+
jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
|
|
48288
48749
|
};
|
|
48289
48750
|
}
|
|
48290
|
-
function
|
|
48751
|
+
function exampleCandidates(root, mediaObject) {
|
|
48752
|
+
const candidates = [];
|
|
48753
|
+
if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
|
|
48754
|
+
const examples = asRecord4(mediaObject.examples);
|
|
48755
|
+
if (examples) {
|
|
48756
|
+
for (const [name, rawExample] of Object.entries(examples)) {
|
|
48757
|
+
let example;
|
|
48758
|
+
try {
|
|
48759
|
+
example = resolveInternalRef(root, rawExample);
|
|
48760
|
+
} catch {
|
|
48761
|
+
continue;
|
|
48762
|
+
}
|
|
48763
|
+
if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
|
|
48764
|
+
}
|
|
48765
|
+
}
|
|
48766
|
+
return candidates;
|
|
48767
|
+
}
|
|
48768
|
+
function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
|
|
48769
|
+
if (packed.schema === void 0 || packed.unsupported) return;
|
|
48770
|
+
const candidates = exampleCandidates(root, mediaObject);
|
|
48771
|
+
if (candidates.length === 0) return;
|
|
48772
|
+
const validate2 = compileSchemaValidator(packed.schema);
|
|
48773
|
+
if (!validate2) return;
|
|
48774
|
+
for (const candidate of candidates) {
|
|
48775
|
+
if (!validate2(candidate.value)) {
|
|
48776
|
+
warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
|
|
48777
|
+
}
|
|
48778
|
+
}
|
|
48779
|
+
}
|
|
48780
|
+
function responseContent(root, version, response, context, warnings) {
|
|
48291
48781
|
const content = asRecord4(response.content);
|
|
48292
48782
|
if (!content) return {};
|
|
48293
48783
|
const media = {};
|
|
48294
48784
|
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48295
|
-
const
|
|
48296
|
-
|
|
48785
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48786
|
+
const schema2 = mediaRecord?.schema;
|
|
48787
|
+
let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
|
|
48788
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
48789
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
|
|
48790
|
+
packed = {};
|
|
48791
|
+
}
|
|
48792
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48793
|
+
if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
|
|
48794
|
+
media[contentType] = packed;
|
|
48297
48795
|
}
|
|
48298
48796
|
return media;
|
|
48299
48797
|
}
|
|
48300
|
-
function responseHeaders(root, version, response) {
|
|
48798
|
+
function responseHeaders(root, version, response, context, warnings) {
|
|
48301
48799
|
const headers = asRecord4(response.headers);
|
|
48302
48800
|
if (!headers) return [];
|
|
48303
|
-
|
|
48801
|
+
const entries = [];
|
|
48802
|
+
for (const [name, rawHeader] of Object.entries(headers)) {
|
|
48803
|
+
if (name.toLowerCase() === "content-type") continue;
|
|
48304
48804
|
const header = resolveInternalRef(root, rawHeader);
|
|
48305
|
-
if (!header)
|
|
48805
|
+
if (!header) {
|
|
48806
|
+
entries.push({ name, required: true, unsupported: "Unresolved response header" });
|
|
48807
|
+
continue;
|
|
48808
|
+
}
|
|
48306
48809
|
const required = header.required === true;
|
|
48307
|
-
if (header.content)
|
|
48308
|
-
|
|
48309
|
-
|
|
48310
|
-
|
|
48810
|
+
if (header.content) {
|
|
48811
|
+
entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
|
|
48812
|
+
continue;
|
|
48813
|
+
}
|
|
48814
|
+
if (!header.schema) {
|
|
48815
|
+
entries.push({ name, required });
|
|
48816
|
+
continue;
|
|
48817
|
+
}
|
|
48818
|
+
const packed = packSchema(root, header.schema, version);
|
|
48819
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
48820
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
|
|
48821
|
+
entries.push({ name, required });
|
|
48822
|
+
continue;
|
|
48823
|
+
}
|
|
48824
|
+
if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
|
|
48825
|
+
warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
|
|
48826
|
+
entries.push({ name, required });
|
|
48827
|
+
continue;
|
|
48828
|
+
}
|
|
48829
|
+
entries.push({ name, required, ...packed });
|
|
48830
|
+
}
|
|
48831
|
+
return entries;
|
|
48311
48832
|
}
|
|
48312
48833
|
function buildContractIndex(root) {
|
|
48313
48834
|
if (root.swagger === "2.0") throw new Error("CONTRACT_UNSUPPORTED_OPENAPI_VERSION: Dynamic contract tests require OpenAPI 3.0 or 3.1 (found swagger 2.0)");
|
|
@@ -48332,11 +48853,23 @@ function buildContractIndex(root) {
|
|
|
48332
48853
|
throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path6} must define at least one response`);
|
|
48333
48854
|
}
|
|
48334
48855
|
const contractResponses = {};
|
|
48856
|
+
const responseWarnings = /* @__PURE__ */ new Set();
|
|
48335
48857
|
for (const [status, rawResponse] of Object.entries(responses)) {
|
|
48336
48858
|
const response = resolveInternalRef(root, rawResponse);
|
|
48337
48859
|
if (!response) continue;
|
|
48338
|
-
|
|
48339
|
-
|
|
48860
|
+
if (asRecord4(response.links)) {
|
|
48861
|
+
responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path6}`);
|
|
48862
|
+
}
|
|
48863
|
+
const responseContext = `${lowerMethod.toUpperCase()} ${path6} status ${status}`;
|
|
48864
|
+
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
48865
|
+
for (const [contentType, media] of Object.entries(content)) {
|
|
48866
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48867
|
+
const schemaType = asRecord4(media.schema)?.type;
|
|
48868
|
+
if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
|
|
48869
|
+
responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
|
|
48870
|
+
}
|
|
48871
|
+
}
|
|
48872
|
+
const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
|
|
48340
48873
|
contractResponses[normalizeResponseKey(status)] = {
|
|
48341
48874
|
content,
|
|
48342
48875
|
hasBody: Object.keys(content).length > 0,
|
|
@@ -48348,11 +48881,26 @@ function buildContractIndex(root) {
|
|
|
48348
48881
|
...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path6))
|
|
48349
48882
|
].map(normalizePath))];
|
|
48350
48883
|
const opWarnings = [];
|
|
48884
|
+
opWarnings.push(...responseWarnings);
|
|
48351
48885
|
opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
|
|
48886
|
+
opWarnings.push(...collectSerializationWarnings(root, pathItem, operation));
|
|
48887
|
+
if (operation.deprecated === true) {
|
|
48888
|
+
opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path6} is marked deprecated in the OpenAPI document`);
|
|
48889
|
+
}
|
|
48352
48890
|
const requiredParameters = collectParameters(root, pathItem, operation);
|
|
48353
48891
|
for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
|
|
48354
48892
|
opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
|
|
48355
48893
|
}
|
|
48894
|
+
for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
|
|
48895
|
+
opWarnings.push(`CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not statically required in generated requests`);
|
|
48896
|
+
}
|
|
48897
|
+
const pathParamWarnings = /* @__PURE__ */ new Set();
|
|
48898
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
48899
|
+
if (String(param.in || "").toLowerCase() !== "path") continue;
|
|
48900
|
+
const name = String(param.name || "");
|
|
48901
|
+
if (name) pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
|
|
48902
|
+
}
|
|
48903
|
+
opWarnings.push(...pathParamWarnings);
|
|
48356
48904
|
operations.push({
|
|
48357
48905
|
id: `${lowerMethod.toUpperCase()} ${path6}`,
|
|
48358
48906
|
method: lowerMethod.toUpperCase(),
|
|
@@ -48361,7 +48909,10 @@ function buildContractIndex(root) {
|
|
|
48361
48909
|
candidates,
|
|
48362
48910
|
responses: contractResponses,
|
|
48363
48911
|
requiredParameters,
|
|
48364
|
-
|
|
48912
|
+
declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
|
|
48913
|
+
parameterChecks: collectParameterChecks(root, pathItem, operation, version, `${lowerMethod.toUpperCase()} ${path6}`, opWarnings),
|
|
48914
|
+
requestBody: collectRequestBody(root, operation, version, `${lowerMethod.toUpperCase()} ${path6}`, opWarnings),
|
|
48915
|
+
security: collectSecurityRuntimeChecks(root, operation),
|
|
48365
48916
|
warnings: opWarnings
|
|
48366
48917
|
});
|
|
48367
48918
|
}
|
|
@@ -48382,35 +48933,6 @@ function buildContractIndex(root) {
|
|
|
48382
48933
|
return { operations, version, warnings };
|
|
48383
48934
|
}
|
|
48384
48935
|
|
|
48385
|
-
// src/lib/spec/schema-validator-code.ts
|
|
48386
|
-
var import_schemasafe = __toESM(require_src(), 1);
|
|
48387
|
-
function compileSchemaValidatorCode(schema2) {
|
|
48388
|
-
try {
|
|
48389
|
-
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
48390
|
-
includeErrors: true,
|
|
48391
|
-
allErrors: true,
|
|
48392
|
-
contentValidation: false,
|
|
48393
|
-
formatAssertion: true,
|
|
48394
|
-
isJSON: true,
|
|
48395
|
-
mode: "default",
|
|
48396
|
-
removeAdditional: false,
|
|
48397
|
-
requireSchema: true,
|
|
48398
|
-
requireStringValidation: false,
|
|
48399
|
-
useDefaults: false
|
|
48400
|
-
});
|
|
48401
|
-
const source = validate2.toModule();
|
|
48402
|
-
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
48403
|
-
throw new Error("schemasafe generated forbidden dynamic code");
|
|
48404
|
-
}
|
|
48405
|
-
return source;
|
|
48406
|
-
} catch (error) {
|
|
48407
|
-
throw new Error(
|
|
48408
|
-
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error instanceof Error ? error.message : String(error)}`,
|
|
48409
|
-
{ cause: error }
|
|
48410
|
-
);
|
|
48411
|
-
}
|
|
48412
|
-
}
|
|
48413
|
-
|
|
48414
48936
|
// src/lib/spec/collection-contracts.ts
|
|
48415
48937
|
var CONTRACT_SIZE_LIMITS = {
|
|
48416
48938
|
warnTestScriptBytes: 256e3,
|
|
@@ -48499,29 +49021,51 @@ function matchOperation(index, request) {
|
|
|
48499
49021
|
function assignValidator(lines, target, source) {
|
|
48500
49022
|
lines.push(`${target} = ${source};`);
|
|
48501
49023
|
}
|
|
48502
|
-
function
|
|
49024
|
+
function tryCompile(target, schema2, lines, warnings, context) {
|
|
49025
|
+
try {
|
|
49026
|
+
assignValidator(lines, target, compileSchemaValidatorCode(schema2));
|
|
49027
|
+
} catch (error) {
|
|
49028
|
+
lines.push(`${target} = { skip: true };`);
|
|
49029
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: ${context} could not be compiled into a runtime validator (${error instanceof Error ? error.message.slice(0, 160) : String(error)})`);
|
|
49030
|
+
}
|
|
49031
|
+
}
|
|
49032
|
+
function buildValidatorAssignments(operation, warnings) {
|
|
48503
49033
|
const lines = ["var validators = {};"];
|
|
49034
|
+
const parameterChecks = operation.parameterChecks ?? [];
|
|
49035
|
+
if (parameterChecks.length > 0) {
|
|
49036
|
+
lines.push("var paramValidators = {};");
|
|
49037
|
+
for (const check of parameterChecks) {
|
|
49038
|
+
tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
|
|
49039
|
+
}
|
|
49040
|
+
}
|
|
49041
|
+
const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
|
|
49042
|
+
if (bodySchemas.length > 0) {
|
|
49043
|
+
lines.push("var requestBodyValidators = {};");
|
|
49044
|
+
for (const [base, schema2] of bodySchemas) {
|
|
49045
|
+
tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
|
|
49046
|
+
}
|
|
49047
|
+
}
|
|
48504
49048
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
48505
49049
|
lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
|
|
48506
49050
|
for (const [mediaType, media] of Object.entries(response.content)) {
|
|
48507
49051
|
if (media.schema !== void 0 && !media.unsupported) {
|
|
48508
|
-
|
|
49052
|
+
tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
|
|
48509
49053
|
}
|
|
48510
49054
|
}
|
|
48511
49055
|
for (const header of response.headers) {
|
|
48512
49056
|
if (header.schema !== void 0 && !header.unsupported) {
|
|
48513
49057
|
lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
|
|
48514
|
-
|
|
49058
|
+
tryCompile(`validators[${JSON.stringify(status)}].__headers[${JSON.stringify(header.name.toLowerCase())}]`, header.schema, lines, warnings, `response header ${header.name} schema on ${operation.id} status ${status}`);
|
|
48515
49059
|
}
|
|
48516
49060
|
}
|
|
48517
49061
|
}
|
|
48518
49062
|
return lines;
|
|
48519
49063
|
}
|
|
48520
|
-
function createContractScript(operation) {
|
|
48521
|
-
const contract = { method: operation.method, path: operation.path, responses: operation.responses };
|
|
49064
|
+
function createContractScript(operation, warnings = []) {
|
|
49065
|
+
const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
|
|
48522
49066
|
return [
|
|
48523
49067
|
`var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
|
|
48524
|
-
...buildValidatorAssignments(operation),
|
|
49068
|
+
...buildValidatorAssignments(operation, warnings),
|
|
48525
49069
|
"function selectedResponseContract() {",
|
|
48526
49070
|
" var status = String(pm.response.code);",
|
|
48527
49071
|
" if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
|
|
@@ -48535,6 +49079,15 @@ function createContractScript(operation) {
|
|
|
48535
49079
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
48536
49080
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
48537
49081
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
49082
|
+
"function coerceBySchema(value, schema) {",
|
|
49083
|
+
" var type = schema && schema.type;",
|
|
49084
|
+
" var types = Array.isArray(type) ? type : [type];",
|
|
49085
|
+
' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
|
|
49086
|
+
' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
|
|
49087
|
+
" return value;",
|
|
49088
|
+
"}",
|
|
49089
|
+
'function requestHeader(name) { var value = ""; pm.request.headers.each(function (header) { if (header && header.disabled !== true && String(header.key).toLowerCase() === String(name).toLowerCase()) value = String(header.value); }); return value; }',
|
|
49090
|
+
"function hasQueryParam(name) { var found = false; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === String(name).toLowerCase()) found = true; }); return found; }",
|
|
48538
49091
|
"function mediaScore(expected, actual) {",
|
|
48539
49092
|
" var e = mediaParts(expected); var a = mediaParts(actual);",
|
|
48540
49093
|
" if (!e.raw || !a.raw) return 0;",
|
|
@@ -48565,7 +49118,8 @@ function createContractScript(operation) {
|
|
|
48565
49118
|
" if (!actual) return;",
|
|
48566
49119
|
' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
|
|
48567
49120
|
" var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
|
|
48568
|
-
|
|
49121
|
+
" if (headerValidator && headerValidator.skip) return;",
|
|
49122
|
+
' if (headerValidator && !headerValidator(coerceBySchema(actual, header.schema))) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
|
|
48569
49123
|
" });",
|
|
48570
49124
|
"});",
|
|
48571
49125
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
@@ -48593,11 +49147,75 @@ function createContractScript(operation) {
|
|
|
48593
49147
|
' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
|
|
48594
49148
|
" if (!media.media.schema) { return; }",
|
|
48595
49149
|
" var validate = validators[selected.key] && validators[selected.key][media.expected];",
|
|
49150
|
+
" if (validate && validate.skip) return;",
|
|
48596
49151
|
' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
|
|
48597
49152
|
' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
|
|
48598
49153
|
" var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
|
|
48599
|
-
|
|
49154
|
+
// Non-JSON object-schema bodies skip schema validation instead of
|
|
49155
|
+
// failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
|
|
49156
|
+
// skip is visible at instrumentation time.
|
|
49157
|
+
' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
|
|
48600
49158
|
' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
|
|
49159
|
+
"});",
|
|
49160
|
+
...operation.security ? [
|
|
49161
|
+
"pm.test('Request carries credentials required by OpenAPI security', function () {",
|
|
49162
|
+
" function satisfied(check) {",
|
|
49163
|
+
" if (!check.checkable) return true;",
|
|
49164
|
+
' if (check.in === "query") return hasQueryParam(check.name);',
|
|
49165
|
+
' if (check.in === "cookie") { if (pm.cookies && pm.cookies.has && pm.cookies.has(String(check.name))) return true; return requestHeader("Cookie").split(";").some(function (part) { return part.split("=")[0].trim() === String(check.name); }); }',
|
|
49166
|
+
' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
|
|
49167
|
+
' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
|
|
49168
|
+
" return Boolean(requestHeader(check.name));",
|
|
49169
|
+
" }",
|
|
49170
|
+
" var alternatives = contract.security || [];",
|
|
49171
|
+
" var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
|
|
49172
|
+
' if (!ok) pm.expect.fail("Request did not carry credentials for any OpenAPI security requirement of " + contract.method + " " + contract.path + ": " + alternatives.map(function (alternative) { return alternative.map(function (check) { return check.scheme + " (" + check.kind + ")"; }).join(" + "); }).join(" | "));',
|
|
49173
|
+
"});"
|
|
49174
|
+
] : [],
|
|
49175
|
+
...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
|
|
49176
|
+
"pm.test('Request parameters match OpenAPI schemas', function () {",
|
|
49177
|
+
' function queryValue(name) { var value; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === name) value = param.value === null || param.value === undefined ? "" : String(param.value); }); return value; }',
|
|
49178
|
+
' function headerValue(name) { var value; pm.request.headers.each(function (header) { if (header && header.disabled !== true && String(header.key).toLowerCase() === String(name).toLowerCase()) value = header.value === null || header.value === undefined ? "" : String(header.value); }); return value; }',
|
|
49179
|
+
' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
|
|
49180
|
+
" (contract.parameters || []).forEach(function (param) {",
|
|
49181
|
+
' var key = param.in + ":" + String(param.name).toLowerCase();',
|
|
49182
|
+
" var validate = paramValidators[key];",
|
|
49183
|
+
" if (!validate || validate.skip) return;",
|
|
49184
|
+
' var value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
49185
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
49186
|
+
' if (value === "" && param.allowEmptyValue) return;',
|
|
49187
|
+
" if (isPlaceholder(value)) return;",
|
|
49188
|
+
' if (!validate(coerceBySchema(value, param.schema))) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
49189
|
+
" });",
|
|
49190
|
+
"});"
|
|
49191
|
+
] : [],
|
|
49192
|
+
...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
|
|
49193
|
+
"pm.test('Request body matches OpenAPI request schema', function () {",
|
|
49194
|
+
" var body = pm.request.body;",
|
|
49195
|
+
' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
|
|
49196
|
+
" if (!raw.trim()) return;",
|
|
49197
|
+
' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
|
|
49198
|
+
' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
|
|
49199
|
+
" if (!validate || validate.skip) return;",
|
|
49200
|
+
" var parsed;",
|
|
49201
|
+
// Unquoted generator placeholders such as {"count": <long>} break
|
|
49202
|
+
// JSON.parse; a parse failure alongside an angle-bracket token is
|
|
49203
|
+
// treated as a placeholder body rather than drift.
|
|
49204
|
+
' try { parsed = JSON.parse(raw); } catch (error) { if (/<[A-Za-z][A-Za-z0-9_ -]*>/.test(raw)) return; pm.expect.fail("Request body for " + contract.method + " " + contract.path + " is not valid JSON: " + error); return; }',
|
|
49205
|
+
' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
49206
|
+
"});"
|
|
49207
|
+
] : [],
|
|
49208
|
+
"pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
|
|
49209
|
+
' var raw = pm.response.headers.get("Content-Length");',
|
|
49210
|
+
" if (raw === null || raw === undefined) return;",
|
|
49211
|
+
// Combined duplicate Content-Length values ("5, 5") fail the integer
|
|
49212
|
+
// syntax check on purpose: RFC 9110 tolerates identical repeats, but a
|
|
49213
|
+
// contract test surfacing them is strict by design.
|
|
49214
|
+
' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
|
|
49215
|
+
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
49216
|
+
' if (pm.response.headers.get("Content-Encoding")) return;',
|
|
49217
|
+
" var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
|
|
49218
|
+
' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
48601
49219
|
"});"
|
|
48602
49220
|
];
|
|
48603
49221
|
}
|
|
@@ -48698,6 +49316,87 @@ function assertStaticRequestShape(operation, request) {
|
|
|
48698
49316
|
);
|
|
48699
49317
|
}
|
|
48700
49318
|
}
|
|
49319
|
+
return collectStaticBodyWarnings(operation, request, contentType);
|
|
49320
|
+
}
|
|
49321
|
+
function requestBodyFieldNames(request, base) {
|
|
49322
|
+
const body = asRecord5(request.body);
|
|
49323
|
+
if (!body) return void 0;
|
|
49324
|
+
if (base === "application/json" || /\+json$/.test(base)) {
|
|
49325
|
+
if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
|
|
49326
|
+
let parsed;
|
|
49327
|
+
try {
|
|
49328
|
+
parsed = JSON.parse(body.raw);
|
|
49329
|
+
} catch {
|
|
49330
|
+
return void 0;
|
|
49331
|
+
}
|
|
49332
|
+
const record = asRecord5(parsed);
|
|
49333
|
+
return record ? Object.keys(record) : void 0;
|
|
49334
|
+
}
|
|
49335
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
49336
|
+
if (!mode || !Array.isArray(body[mode])) return void 0;
|
|
49337
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
|
|
49338
|
+
}
|
|
49339
|
+
function requestBodyEntries(request, base) {
|
|
49340
|
+
const body = asRecord5(request.body);
|
|
49341
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
49342
|
+
if (!body || !mode || !Array.isArray(body[mode])) return void 0;
|
|
49343
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
|
|
49344
|
+
}
|
|
49345
|
+
function mediaTypeMatchesPattern(pattern, actual) {
|
|
49346
|
+
return pattern.split(",").some((candidate) => {
|
|
49347
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
49348
|
+
if (!entry) return false;
|
|
49349
|
+
if (entry === "*/*" || entry === actual) return true;
|
|
49350
|
+
if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
|
|
49351
|
+
return false;
|
|
49352
|
+
});
|
|
49353
|
+
}
|
|
49354
|
+
function collectStaticEncodingWarnings(operation, request, base, rule) {
|
|
49355
|
+
const encodings = rule.encodings;
|
|
49356
|
+
if (!encodings || base !== "multipart/form-data") return [];
|
|
49357
|
+
const entries = requestBodyEntries(request, base);
|
|
49358
|
+
if (!entries) return [];
|
|
49359
|
+
const warnings = [];
|
|
49360
|
+
for (const [field, encoding] of Object.entries(encodings)) {
|
|
49361
|
+
const entry = entries.find((candidate) => String(candidate.key || "") === field);
|
|
49362
|
+
if (!entry) continue;
|
|
49363
|
+
if (encoding.binary && String(entry.type || "") !== "file") {
|
|
49364
|
+
warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
|
|
49365
|
+
}
|
|
49366
|
+
if (encoding.contentType) {
|
|
49367
|
+
const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
|
|
49368
|
+
if (!actual) {
|
|
49369
|
+
warnings.push(
|
|
49370
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
|
|
49371
|
+
);
|
|
49372
|
+
} else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
|
|
49373
|
+
warnings.push(
|
|
49374
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
|
|
49375
|
+
);
|
|
49376
|
+
}
|
|
49377
|
+
}
|
|
49378
|
+
}
|
|
49379
|
+
return warnings;
|
|
49380
|
+
}
|
|
49381
|
+
function collectStaticBodyWarnings(operation, request, contentType) {
|
|
49382
|
+
const rules = operation.requestBody?.fieldRules;
|
|
49383
|
+
if (!rules) return [];
|
|
49384
|
+
const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
|
|
49385
|
+
const rule = rules[base];
|
|
49386
|
+
if (!rule) return [];
|
|
49387
|
+
const warnings = [...collectStaticEncodingWarnings(operation, request, base, rule)];
|
|
49388
|
+
const names = requestBodyFieldNames(request, base);
|
|
49389
|
+
if (!names) return warnings;
|
|
49390
|
+
const present = new Set(names);
|
|
49391
|
+
const missing = rule.required.filter((name) => !present.has(name));
|
|
49392
|
+
if (missing.length > 0) {
|
|
49393
|
+
warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
|
|
49394
|
+
}
|
|
49395
|
+
const readOnlySent = rule.readOnly.filter((name) => present.has(name));
|
|
49396
|
+
if (readOnlySent.length > 0) {
|
|
49397
|
+
warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
|
|
49398
|
+
}
|
|
49399
|
+
return warnings;
|
|
48701
49400
|
}
|
|
48702
49401
|
function validateScript(script) {
|
|
48703
49402
|
const source = script.join("\n");
|
|
@@ -48746,9 +49445,12 @@ function instrumentContractCollection(collection, index) {
|
|
|
48746
49445
|
if (result.operation) {
|
|
48747
49446
|
const previous = covered.get(result.operation.id);
|
|
48748
49447
|
if (previous) throw new Error(`CONTRACT_DUPLICATE_OPERATION_REQUEST: ${result.operation.id} matched more than one generated request (${previous}, ${String(item.name || "<unnamed>")})`);
|
|
48749
|
-
assertStaticRequestShape(result.operation, request);
|
|
49448
|
+
warnings.push(...assertStaticRequestShape(result.operation, request));
|
|
49449
|
+
for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
|
|
49450
|
+
warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
|
|
49451
|
+
}
|
|
48750
49452
|
covered.set(result.operation.id, String(item.name || "<unnamed>"));
|
|
48751
|
-
script = createContractScript(result.operation);
|
|
49453
|
+
script = createContractScript(result.operation, warnings);
|
|
48752
49454
|
} else if (result.ambiguous && result.ambiguous.length > 0) {
|
|
48753
49455
|
script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
|
|
48754
49456
|
} else {
|