@postman-cse/onboarding-bootstrap 1.0.0 → 1.2.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 +24 -4
- package/dist/action.cjs +1068 -107
- package/dist/cli.cjs +1068 -107
- package/dist/index.cjs +1068 -107
- 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,74 @@ 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 discriminator = asRecord3(sourceSchema.discriminator);
|
|
48111
|
+
if (discriminator && typeof discriminator.propertyName === "string" && discriminator.propertyName) {
|
|
48112
|
+
const propertyName = discriminator.propertyName;
|
|
48113
|
+
const branchKey = Array.isArray(sourceSchema.oneOf) ? "oneOf" : Array.isArray(sourceSchema.anyOf) ? "anyOf" : "";
|
|
48114
|
+
const members = branchKey ? sourceSchema[branchKey] : [];
|
|
48115
|
+
const memberRefs = members.map((member) => {
|
|
48116
|
+
const memberRecord = asRecord3(member);
|
|
48117
|
+
const memberRef = memberRecord && Object.keys(memberRecord).length === 1 && typeof memberRecord.$ref === "string" ? memberRecord.$ref : "";
|
|
48118
|
+
return memberRef.startsWith("#/") ? memberRef : "";
|
|
48119
|
+
});
|
|
48120
|
+
if (branchKey && members.length > 0 && memberRefs.every(Boolean)) {
|
|
48121
|
+
const valuesByRef = /* @__PURE__ */ new Map();
|
|
48122
|
+
const refByMappingKey = /* @__PURE__ */ new Map();
|
|
48123
|
+
for (const [value, target] of Object.entries(asRecord3(discriminator.mapping) ?? {})) {
|
|
48124
|
+
if (typeof target !== "string" || !target) continue;
|
|
48125
|
+
const targetRef = target.startsWith("#/") ? target : `#/components/schemas/${target}`;
|
|
48126
|
+
valuesByRef.set(targetRef, [...valuesByRef.get(targetRef) ?? [], value]);
|
|
48127
|
+
refByMappingKey.set(value, targetRef);
|
|
48128
|
+
}
|
|
48129
|
+
sourceSchema[branchKey] = members.map((member, index) => {
|
|
48130
|
+
const memberRef = memberRefs[index] ?? "";
|
|
48131
|
+
const tail = (memberRef.split("/").pop() ?? "").replace(/~1/g, "/").replace(/~0/g, "~");
|
|
48132
|
+
const values = [...valuesByRef.get(memberRef) ?? []];
|
|
48133
|
+
const tailClaimedElsewhere = refByMappingKey.has(tail) && refByMappingKey.get(tail) !== memberRef;
|
|
48134
|
+
if (tail && !tailClaimedElsewhere && !values.includes(tail)) values.push(tail);
|
|
48135
|
+
const dispatch = values.length === 1 ? { const: values[0] } : { enum: values };
|
|
48136
|
+
return { allOf: [member, { type: "object", required: [propertyName], properties: { [propertyName]: dispatch } }] };
|
|
48137
|
+
});
|
|
48138
|
+
} else {
|
|
48139
|
+
ctx.notes.add("discriminator");
|
|
48140
|
+
}
|
|
48141
|
+
}
|
|
48142
|
+
const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
|
|
48143
|
+
const strippedProperties = /* @__PURE__ */ new Set();
|
|
48068
48144
|
const rawProperties = asRecord3(sourceSchema.properties);
|
|
48069
48145
|
if (rawProperties) {
|
|
48070
48146
|
for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
|
|
48071
|
-
|
|
48147
|
+
let flagSource = asRecord3(propertySchema);
|
|
48148
|
+
if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
|
|
48149
|
+
flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
|
|
48150
|
+
}
|
|
48151
|
+
if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
|
|
48072
48152
|
}
|
|
48073
48153
|
}
|
|
48074
48154
|
const normalized = {};
|
|
@@ -48095,17 +48175,69 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48095
48175
|
delete normalized.maximum;
|
|
48096
48176
|
continue;
|
|
48097
48177
|
}
|
|
48178
|
+
if (key === "format") {
|
|
48179
|
+
if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
|
|
48180
|
+
continue;
|
|
48181
|
+
}
|
|
48098
48182
|
if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
|
|
48099
|
-
if (
|
|
48183
|
+
if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
|
|
48184
|
+
return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
|
|
48185
|
+
}
|
|
48186
|
+
if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
|
|
48187
|
+
return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
|
|
48188
|
+
}
|
|
48189
|
+
if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
|
|
48100
48190
|
return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
|
|
48101
48191
|
}
|
|
48192
|
+
if (key === "patternProperties" || key === "dependentSchemas") {
|
|
48193
|
+
const map2 = asRecord3(value);
|
|
48194
|
+
if (!map2) continue;
|
|
48195
|
+
const next = {};
|
|
48196
|
+
for (const [mapKey, mapSchema] of Object.entries(map2)) {
|
|
48197
|
+
const child2 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
|
|
48198
|
+
const bad2 = hasUnsupported(child2);
|
|
48199
|
+
if (bad2) return unsupported(bad2);
|
|
48200
|
+
next[mapKey] = child2;
|
|
48201
|
+
}
|
|
48202
|
+
normalized[key] = next;
|
|
48203
|
+
continue;
|
|
48204
|
+
}
|
|
48205
|
+
if (key === "dependentRequired") {
|
|
48206
|
+
const map2 = asRecord3(value);
|
|
48207
|
+
if (!map2) continue;
|
|
48208
|
+
for (const names of Object.values(map2)) {
|
|
48209
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
|
|
48210
|
+
return unsupported("dependentRequired requires string array values");
|
|
48211
|
+
}
|
|
48212
|
+
}
|
|
48213
|
+
normalized.dependentRequired = value;
|
|
48214
|
+
continue;
|
|
48215
|
+
}
|
|
48216
|
+
if (key === "dependencies") {
|
|
48217
|
+
const map2 = asRecord3(value);
|
|
48218
|
+
if (!map2) continue;
|
|
48219
|
+
const next = {};
|
|
48220
|
+
for (const [mapKey, dependency] of Object.entries(map2)) {
|
|
48221
|
+
if (Array.isArray(dependency)) {
|
|
48222
|
+
if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
|
|
48223
|
+
next[mapKey] = dependency;
|
|
48224
|
+
continue;
|
|
48225
|
+
}
|
|
48226
|
+
const child2 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
|
|
48227
|
+
const bad2 = hasUnsupported(child2);
|
|
48228
|
+
if (bad2) return unsupported(bad2);
|
|
48229
|
+
next[mapKey] = child2;
|
|
48230
|
+
}
|
|
48231
|
+
normalized.dependencies = next;
|
|
48232
|
+
continue;
|
|
48233
|
+
}
|
|
48102
48234
|
if (key === "properties") {
|
|
48103
48235
|
const properties = asRecord3(value);
|
|
48104
48236
|
if (!properties) continue;
|
|
48105
48237
|
const nextProperties = {};
|
|
48106
48238
|
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
48107
|
-
if (
|
|
48108
|
-
const child2 = normalizeSchema(
|
|
48239
|
+
if (strippedProperties.has(propertyName)) continue;
|
|
48240
|
+
const child2 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
|
|
48109
48241
|
const bad2 = hasUnsupported(child2);
|
|
48110
48242
|
if (bad2) return unsupported(bad2);
|
|
48111
48243
|
nextProperties[propertyName] = child2;
|
|
@@ -48114,16 +48246,46 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48114
48246
|
continue;
|
|
48115
48247
|
}
|
|
48116
48248
|
if (key === "required") {
|
|
48117
|
-
const required =
|
|
48249
|
+
const required = mergeRequiredWithoutStripped(value, strippedProperties);
|
|
48118
48250
|
if (required) normalized.required = required;
|
|
48119
48251
|
continue;
|
|
48120
48252
|
}
|
|
48121
|
-
const child = normalizeSchema(
|
|
48253
|
+
const child = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
|
|
48122
48254
|
const bad = hasUnsupported(child);
|
|
48123
48255
|
if (bad) return unsupported(bad);
|
|
48124
48256
|
normalized[key] = child;
|
|
48125
48257
|
}
|
|
48126
|
-
if (
|
|
48258
|
+
if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
|
|
48259
|
+
if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
|
|
48260
|
+
else delete normalized.exclusiveMinimum;
|
|
48261
|
+
}
|
|
48262
|
+
if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
|
|
48263
|
+
if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
|
|
48264
|
+
else delete normalized.exclusiveMaximum;
|
|
48265
|
+
}
|
|
48266
|
+
if (sourceSchema.format === "int32") {
|
|
48267
|
+
const type2 = normalized.type;
|
|
48268
|
+
const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
|
|
48269
|
+
if (isInteger2) {
|
|
48270
|
+
if (typeof normalized.exclusiveMinimum === "number") {
|
|
48271
|
+
if (normalized.exclusiveMinimum < INT32_MIN) {
|
|
48272
|
+
delete normalized.exclusiveMinimum;
|
|
48273
|
+
normalized.minimum = INT32_MIN;
|
|
48274
|
+
}
|
|
48275
|
+
} else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
|
|
48276
|
+
normalized.minimum = INT32_MIN;
|
|
48277
|
+
}
|
|
48278
|
+
if (typeof normalized.exclusiveMaximum === "number") {
|
|
48279
|
+
if (normalized.exclusiveMaximum > INT32_MAX) {
|
|
48280
|
+
delete normalized.exclusiveMaximum;
|
|
48281
|
+
normalized.maximum = INT32_MAX;
|
|
48282
|
+
}
|
|
48283
|
+
} else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
|
|
48284
|
+
normalized.maximum = INT32_MAX;
|
|
48285
|
+
}
|
|
48286
|
+
}
|
|
48287
|
+
}
|
|
48288
|
+
if (options.rootSchema) normalized.$schema = ctx.dialect;
|
|
48127
48289
|
if (nullable) {
|
|
48128
48290
|
if (typeof normalized.type === "string") {
|
|
48129
48291
|
normalized.type = [normalized.type, "null"];
|
|
@@ -48135,23 +48297,108 @@ function normalizeSchema(root, schema2, options) {
|
|
|
48135
48297
|
const wrappedSchema = { ...normalized };
|
|
48136
48298
|
delete wrappedSchema.$schema;
|
|
48137
48299
|
const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
|
|
48138
|
-
if (options.rootSchema) wrapper.$schema =
|
|
48300
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
48139
48301
|
return wrapper;
|
|
48140
48302
|
}
|
|
48141
48303
|
}
|
|
48142
48304
|
return normalized;
|
|
48143
48305
|
}
|
|
48144
|
-
function
|
|
48306
|
+
function isSchemaGraphOverflow(packed) {
|
|
48307
|
+
return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
|
|
48308
|
+
}
|
|
48309
|
+
function packSchema(root, schema2, version, direction = "response") {
|
|
48145
48310
|
try {
|
|
48311
|
+
if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
|
|
48312
|
+
if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
|
|
48146
48313
|
const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
|
|
48147
|
-
const
|
|
48314
|
+
const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map(), notes: /* @__PURE__ */ new Set() };
|
|
48315
|
+
const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
|
|
48148
48316
|
const message = hasUnsupported(normalized);
|
|
48149
|
-
|
|
48317
|
+
if (message) return unsupported(message);
|
|
48318
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
48319
|
+
for (const entry of ctx.defs.values()) {
|
|
48320
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
48321
|
+
const entrySchema = asRecord3(entry.schema);
|
|
48322
|
+
const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
|
|
48323
|
+
if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
|
|
48324
|
+
}
|
|
48325
|
+
for (const start of aliasTargets.keys()) {
|
|
48326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48327
|
+
let current = start;
|
|
48328
|
+
while (current !== void 0 && aliasTargets.has(current)) {
|
|
48329
|
+
if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
|
|
48330
|
+
seen.add(current);
|
|
48331
|
+
current = aliasTargets.get(current);
|
|
48332
|
+
}
|
|
48333
|
+
}
|
|
48334
|
+
if (ctx.defs.size > 0) {
|
|
48335
|
+
const normalizedRecord = asRecord3(normalized);
|
|
48336
|
+
if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
|
|
48337
|
+
normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
|
|
48338
|
+
}
|
|
48339
|
+
const packed = { schema: normalized };
|
|
48340
|
+
if (ctx.notes.size > 0) packed.notes = [...ctx.notes].sort();
|
|
48341
|
+
return packed;
|
|
48150
48342
|
} catch (error) {
|
|
48151
48343
|
return unsupported(error instanceof Error ? error.message : String(error));
|
|
48152
48344
|
}
|
|
48153
48345
|
}
|
|
48154
48346
|
|
|
48347
|
+
// src/lib/spec/schema-validator-code.ts
|
|
48348
|
+
var import_schemasafe = __toESM(require_src(), 1);
|
|
48349
|
+
function compileSchemaValidator(schema2) {
|
|
48350
|
+
try {
|
|
48351
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
48352
|
+
includeErrors: false,
|
|
48353
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
48354
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
48355
|
+
// Plaid, and DigitalOcean public specs).
|
|
48356
|
+
allowUnusedKeywords: true,
|
|
48357
|
+
contentValidation: false,
|
|
48358
|
+
formatAssertion: true,
|
|
48359
|
+
isJSON: true,
|
|
48360
|
+
mode: "default",
|
|
48361
|
+
removeAdditional: false,
|
|
48362
|
+
requireSchema: true,
|
|
48363
|
+
requireStringValidation: false,
|
|
48364
|
+
useDefaults: false
|
|
48365
|
+
});
|
|
48366
|
+
return (value) => validate2(value);
|
|
48367
|
+
} catch {
|
|
48368
|
+
return null;
|
|
48369
|
+
}
|
|
48370
|
+
}
|
|
48371
|
+
function compileSchemaValidatorCode(schema2) {
|
|
48372
|
+
try {
|
|
48373
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
48374
|
+
includeErrors: true,
|
|
48375
|
+
allErrors: true,
|
|
48376
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
48377
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
48378
|
+
// Plaid, and DigitalOcean public specs).
|
|
48379
|
+
allowUnusedKeywords: true,
|
|
48380
|
+
contentValidation: false,
|
|
48381
|
+
formatAssertion: true,
|
|
48382
|
+
isJSON: true,
|
|
48383
|
+
mode: "default",
|
|
48384
|
+
removeAdditional: false,
|
|
48385
|
+
requireSchema: true,
|
|
48386
|
+
requireStringValidation: false,
|
|
48387
|
+
useDefaults: false
|
|
48388
|
+
});
|
|
48389
|
+
const source = validate2.toModule();
|
|
48390
|
+
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
48391
|
+
throw new Error("schemasafe generated forbidden dynamic code");
|
|
48392
|
+
}
|
|
48393
|
+
return source;
|
|
48394
|
+
} catch (error) {
|
|
48395
|
+
throw new Error(
|
|
48396
|
+
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error instanceof Error ? error.message : String(error)}`,
|
|
48397
|
+
{ cause: error }
|
|
48398
|
+
);
|
|
48399
|
+
}
|
|
48400
|
+
}
|
|
48401
|
+
|
|
48155
48402
|
// src/lib/spec/contract-index.ts
|
|
48156
48403
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
48157
48404
|
function asRecord4(value) {
|
|
@@ -48227,7 +48474,7 @@ function collectSecurityApiKeys(root, operation) {
|
|
|
48227
48474
|
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
48228
48475
|
for (const schemeName of Object.keys(requirement)) {
|
|
48229
48476
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
48230
|
-
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
|
|
48477
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
|
|
48231
48478
|
names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
|
|
48232
48479
|
}
|
|
48233
48480
|
}
|
|
@@ -48249,12 +48496,197 @@ function collectSecuritySchemeWarnings(root, operation) {
|
|
|
48249
48496
|
for (const schemeName of Object.keys(requirement)) {
|
|
48250
48497
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
48251
48498
|
warnings.add(
|
|
48252
|
-
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
|
|
48499
|
+
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
|
|
48253
48500
|
);
|
|
48254
48501
|
}
|
|
48255
48502
|
}
|
|
48256
48503
|
return [...warnings];
|
|
48257
48504
|
}
|
|
48505
|
+
function securityCheckFor(schemeName, scheme) {
|
|
48506
|
+
const kind = securitySchemeKind(scheme);
|
|
48507
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
|
|
48508
|
+
return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
|
|
48509
|
+
}
|
|
48510
|
+
if (scheme?.type === "http") {
|
|
48511
|
+
const httpScheme = String(scheme.scheme || "").toLowerCase();
|
|
48512
|
+
if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
|
|
48513
|
+
if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
|
|
48514
|
+
if (httpScheme) return { scheme: schemeName, kind, checkable: true, prefix: `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} ` };
|
|
48515
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
48516
|
+
}
|
|
48517
|
+
if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
|
|
48518
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
48519
|
+
}
|
|
48520
|
+
return { scheme: schemeName, kind, checkable: false };
|
|
48521
|
+
}
|
|
48522
|
+
function collectSecurityRuntimeChecks(root, operation) {
|
|
48523
|
+
const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
|
|
48524
|
+
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
48525
|
+
const alternatives = [];
|
|
48526
|
+
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
48527
|
+
const schemeNames = Object.keys(requirement);
|
|
48528
|
+
if (schemeNames.length === 0) return void 0;
|
|
48529
|
+
alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
|
|
48530
|
+
}
|
|
48531
|
+
if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
|
|
48532
|
+
return alternatives.length > 0 ? alternatives : void 0;
|
|
48533
|
+
}
|
|
48534
|
+
function resolvedParameters(root, pathItem, operation) {
|
|
48535
|
+
return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
|
|
48536
|
+
try {
|
|
48537
|
+
return resolveInternalRef(root, rawParam);
|
|
48538
|
+
} catch {
|
|
48539
|
+
return null;
|
|
48540
|
+
}
|
|
48541
|
+
}).filter((param) => Boolean(param));
|
|
48542
|
+
}
|
|
48543
|
+
var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
|
|
48544
|
+
var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
|
|
48545
|
+
function isIgnoredParameter(location2, name) {
|
|
48546
|
+
return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
|
|
48547
|
+
}
|
|
48548
|
+
function jsonContentParameterMedia(param) {
|
|
48549
|
+
const content = asRecord4(param.content);
|
|
48550
|
+
if (!content) return void 0;
|
|
48551
|
+
const entries = Object.entries(content);
|
|
48552
|
+
if (entries.length !== 1) return void 0;
|
|
48553
|
+
const [contentType, mediaObject] = entries[0];
|
|
48554
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48555
|
+
if (!isJsonBaseType(base)) return void 0;
|
|
48556
|
+
const schema2 = asRecord4(mediaObject)?.schema;
|
|
48557
|
+
return schema2 === void 0 ? void 0 : schema2;
|
|
48558
|
+
}
|
|
48559
|
+
function collectSerializationWarnings(root, pathItem, operation, decodedKeys) {
|
|
48560
|
+
const warnings = [];
|
|
48561
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
48562
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
48563
|
+
const name = String(param.name || "");
|
|
48564
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
48565
|
+
if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
|
|
48566
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
48567
|
+
const defaultExplode = style === "form";
|
|
48568
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
48569
|
+
const unvalidatedContent = param.content !== void 0 && (jsonContentParameterMedia(param) === void 0 || location2 !== "query" && location2 !== "header");
|
|
48570
|
+
if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || unvalidatedContent) {
|
|
48571
|
+
if (decodedKeys.has(`${location2}:${name.toLowerCase()}`) && param.allowReserved !== true && param.content === void 0) continue;
|
|
48572
|
+
warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
|
|
48573
|
+
}
|
|
48574
|
+
}
|
|
48575
|
+
return warnings;
|
|
48576
|
+
}
|
|
48577
|
+
var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
48578
|
+
function packedScalarSchema(packed) {
|
|
48579
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
48580
|
+
const record = asRecord4(packed.schema);
|
|
48581
|
+
if (!record) return void 0;
|
|
48582
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
48583
|
+
if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
48584
|
+
return packed.schema;
|
|
48585
|
+
}
|
|
48586
|
+
function packedArrayItemsSchema(packed) {
|
|
48587
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
48588
|
+
const record = asRecord4(packed.schema);
|
|
48589
|
+
if (!record) return void 0;
|
|
48590
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
48591
|
+
if (types2.length !== 1 || types2[0] !== "array") return void 0;
|
|
48592
|
+
if (record.prefixItems !== void 0 || Array.isArray(record.items)) return void 0;
|
|
48593
|
+
if (record.items === void 0) return {};
|
|
48594
|
+
const items = asRecord4(record.items);
|
|
48595
|
+
if (!items || typeof items.$ref === "string") return void 0;
|
|
48596
|
+
const itemTypes = Array.isArray(items.type) ? items.type : [items.type];
|
|
48597
|
+
if (!itemTypes.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
48598
|
+
return items;
|
|
48599
|
+
}
|
|
48600
|
+
var QUERY_ARRAY_DECODES = {
|
|
48601
|
+
"form:true": "multi",
|
|
48602
|
+
"form:false": "csv",
|
|
48603
|
+
"spaceDelimited:false": "ssv",
|
|
48604
|
+
"pipeDelimited:false": "pipes"
|
|
48605
|
+
};
|
|
48606
|
+
function collectParameterChecks(root, pathItem, operation, version, operationId, pathTemplate, warnings) {
|
|
48607
|
+
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
48608
|
+
const checks = [];
|
|
48609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48610
|
+
const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
|
|
48611
|
+
try {
|
|
48612
|
+
return resolveInternalRef(root, rawParam);
|
|
48613
|
+
} catch {
|
|
48614
|
+
return null;
|
|
48615
|
+
}
|
|
48616
|
+
}).filter((param) => Boolean(param));
|
|
48617
|
+
for (const param of orderedParams) {
|
|
48618
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
48619
|
+
if (location2 !== "query" && location2 !== "header" && location2 !== "path" && location2 !== "cookie") continue;
|
|
48620
|
+
const name = String(param.name || "");
|
|
48621
|
+
if (!name || isIgnoredParameter(location2, name)) continue;
|
|
48622
|
+
const key = `${location2}:${name.toLowerCase()}`;
|
|
48623
|
+
if (seen.has(key)) continue;
|
|
48624
|
+
seen.add(key);
|
|
48625
|
+
if (securityKeys.has(key)) continue;
|
|
48626
|
+
const contentMedia = jsonContentParameterMedia(param);
|
|
48627
|
+
if (contentMedia !== void 0 && (location2 === "query" || location2 === "header")) {
|
|
48628
|
+
const packed2 = packSchema(root, contentMedia, version, "request");
|
|
48629
|
+
warnings.push(...packNoteWarnings(packed2, `parameter ${location2}:${name} of ${operationId}`));
|
|
48630
|
+
if (packed2.unsupported) {
|
|
48631
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed2.unsupported})`);
|
|
48632
|
+
} else if (packed2.schema !== void 0) {
|
|
48633
|
+
const check2 = { in: location2, name, required: param.required === true, content: true, schema: packed2.schema };
|
|
48634
|
+
if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
|
|
48635
|
+
checks.push(check2);
|
|
48636
|
+
}
|
|
48637
|
+
continue;
|
|
48638
|
+
}
|
|
48639
|
+
if (param.content !== void 0 || param.schema === void 0) continue;
|
|
48640
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
48641
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
48642
|
+
const defaultExplode = style === "form";
|
|
48643
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
48644
|
+
const defaultSerialization = style === defaultStyle && explode === defaultExplode;
|
|
48645
|
+
const packed = packSchema(root, param.schema, version);
|
|
48646
|
+
const noteWarnings = packNoteWarnings(packed, `parameter ${location2}:${name} of ${operationId}`);
|
|
48647
|
+
if (defaultSerialization) warnings.push(...noteWarnings);
|
|
48648
|
+
if (packed.unsupported) {
|
|
48649
|
+
if (defaultSerialization) warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
|
|
48650
|
+
continue;
|
|
48651
|
+
}
|
|
48652
|
+
if (location2 === "path" && !pathTemplate.split("/").includes(`{${name}}`)) continue;
|
|
48653
|
+
const scalarSchema = packedScalarSchema(packed);
|
|
48654
|
+
if (scalarSchema !== void 0) {
|
|
48655
|
+
if (!defaultSerialization) continue;
|
|
48656
|
+
const check2 = { in: location2, name, required: param.required === true, schema: scalarSchema };
|
|
48657
|
+
if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
|
|
48658
|
+
checks.push(check2);
|
|
48659
|
+
continue;
|
|
48660
|
+
}
|
|
48661
|
+
if (location2 !== "query" && location2 !== "header") continue;
|
|
48662
|
+
const items = packedArrayItemsSchema(packed);
|
|
48663
|
+
if (items === void 0) continue;
|
|
48664
|
+
const decode = location2 === "query" ? QUERY_ARRAY_DECODES[`${style}:${explode}`] : style === "simple" && !explode ? "csv" : void 0;
|
|
48665
|
+
if (!decode) continue;
|
|
48666
|
+
if (!defaultSerialization) warnings.push(...noteWarnings);
|
|
48667
|
+
const check = { in: location2, name, required: param.required === true, schema: packed.schema, decode, items };
|
|
48668
|
+
if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
|
|
48669
|
+
checks.push(check);
|
|
48670
|
+
}
|
|
48671
|
+
return checks.length > 0 ? checks : void 0;
|
|
48672
|
+
}
|
|
48673
|
+
function packNoteWarnings(packed, context) {
|
|
48674
|
+
return (packed.notes ?? []).map(
|
|
48675
|
+
(note) => note === "discriminator" ? `CONTRACT_DISCRIMINATOR_NOT_VALIDATED: discriminator on ${context} has no sibling oneOf/anyOf of internal $ref members and is not validated` : `CONTRACT_SCHEMA_NOT_COMPILED: ${note} on ${context} is not validated`
|
|
48676
|
+
);
|
|
48677
|
+
}
|
|
48678
|
+
function collectDeclaredQueryParameters(root, pathItem, operation) {
|
|
48679
|
+
const names = /* @__PURE__ */ new Set();
|
|
48680
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
48681
|
+
if (String(param.in || "").toLowerCase() !== "query") continue;
|
|
48682
|
+
const name = String(param.name || "");
|
|
48683
|
+
if (name) names.add(name.toLowerCase());
|
|
48684
|
+
}
|
|
48685
|
+
for (const key of collectSecurityApiKeys(root, operation)) {
|
|
48686
|
+
if (key.startsWith("query:")) names.add(key.slice("query:".length));
|
|
48687
|
+
}
|
|
48688
|
+
return [...names];
|
|
48689
|
+
}
|
|
48258
48690
|
function collectParameters(root, pathItem, operation) {
|
|
48259
48691
|
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
48260
48692
|
const requirements = [];
|
|
@@ -48264,9 +48696,9 @@ function collectParameters(root, pathItem, operation) {
|
|
|
48264
48696
|
const param = resolveInternalRef(root, rawParam);
|
|
48265
48697
|
if (!param) continue;
|
|
48266
48698
|
const location2 = String(param.in || "").toLowerCase();
|
|
48267
|
-
if (!["path", "query", "header"].includes(location2)) continue;
|
|
48699
|
+
if (!["path", "query", "header", "cookie"].includes(location2)) continue;
|
|
48268
48700
|
const name = String(param.name || "");
|
|
48269
|
-
if (!name || param.required !== true) continue;
|
|
48701
|
+
if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
|
|
48270
48702
|
const key = `${location2}:${name.toLowerCase()}`;
|
|
48271
48703
|
if (seen.has(key)) continue;
|
|
48272
48704
|
seen.add(key);
|
|
@@ -48278,36 +48710,263 @@ function collectParameters(root, pathItem, operation) {
|
|
|
48278
48710
|
}
|
|
48279
48711
|
return requirements;
|
|
48280
48712
|
}
|
|
48281
|
-
|
|
48713
|
+
var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
|
|
48714
|
+
function isJsonBaseType(base) {
|
|
48715
|
+
return base === "application/json" || /\+json$/.test(base);
|
|
48716
|
+
}
|
|
48717
|
+
function mergeObjectSchema(root, rawSchema, depth) {
|
|
48718
|
+
if (depth > 10) return null;
|
|
48719
|
+
let schema2;
|
|
48720
|
+
try {
|
|
48721
|
+
schema2 = resolveInternalRef(root, rawSchema);
|
|
48722
|
+
} catch {
|
|
48723
|
+
return null;
|
|
48724
|
+
}
|
|
48725
|
+
if (!schema2) return null;
|
|
48726
|
+
const merged = {
|
|
48727
|
+
required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
|
|
48728
|
+
properties: { ...asRecord4(schema2.properties) ?? {} }
|
|
48729
|
+
};
|
|
48730
|
+
for (const member of asArray2(schema2.allOf)) {
|
|
48731
|
+
const child = mergeObjectSchema(root, member, depth + 1);
|
|
48732
|
+
if (!child) continue;
|
|
48733
|
+
merged.required.push(...child.required);
|
|
48734
|
+
merged.properties = { ...merged.properties, ...child.properties };
|
|
48735
|
+
}
|
|
48736
|
+
merged.required = [...new Set(merged.required)];
|
|
48737
|
+
return merged;
|
|
48738
|
+
}
|
|
48739
|
+
function propertyIsReadOnly(root, properties, name) {
|
|
48740
|
+
try {
|
|
48741
|
+
return resolveInternalRef(root, properties[name])?.readOnly === true;
|
|
48742
|
+
} catch {
|
|
48743
|
+
return false;
|
|
48744
|
+
}
|
|
48745
|
+
}
|
|
48746
|
+
function propertyIsBinary(root, properties, name) {
|
|
48747
|
+
let schema2;
|
|
48748
|
+
try {
|
|
48749
|
+
schema2 = resolveInternalRef(root, properties[name]);
|
|
48750
|
+
} catch {
|
|
48751
|
+
return false;
|
|
48752
|
+
}
|
|
48753
|
+
if (!schema2) return false;
|
|
48754
|
+
if (schema2.format === "binary") return true;
|
|
48755
|
+
if (typeof schema2.contentMediaType !== "string" || typeof schema2.contentEncoding === "string") return false;
|
|
48756
|
+
const media = schema2.contentMediaType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48757
|
+
if (!media) return false;
|
|
48758
|
+
return media !== "application/json" && !media.endsWith("+json") && !media.startsWith("text/");
|
|
48759
|
+
}
|
|
48760
|
+
function fieldEncodings(root, base, mediaObject, properties) {
|
|
48761
|
+
const declared = asRecord4(mediaObject?.encoding);
|
|
48762
|
+
const encodings = {};
|
|
48763
|
+
for (const name of Object.keys(properties)) {
|
|
48764
|
+
if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
|
|
48765
|
+
encodings[name] = { ...encodings[name], binary: true };
|
|
48766
|
+
}
|
|
48767
|
+
}
|
|
48768
|
+
if (declared) {
|
|
48769
|
+
for (const [name, rawEncoding] of Object.entries(declared)) {
|
|
48770
|
+
const encoding = asRecord4(rawEncoding);
|
|
48771
|
+
if (!encoding) continue;
|
|
48772
|
+
const entry = { ...encodings[name] };
|
|
48773
|
+
if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
|
|
48774
|
+
entry.contentType = encoding.contentType.toLowerCase();
|
|
48775
|
+
}
|
|
48776
|
+
if (base === "multipart/form-data" && asRecord4(encoding.headers)) {
|
|
48777
|
+
entry.hasHeaders = true;
|
|
48778
|
+
}
|
|
48779
|
+
if (base === "application/x-www-form-urlencoded") {
|
|
48780
|
+
const style = typeof encoding.style === "string" ? encoding.style : "form";
|
|
48781
|
+
const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
|
|
48782
|
+
if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
|
|
48783
|
+
entry.nonDefaultSerialization = true;
|
|
48784
|
+
}
|
|
48785
|
+
}
|
|
48786
|
+
if (Object.keys(entry).length > 0) encodings[name] = entry;
|
|
48787
|
+
}
|
|
48788
|
+
}
|
|
48789
|
+
return Object.keys(encodings).length > 0 ? encodings : void 0;
|
|
48790
|
+
}
|
|
48791
|
+
function formFieldSchemas(root, version, properties, context, warnings) {
|
|
48792
|
+
const schemas = {};
|
|
48793
|
+
for (const name of Object.keys(properties)) {
|
|
48794
|
+
const packed = packSchema(root, properties[name], version, "request");
|
|
48795
|
+
warnings.push(...packNoteWarnings(packed, `field ${name} of ${context}`));
|
|
48796
|
+
if (packed.unsupported) {
|
|
48797
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: field ${name} of ${context} skipped (${packed.unsupported})`);
|
|
48798
|
+
continue;
|
|
48799
|
+
}
|
|
48800
|
+
if (packed.schema === void 0) continue;
|
|
48801
|
+
const schema2 = packedScalarSchema(packed);
|
|
48802
|
+
if (schema2 !== void 0) schemas[name] = schema2;
|
|
48803
|
+
}
|
|
48804
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
48805
|
+
}
|
|
48806
|
+
function requestBodyFieldRules(root, content, version, operationId, warnings) {
|
|
48807
|
+
const rules = {};
|
|
48808
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48809
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48810
|
+
if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
|
|
48811
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48812
|
+
const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
|
|
48813
|
+
if (!merged) continue;
|
|
48814
|
+
const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
|
|
48815
|
+
const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
|
|
48816
|
+
const formRules = BODY_FIELD_RULE_TYPES.has(base);
|
|
48817
|
+
const encodings = formRules ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
|
|
48818
|
+
const fieldSchemas = formRules ? formFieldSchemas(root, version, merged.properties, `request body ${contentType} of ${operationId}`, warnings) : void 0;
|
|
48819
|
+
if (required.length > 0 || readOnly.length > 0 || encodings || fieldSchemas) {
|
|
48820
|
+
const rule = { required, readOnly };
|
|
48821
|
+
if (encodings) rule.encodings = encodings;
|
|
48822
|
+
if (fieldSchemas) rule.fieldSchemas = fieldSchemas;
|
|
48823
|
+
rules[base] = rule;
|
|
48824
|
+
}
|
|
48825
|
+
}
|
|
48826
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
48827
|
+
}
|
|
48828
|
+
function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
|
|
48829
|
+
const schemas = {};
|
|
48830
|
+
const exampleWarnings = /* @__PURE__ */ new Set();
|
|
48831
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48832
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48833
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48834
|
+
const schema2 = mediaRecord?.schema;
|
|
48835
|
+
if (!isJsonBaseType(base)) {
|
|
48836
|
+
if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
|
|
48837
|
+
warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
|
|
48838
|
+
}
|
|
48839
|
+
continue;
|
|
48840
|
+
}
|
|
48841
|
+
if (schema2 === void 0) continue;
|
|
48842
|
+
const packed = packSchema(root, schema2, version, "request");
|
|
48843
|
+
warnings.push(...packNoteWarnings(packed, `request body ${contentType} of ${operationId}`));
|
|
48844
|
+
if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
|
|
48845
|
+
if (packed.unsupported) {
|
|
48846
|
+
warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
|
|
48847
|
+
continue;
|
|
48848
|
+
}
|
|
48849
|
+
if (packed.schema !== void 0) schemas[base] = packed.schema;
|
|
48850
|
+
}
|
|
48851
|
+
warnings.push(...exampleWarnings);
|
|
48852
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
48853
|
+
}
|
|
48854
|
+
function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
48282
48855
|
const body = resolveInternalRef(root, operation.requestBody);
|
|
48283
|
-
if (!body
|
|
48856
|
+
if (!body) return void 0;
|
|
48284
48857
|
const content = asRecord4(body.content);
|
|
48858
|
+
const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
|
|
48859
|
+
if (fieldRules) {
|
|
48860
|
+
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
48861
|
+
for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
|
|
48862
|
+
if (encoding.nonDefaultSerialization) {
|
|
48863
|
+
warnings.push(
|
|
48864
|
+
`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`
|
|
48865
|
+
);
|
|
48866
|
+
}
|
|
48867
|
+
if (encoding.hasHeaders) {
|
|
48868
|
+
warnings.push(
|
|
48869
|
+
`CONTRACT_ENCODING_HEADERS_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares per-part headers that generated formdata entries cannot carry`
|
|
48870
|
+
);
|
|
48871
|
+
}
|
|
48872
|
+
}
|
|
48873
|
+
}
|
|
48874
|
+
}
|
|
48285
48875
|
return {
|
|
48286
|
-
required: true,
|
|
48287
|
-
contentTypes: content ? Object.keys(content) : []
|
|
48876
|
+
required: body.required === true,
|
|
48877
|
+
contentTypes: content ? Object.keys(content) : [],
|
|
48878
|
+
fieldRules,
|
|
48879
|
+
jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
|
|
48288
48880
|
};
|
|
48289
48881
|
}
|
|
48290
|
-
function
|
|
48882
|
+
function exampleCandidates(root, mediaObject) {
|
|
48883
|
+
const candidates = [];
|
|
48884
|
+
if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
|
|
48885
|
+
const examples = asRecord4(mediaObject.examples);
|
|
48886
|
+
if (examples) {
|
|
48887
|
+
for (const [name, rawExample] of Object.entries(examples)) {
|
|
48888
|
+
let example;
|
|
48889
|
+
try {
|
|
48890
|
+
example = resolveInternalRef(root, rawExample);
|
|
48891
|
+
} catch {
|
|
48892
|
+
continue;
|
|
48893
|
+
}
|
|
48894
|
+
if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
|
|
48895
|
+
}
|
|
48896
|
+
}
|
|
48897
|
+
return candidates;
|
|
48898
|
+
}
|
|
48899
|
+
function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
|
|
48900
|
+
if (packed.schema === void 0 || packed.unsupported) return;
|
|
48901
|
+
const candidates = exampleCandidates(root, mediaObject);
|
|
48902
|
+
if (candidates.length === 0) return;
|
|
48903
|
+
const validate2 = compileSchemaValidator(packed.schema);
|
|
48904
|
+
if (!validate2) return;
|
|
48905
|
+
for (const candidate of candidates) {
|
|
48906
|
+
if (!validate2(candidate.value)) {
|
|
48907
|
+
warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
|
|
48908
|
+
}
|
|
48909
|
+
}
|
|
48910
|
+
}
|
|
48911
|
+
function responseContent(root, version, response, context, warnings) {
|
|
48291
48912
|
const content = asRecord4(response.content);
|
|
48292
48913
|
if (!content) return {};
|
|
48293
48914
|
const media = {};
|
|
48294
48915
|
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
48295
|
-
const
|
|
48296
|
-
|
|
48916
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
48917
|
+
const schema2 = mediaRecord?.schema;
|
|
48918
|
+
let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
|
|
48919
|
+
for (const warning of packNoteWarnings(packed, `response ${contentType} of ${context}`)) warnings.add(warning);
|
|
48920
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
48921
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
|
|
48922
|
+
packed = {};
|
|
48923
|
+
}
|
|
48924
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
48925
|
+
if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
|
|
48926
|
+
media[contentType] = packed;
|
|
48297
48927
|
}
|
|
48298
48928
|
return media;
|
|
48299
48929
|
}
|
|
48300
|
-
function responseHeaders(root, version, response) {
|
|
48930
|
+
function responseHeaders(root, version, response, context, warnings) {
|
|
48301
48931
|
const headers = asRecord4(response.headers);
|
|
48302
48932
|
if (!headers) return [];
|
|
48303
|
-
|
|
48933
|
+
const entries = [];
|
|
48934
|
+
for (const [name, rawHeader] of Object.entries(headers)) {
|
|
48935
|
+
if (name.toLowerCase() === "content-type") continue;
|
|
48304
48936
|
const header = resolveInternalRef(root, rawHeader);
|
|
48305
|
-
if (!header)
|
|
48937
|
+
if (!header) {
|
|
48938
|
+
entries.push({ name, required: true, unsupported: "Unresolved response header" });
|
|
48939
|
+
continue;
|
|
48940
|
+
}
|
|
48306
48941
|
const required = header.required === true;
|
|
48307
|
-
if (header.content)
|
|
48308
|
-
|
|
48309
|
-
|
|
48310
|
-
|
|
48942
|
+
if (header.content) {
|
|
48943
|
+
entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
|
|
48944
|
+
continue;
|
|
48945
|
+
}
|
|
48946
|
+
if (!header.schema) {
|
|
48947
|
+
entries.push({ name, required });
|
|
48948
|
+
continue;
|
|
48949
|
+
}
|
|
48950
|
+
const packed = packSchema(root, header.schema, version);
|
|
48951
|
+
for (const warning of packNoteWarnings(packed, `response header ${name} of ${context}`)) warnings.add(warning);
|
|
48952
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
48953
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
|
|
48954
|
+
entries.push({ name, required });
|
|
48955
|
+
continue;
|
|
48956
|
+
}
|
|
48957
|
+
if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
|
|
48958
|
+
const items = packedArrayItemsSchema(packed);
|
|
48959
|
+
if (items !== void 0) {
|
|
48960
|
+
entries.push({ name, required, schema: packed.schema, items });
|
|
48961
|
+
continue;
|
|
48962
|
+
}
|
|
48963
|
+
warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
|
|
48964
|
+
entries.push({ name, required });
|
|
48965
|
+
continue;
|
|
48966
|
+
}
|
|
48967
|
+
entries.push({ name, required, ...packed });
|
|
48968
|
+
}
|
|
48969
|
+
return entries;
|
|
48311
48970
|
}
|
|
48312
48971
|
function buildContractIndex(root) {
|
|
48313
48972
|
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 +48991,23 @@ function buildContractIndex(root) {
|
|
|
48332
48991
|
throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path6} must define at least one response`);
|
|
48333
48992
|
}
|
|
48334
48993
|
const contractResponses = {};
|
|
48994
|
+
const responseWarnings = /* @__PURE__ */ new Set();
|
|
48335
48995
|
for (const [status, rawResponse] of Object.entries(responses)) {
|
|
48336
48996
|
const response = resolveInternalRef(root, rawResponse);
|
|
48337
48997
|
if (!response) continue;
|
|
48338
|
-
|
|
48339
|
-
|
|
48998
|
+
if (asRecord4(response.links)) {
|
|
48999
|
+
responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path6}`);
|
|
49000
|
+
}
|
|
49001
|
+
const responseContext = `${lowerMethod.toUpperCase()} ${path6} status ${status}`;
|
|
49002
|
+
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
49003
|
+
for (const [contentType, media] of Object.entries(content)) {
|
|
49004
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
49005
|
+
const schemaType = asRecord4(media.schema)?.type;
|
|
49006
|
+
if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
|
|
49007
|
+
responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
|
|
49008
|
+
}
|
|
49009
|
+
}
|
|
49010
|
+
const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
|
|
48340
49011
|
contractResponses[normalizeResponseKey(status)] = {
|
|
48341
49012
|
content,
|
|
48342
49013
|
hasBody: Object.keys(content).length > 0,
|
|
@@ -48347,21 +49018,45 @@ function buildContractIndex(root) {
|
|
|
48347
49018
|
path6,
|
|
48348
49019
|
...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path6))
|
|
48349
49020
|
].map(normalizePath))];
|
|
49021
|
+
const operationId = `${lowerMethod.toUpperCase()} ${path6}`;
|
|
48350
49022
|
const opWarnings = [];
|
|
49023
|
+
opWarnings.push(...responseWarnings);
|
|
48351
49024
|
opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
|
|
49025
|
+
const parameterChecks = collectParameterChecks(root, pathItem, operation, version, operationId, path6, opWarnings);
|
|
49026
|
+
const checkedKeys = new Set((parameterChecks ?? []).map((check) => `${check.in}:${check.name.toLowerCase()}`));
|
|
49027
|
+
const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode).map((check) => `${check.in}:${check.name.toLowerCase()}`));
|
|
49028
|
+
opWarnings.push(...collectSerializationWarnings(root, pathItem, operation, decodedKeys));
|
|
49029
|
+
if (operation.deprecated === true) {
|
|
49030
|
+
opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path6} is marked deprecated in the OpenAPI document`);
|
|
49031
|
+
}
|
|
48352
49032
|
const requiredParameters = collectParameters(root, pathItem, operation);
|
|
48353
49033
|
for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
|
|
48354
49034
|
opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
|
|
48355
49035
|
}
|
|
49036
|
+
for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
|
|
49037
|
+
opWarnings.push(checkedKeys.has(`cookie:${parameter.name.toLowerCase()}`) ? `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests; the runtime test fails until the cookie is supplied at send time` : `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests and its value is not runtime-validated`);
|
|
49038
|
+
}
|
|
49039
|
+
const pathParamWarnings = /* @__PURE__ */ new Set();
|
|
49040
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
49041
|
+
if (String(param.in || "").toLowerCase() !== "path") continue;
|
|
49042
|
+
const name = String(param.name || "");
|
|
49043
|
+
if (name && !checkedKeys.has(`path:${name.toLowerCase()}`)) {
|
|
49044
|
+
pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
|
|
49045
|
+
}
|
|
49046
|
+
}
|
|
49047
|
+
opWarnings.push(...pathParamWarnings);
|
|
48356
49048
|
operations.push({
|
|
48357
|
-
id:
|
|
49049
|
+
id: operationId,
|
|
48358
49050
|
method: lowerMethod.toUpperCase(),
|
|
48359
49051
|
path: path6,
|
|
48360
49052
|
pointer: `/paths/${path6.replace(/~/g, "~0").replace(/\//g, "~1")}/${lowerMethod}`,
|
|
48361
49053
|
candidates,
|
|
48362
49054
|
responses: contractResponses,
|
|
48363
49055
|
requiredParameters,
|
|
48364
|
-
|
|
49056
|
+
declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
|
|
49057
|
+
parameterChecks,
|
|
49058
|
+
requestBody: collectRequestBody(root, operation, version, operationId, opWarnings),
|
|
49059
|
+
security: collectSecurityRuntimeChecks(root, operation),
|
|
48365
49060
|
warnings: opWarnings
|
|
48366
49061
|
});
|
|
48367
49062
|
}
|
|
@@ -48382,35 +49077,6 @@ function buildContractIndex(root) {
|
|
|
48382
49077
|
return { operations, version, warnings };
|
|
48383
49078
|
}
|
|
48384
49079
|
|
|
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
49080
|
// src/lib/spec/collection-contracts.ts
|
|
48415
49081
|
var CONTRACT_SIZE_LIMITS = {
|
|
48416
49082
|
warnTestScriptBytes: 256e3,
|
|
@@ -48499,29 +49165,51 @@ function matchOperation(index, request) {
|
|
|
48499
49165
|
function assignValidator(lines, target, source) {
|
|
48500
49166
|
lines.push(`${target} = ${source};`);
|
|
48501
49167
|
}
|
|
48502
|
-
function
|
|
49168
|
+
function tryCompile(target, schema2, lines, warnings, context) {
|
|
49169
|
+
try {
|
|
49170
|
+
assignValidator(lines, target, compileSchemaValidatorCode(schema2));
|
|
49171
|
+
} catch (error) {
|
|
49172
|
+
lines.push(`${target} = { skip: true };`);
|
|
49173
|
+
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)})`);
|
|
49174
|
+
}
|
|
49175
|
+
}
|
|
49176
|
+
function buildValidatorAssignments(operation, warnings) {
|
|
48503
49177
|
const lines = ["var validators = {};"];
|
|
49178
|
+
const parameterChecks = operation.parameterChecks ?? [];
|
|
49179
|
+
if (parameterChecks.length > 0) {
|
|
49180
|
+
lines.push("var paramValidators = {};");
|
|
49181
|
+
for (const check of parameterChecks) {
|
|
49182
|
+
tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
|
|
49183
|
+
}
|
|
49184
|
+
}
|
|
49185
|
+
const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
|
|
49186
|
+
if (bodySchemas.length > 0) {
|
|
49187
|
+
lines.push("var requestBodyValidators = {};");
|
|
49188
|
+
for (const [base, schema2] of bodySchemas) {
|
|
49189
|
+
tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
|
|
49190
|
+
}
|
|
49191
|
+
}
|
|
48504
49192
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
48505
49193
|
lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
|
|
48506
49194
|
for (const [mediaType, media] of Object.entries(response.content)) {
|
|
48507
49195
|
if (media.schema !== void 0 && !media.unsupported) {
|
|
48508
|
-
|
|
49196
|
+
tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
|
|
48509
49197
|
}
|
|
48510
49198
|
}
|
|
48511
49199
|
for (const header of response.headers) {
|
|
48512
49200
|
if (header.schema !== void 0 && !header.unsupported) {
|
|
48513
49201
|
lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
|
|
48514
|
-
|
|
49202
|
+
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
49203
|
}
|
|
48516
49204
|
}
|
|
48517
49205
|
}
|
|
48518
49206
|
return lines;
|
|
48519
49207
|
}
|
|
48520
|
-
function createContractScript(operation) {
|
|
48521
|
-
const contract = { method: operation.method, path: operation.path, responses: operation.responses };
|
|
49208
|
+
function createContractScript(operation, warnings = []) {
|
|
49209
|
+
const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
|
|
48522
49210
|
return [
|
|
48523
49211
|
`var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
|
|
48524
|
-
...buildValidatorAssignments(operation),
|
|
49212
|
+
...buildValidatorAssignments(operation, warnings),
|
|
48525
49213
|
"function selectedResponseContract() {",
|
|
48526
49214
|
" var status = String(pm.response.code);",
|
|
48527
49215
|
" if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
|
|
@@ -48535,6 +49223,15 @@ function createContractScript(operation) {
|
|
|
48535
49223
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
48536
49224
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
48537
49225
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
49226
|
+
"function coerceBySchema(value, schema) {",
|
|
49227
|
+
" var type = schema && schema.type;",
|
|
49228
|
+
" var types = Array.isArray(type) ? type : [type];",
|
|
49229
|
+
' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
|
|
49230
|
+
' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
|
|
49231
|
+
" return value;",
|
|
49232
|
+
"}",
|
|
49233
|
+
'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; }',
|
|
49234
|
+
"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
49235
|
"function mediaScore(expected, actual) {",
|
|
48539
49236
|
" var e = mediaParts(expected); var a = mediaParts(actual);",
|
|
48540
49237
|
" if (!e.raw || !a.raw) return 0;",
|
|
@@ -48565,7 +49262,11 @@ function createContractScript(operation) {
|
|
|
48565
49262
|
" if (!actual) return;",
|
|
48566
49263
|
' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
|
|
48567
49264
|
" var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
|
|
48568
|
-
|
|
49265
|
+
" if (!headerValidator || headerValidator.skip) return;",
|
|
49266
|
+
" var expected;",
|
|
49267
|
+
' if (header.items) { var joined = String(actual).trim(); expected = joined === "" ? [] : joined.split(",").map(function (entry) { return coerceBySchema(entry.trim(), header.items); }); }',
|
|
49268
|
+
" else expected = coerceBySchema(actual, header.schema);",
|
|
49269
|
+
' if (!headerValidator(expected)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
|
|
48569
49270
|
" });",
|
|
48570
49271
|
"});",
|
|
48571
49272
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
@@ -48593,11 +49294,122 @@ function createContractScript(operation) {
|
|
|
48593
49294
|
' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
|
|
48594
49295
|
" if (!media.media.schema) { return; }",
|
|
48595
49296
|
" var validate = validators[selected.key] && validators[selected.key][media.expected];",
|
|
49297
|
+
" if (validate && validate.skip) return;",
|
|
48596
49298
|
' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
|
|
48597
49299
|
' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
|
|
48598
49300
|
" var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
|
|
48599
|
-
|
|
49301
|
+
// Non-JSON object-schema bodies skip schema validation instead of
|
|
49302
|
+
// failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
|
|
49303
|
+
// skip is visible at instrumentation time.
|
|
49304
|
+
' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
|
|
48600
49305
|
' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
|
|
49306
|
+
"});",
|
|
49307
|
+
...operation.security ? [
|
|
49308
|
+
"pm.test('Request carries credentials required by OpenAPI security', function () {",
|
|
49309
|
+
" function satisfied(check) {",
|
|
49310
|
+
" if (!check.checkable) return true;",
|
|
49311
|
+
' if (check.in === "query") return hasQueryParam(check.name);',
|
|
49312
|
+
' 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); }); }',
|
|
49313
|
+
' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
|
|
49314
|
+
' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
|
|
49315
|
+
" return Boolean(requestHeader(check.name));",
|
|
49316
|
+
" }",
|
|
49317
|
+
" var alternatives = contract.security || [];",
|
|
49318
|
+
" var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
|
|
49319
|
+
' 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(" | "));',
|
|
49320
|
+
"});"
|
|
49321
|
+
] : [],
|
|
49322
|
+
...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
|
|
49323
|
+
"pm.test('Request parameters match OpenAPI schemas', function () {",
|
|
49324
|
+
' 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; }',
|
|
49325
|
+
' function queryValues(name) { var values = []; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === name) values.push(param.value === null || param.value === undefined ? "" : String(param.value)); }); return values; }',
|
|
49326
|
+
' 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; }',
|
|
49327
|
+
' function cookieValue(name) { try { if (pm.cookies && pm.cookies.get) { var jar = pm.cookies.get(String(name)); if (jar !== null && jar !== undefined) return String(jar); } } catch (ignored) {} var raw = requestHeader("Cookie"); if (!raw) return undefined; var found; raw.split(";").forEach(function (part) { var split = part.indexOf("="); if (split === -1) return; if (part.slice(0, split).trim() === String(name)) found = part.slice(split + 1).trim(); }); return found; }',
|
|
49328
|
+
' function requestPathSegments() { var raw = ""; try { raw = typeof pm.request.url.getPath === "function" ? String(pm.request.url.getPath() || "") : ""; } catch (ignored) {} if (!raw) { var path = pm.request.url.path; raw = Array.isArray(path) ? "/" + path.join("/") : String(path || ""); } return raw.split("?")[0].split("#")[0].split("/").filter(function (segment) { return segment.length > 0; }); }',
|
|
49329
|
+
// Server path prefixes sit ahead of the template segments, so the
|
|
49330
|
+
// template aligns against the trailing request segments.
|
|
49331
|
+
' function pathParamValue(name) { var template = String(contract.path).split("/").filter(function (segment) { return segment.length > 0; }); var actual = requestPathSegments(); var offset = actual.length - template.length; if (offset < 0) return undefined; for (var i = 0; i < template.length; i += 1) { if (template[i] === "{" + name + "}") { var segment = actual[offset + i]; try { return decodeURIComponent(segment); } catch (ignored) { return segment; } } } return undefined; }',
|
|
49332
|
+
' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
|
|
49333
|
+
' function splitDelimited(value, decode) { if (decode === "csv") return value.split(","); if (decode === "ssv") return value.split(/%20| /); return value.split(/%7C|\\|/i); }',
|
|
49334
|
+
" function decodeComponent(value) { try { return decodeURIComponent(value); } catch (ignored) { return value; } }",
|
|
49335
|
+
" (contract.parameters || []).forEach(function (param) {",
|
|
49336
|
+
' var key = param.in + ":" + String(param.name).toLowerCase();',
|
|
49337
|
+
" var validate = paramValidators[key];",
|
|
49338
|
+
" if (!validate || validate.skip) return;",
|
|
49339
|
+
" var value;",
|
|
49340
|
+
' if (param.decode === "multi") {',
|
|
49341
|
+
" var entries = queryValues(String(param.name).toLowerCase());",
|
|
49342
|
+
' if (entries.length === 0) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
49343
|
+
' if (param.allowEmptyValue && entries.length === 1 && entries[0] === "") return;',
|
|
49344
|
+
" if (entries.some(isPlaceholder)) return;",
|
|
49345
|
+
" value = entries.map(function (entry) { return coerceBySchema(decodeComponent(entry), param.items); });",
|
|
49346
|
+
" } else if (param.decode) {",
|
|
49347
|
+
' var joined = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
49348
|
+
' if (joined === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
49349
|
+
' if (joined === "" && param.allowEmptyValue) return;',
|
|
49350
|
+
" if (isPlaceholder(joined)) return;",
|
|
49351
|
+
' var parts = joined === "" ? [] : splitDelimited(String(joined), param.decode);',
|
|
49352
|
+
// HTTP header lists allow optional whitespace after the comma, so
|
|
49353
|
+
// header-sourced items are trimmed; query values stay literal.
|
|
49354
|
+
' if (param.in === "header") parts = parts.map(function (entry) { return entry.trim(); });',
|
|
49355
|
+
" if (parts.some(isPlaceholder)) return;",
|
|
49356
|
+
// Query values arrive percent-encoded while the server validates the
|
|
49357
|
+
// decoded form; header values are never percent-encoded.
|
|
49358
|
+
' value = parts.map(function (entry) { return coerceBySchema(param.in === "query" ? decodeComponent(entry) : entry, param.items); });',
|
|
49359
|
+
' } else if (param.in === "path") {',
|
|
49360
|
+
" value = pathParamValue(param.name);",
|
|
49361
|
+
" if (value === undefined) return;",
|
|
49362
|
+
' if (isPlaceholder(value) || value.charAt(0) === ":" || value.charAt(0) === "{") return;',
|
|
49363
|
+
" value = coerceBySchema(value, param.schema);",
|
|
49364
|
+
' } else if (param.in === "cookie") {',
|
|
49365
|
+
" value = cookieValue(param.name);",
|
|
49366
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required cookie parameter " + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
49367
|
+
" if (isPlaceholder(value)) return;",
|
|
49368
|
+
" value = coerceBySchema(value, param.schema);",
|
|
49369
|
+
" } else {",
|
|
49370
|
+
' value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
49371
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
49372
|
+
' if (value === "" && param.allowEmptyValue) return;',
|
|
49373
|
+
" if (isPlaceholder(value)) return;",
|
|
49374
|
+
" if (param.content) {",
|
|
49375
|
+
" var parsed;",
|
|
49376
|
+
' try { parsed = JSON.parse(value); } catch (parseError) { pm.expect.fail("Parameter " + param.in + ":" + param.name + " declares JSON content but its value is not parseable JSON for " + contract.method + " " + contract.path); return; }',
|
|
49377
|
+
' if (!validate(parsed)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
49378
|
+
" return;",
|
|
49379
|
+
" }",
|
|
49380
|
+
" value = coerceBySchema(value, param.schema);",
|
|
49381
|
+
" }",
|
|
49382
|
+
' if (!validate(value)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
49383
|
+
" });",
|
|
49384
|
+
"});"
|
|
49385
|
+
] : [],
|
|
49386
|
+
...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
|
|
49387
|
+
"pm.test('Request body matches OpenAPI request schema', function () {",
|
|
49388
|
+
" var body = pm.request.body;",
|
|
49389
|
+
' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
|
|
49390
|
+
" if (!raw.trim()) return;",
|
|
49391
|
+
' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
|
|
49392
|
+
' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
|
|
49393
|
+
" if (!validate || validate.skip) return;",
|
|
49394
|
+
" var parsed;",
|
|
49395
|
+
// Unquoted generator placeholders such as {"count": <long>} break
|
|
49396
|
+
// JSON.parse; a parse failure alongside an angle-bracket token is
|
|
49397
|
+
// treated as a placeholder body rather than drift.
|
|
49398
|
+
' 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; }',
|
|
49399
|
+
' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
49400
|
+
"});"
|
|
49401
|
+
] : [],
|
|
49402
|
+
"pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
|
|
49403
|
+
' var raw = pm.response.headers.get("Content-Length");',
|
|
49404
|
+
" if (raw === null || raw === undefined) return;",
|
|
49405
|
+
// Combined duplicate Content-Length values ("5, 5") fail the integer
|
|
49406
|
+
// syntax check on purpose: RFC 9110 tolerates identical repeats, but a
|
|
49407
|
+
// contract test surfacing them is strict by design.
|
|
49408
|
+
' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
|
|
49409
|
+
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
49410
|
+
' if (pm.response.headers.get("Content-Encoding")) return;',
|
|
49411
|
+
" var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
|
|
49412
|
+
' 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
49413
|
"});"
|
|
48602
49414
|
];
|
|
48603
49415
|
}
|
|
@@ -48690,14 +49502,160 @@ function assertStaticRequestShape(operation, request) {
|
|
|
48690
49502
|
if (!contentType) {
|
|
48691
49503
|
throw new Error(`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} missing required request Content-Type`);
|
|
48692
49504
|
}
|
|
48693
|
-
const actual = contentType.toLowerCase().split(";")[0]?.trim();
|
|
48694
|
-
const matches = operation.requestBody.contentTypes.some((expected) => expected.toLowerCase()
|
|
49505
|
+
const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
49506
|
+
const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
|
|
48695
49507
|
if (!matches) {
|
|
48696
49508
|
throw new Error(
|
|
48697
49509
|
`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} request Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
|
|
48698
49510
|
);
|
|
48699
49511
|
}
|
|
48700
49512
|
}
|
|
49513
|
+
const warnings = collectStaticBodyWarnings(operation, request, contentType);
|
|
49514
|
+
if (operation.requestBody && !operation.requestBody.required && operation.requestBody.contentTypes.length > 0 && hasRequestBody(request) && contentType) {
|
|
49515
|
+
const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
49516
|
+
const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
|
|
49517
|
+
if (!matches) {
|
|
49518
|
+
warnings.push(
|
|
49519
|
+
`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} optional request body Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
|
|
49520
|
+
);
|
|
49521
|
+
}
|
|
49522
|
+
}
|
|
49523
|
+
return warnings;
|
|
49524
|
+
}
|
|
49525
|
+
function requestBodyFieldNames(request, base) {
|
|
49526
|
+
const body = asRecord5(request.body);
|
|
49527
|
+
if (!body) return void 0;
|
|
49528
|
+
if (base === "application/json" || /\+json$/.test(base)) {
|
|
49529
|
+
if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
|
|
49530
|
+
let parsed;
|
|
49531
|
+
try {
|
|
49532
|
+
parsed = JSON.parse(body.raw);
|
|
49533
|
+
} catch {
|
|
49534
|
+
return void 0;
|
|
49535
|
+
}
|
|
49536
|
+
const record = asRecord5(parsed);
|
|
49537
|
+
return record ? Object.keys(record) : void 0;
|
|
49538
|
+
}
|
|
49539
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
49540
|
+
if (!mode || !Array.isArray(body[mode])) return void 0;
|
|
49541
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
|
|
49542
|
+
}
|
|
49543
|
+
function requestBodyEntries(request, base) {
|
|
49544
|
+
const body = asRecord5(request.body);
|
|
49545
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
49546
|
+
if (!body || !mode || !Array.isArray(body[mode])) return void 0;
|
|
49547
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
|
|
49548
|
+
}
|
|
49549
|
+
function mediaTypeMatchesPattern(pattern, actual) {
|
|
49550
|
+
return pattern.split(",").some((candidate) => {
|
|
49551
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
49552
|
+
if (!entry) return false;
|
|
49553
|
+
if (entry === "*/*" || entry === actual) return true;
|
|
49554
|
+
if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
|
|
49555
|
+
return false;
|
|
49556
|
+
});
|
|
49557
|
+
}
|
|
49558
|
+
function isJsonEncodingContentType(declared) {
|
|
49559
|
+
return declared.split(",").some((candidate) => {
|
|
49560
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
49561
|
+
return entry === "application/json" || entry.endsWith("+json");
|
|
49562
|
+
});
|
|
49563
|
+
}
|
|
49564
|
+
function isPlaceholderValue(value) {
|
|
49565
|
+
return /^<[^>]*>$/.test(value.trim()) || value.includes("{{");
|
|
49566
|
+
}
|
|
49567
|
+
function collectStaticEncodingWarnings(operation, request, base, rule) {
|
|
49568
|
+
const encodings = rule.encodings;
|
|
49569
|
+
if (!encodings) return [];
|
|
49570
|
+
const multipart = base === "multipart/form-data";
|
|
49571
|
+
const entries = requestBodyEntries(request, base);
|
|
49572
|
+
if (!entries) return [];
|
|
49573
|
+
const warnings = [];
|
|
49574
|
+
const part = multipart ? "multipart" : "urlencoded";
|
|
49575
|
+
for (const [field, encoding] of Object.entries(encodings)) {
|
|
49576
|
+
for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
|
|
49577
|
+
if (multipart && encoding.binary && String(entry.type || "") !== "file") {
|
|
49578
|
+
warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
|
|
49579
|
+
}
|
|
49580
|
+
if (multipart && encoding.contentType) {
|
|
49581
|
+
const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
|
|
49582
|
+
if (!actual) {
|
|
49583
|
+
warnings.push(
|
|
49584
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
|
|
49585
|
+
);
|
|
49586
|
+
} else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
|
|
49587
|
+
warnings.push(
|
|
49588
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
|
|
49589
|
+
);
|
|
49590
|
+
}
|
|
49591
|
+
}
|
|
49592
|
+
if (encoding.contentType && isJsonEncodingContentType(encoding.contentType) && String(entry.type || "text") !== "file") {
|
|
49593
|
+
const value = typeof entry.value === "string" ? entry.value : "";
|
|
49594
|
+
if (value.trim() && !isPlaceholderValue(value)) {
|
|
49595
|
+
try {
|
|
49596
|
+
JSON.parse(value);
|
|
49597
|
+
} catch {
|
|
49598
|
+
warnings.push(
|
|
49599
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated ${part} field ${field} declares JSON encoding ${encoding.contentType} but its value is not parseable JSON`
|
|
49600
|
+
);
|
|
49601
|
+
}
|
|
49602
|
+
}
|
|
49603
|
+
}
|
|
49604
|
+
}
|
|
49605
|
+
}
|
|
49606
|
+
return warnings;
|
|
49607
|
+
}
|
|
49608
|
+
function coerceFormValue(value, schema2) {
|
|
49609
|
+
const record = asRecord5(schema2);
|
|
49610
|
+
const type2 = record?.type;
|
|
49611
|
+
const types2 = Array.isArray(type2) ? type2 : [type2];
|
|
49612
|
+
if ((types2.includes("integer") || types2.includes("number")) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(value.trim())) return Number(value);
|
|
49613
|
+
if (types2.includes("boolean") && (value === "true" || value === "false")) return value === "true";
|
|
49614
|
+
return value;
|
|
49615
|
+
}
|
|
49616
|
+
function collectStaticFieldSchemaWarnings(operation, request, base, rule) {
|
|
49617
|
+
const fieldSchemas = rule.fieldSchemas;
|
|
49618
|
+
if (!fieldSchemas) return [];
|
|
49619
|
+
const entries = requestBodyEntries(request, base);
|
|
49620
|
+
if (!entries) return [];
|
|
49621
|
+
const part = base === "multipart/form-data" ? "multipart" : "urlencoded";
|
|
49622
|
+
const warnings = [];
|
|
49623
|
+
for (const [field, schema2] of Object.entries(fieldSchemas)) {
|
|
49624
|
+
const validate2 = compileSchemaValidator(schema2);
|
|
49625
|
+
if (!validate2) continue;
|
|
49626
|
+
for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
|
|
49627
|
+
if (String(entry.type || "text") === "file") continue;
|
|
49628
|
+
const value = typeof entry.value === "string" ? entry.value : "";
|
|
49629
|
+
if (!value.trim() || isPlaceholderValue(value)) continue;
|
|
49630
|
+
if (!validate2(coerceFormValue(value, schema2))) {
|
|
49631
|
+
warnings.push(`CONTRACT_FORM_FIELD_SCHEMA_MISMATCH: ${operation.id} generated ${part} field ${field} value does not match its schema`);
|
|
49632
|
+
}
|
|
49633
|
+
}
|
|
49634
|
+
}
|
|
49635
|
+
return warnings;
|
|
49636
|
+
}
|
|
49637
|
+
function collectStaticBodyWarnings(operation, request, contentType) {
|
|
49638
|
+
const rules = operation.requestBody?.fieldRules;
|
|
49639
|
+
if (!rules) return [];
|
|
49640
|
+
const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
|
|
49641
|
+
const rule = rules[base];
|
|
49642
|
+
if (!rule) return [];
|
|
49643
|
+
const warnings = [
|
|
49644
|
+
...collectStaticEncodingWarnings(operation, request, base, rule),
|
|
49645
|
+
...collectStaticFieldSchemaWarnings(operation, request, base, rule)
|
|
49646
|
+
];
|
|
49647
|
+
const names = requestBodyFieldNames(request, base);
|
|
49648
|
+
if (!names) return warnings;
|
|
49649
|
+
const present = new Set(names);
|
|
49650
|
+
const missing = rule.required.filter((name) => !present.has(name));
|
|
49651
|
+
if (missing.length > 0) {
|
|
49652
|
+
warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
|
|
49653
|
+
}
|
|
49654
|
+
const readOnlySent = rule.readOnly.filter((name) => present.has(name));
|
|
49655
|
+
if (readOnlySent.length > 0) {
|
|
49656
|
+
warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
|
|
49657
|
+
}
|
|
49658
|
+
return warnings;
|
|
48701
49659
|
}
|
|
48702
49660
|
function validateScript(script) {
|
|
48703
49661
|
const source = script.join("\n");
|
|
@@ -48746,9 +49704,12 @@ function instrumentContractCollection(collection, index) {
|
|
|
48746
49704
|
if (result.operation) {
|
|
48747
49705
|
const previous = covered.get(result.operation.id);
|
|
48748
49706
|
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);
|
|
49707
|
+
warnings.push(...assertStaticRequestShape(result.operation, request));
|
|
49708
|
+
for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
|
|
49709
|
+
warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
|
|
49710
|
+
}
|
|
48750
49711
|
covered.set(result.operation.id, String(item.name || "<unnamed>"));
|
|
48751
|
-
script = createContractScript(result.operation);
|
|
49712
|
+
script = createContractScript(result.operation, warnings);
|
|
48752
49713
|
} else if (result.ambiguous && result.ambiguous.length > 0) {
|
|
48753
49714
|
script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
|
|
48754
49715
|
} else {
|