@orval/mock 8.28.1 → 8.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { EnumGeneration, OutputMockType, OutputMode, PropertySortOrder, camelPathParamName, compareNatural, compareVersions, escapeRegExp, generalJSTypesWithArray, generateDependencyImports, getKey, getOperationTagKey, getRefInfo, getRequiredKeys, isBoolean, isFunction, isMswMock, isNumber, isObject, isReference, isString, jsStringLiteralEscape, mergeDeep, pascal, resolveRef, stringify, toColonRoutePath } from "@orval/core";
1
+ import { EnumGeneration, OutputMockType, OutputMode, PropertySortOrder, camelPathParamName, compareNatural, compareVersions, escapeRegExp, generalJSTypesWithArray, generateDependencyImports, getKey, getOperationTagKey, getRefInfo, getRequiredKeys, isBoolean, isFunction, isMswMock, isNumber, isObject, isReference, isString, jsStringLiteralEscape, mergeDeep, pascal, resolveRef, safeNumericConstraint, stringify, toColonRoutePath } from "@orval/core";
2
2
  import { prop } from "remeda";
3
3
  //#region src/mock-types.ts
4
4
  function isStrictMock(mockOptions) {
@@ -911,18 +911,20 @@ function getMockScalar({ item, imports, mockOptions, operationId, tags, combine,
911
911
  case "number":
912
912
  case "integer": {
913
913
  const intFunction = context.output.override.useBigInt && (item.format === "int64" || item.format === "uint64") ? "bigInt" : "int";
914
- const numMin = typeof item.exclusiveMinimum === "number" ? item.exclusiveMinimum : item.minimum ?? safeMockOptions.numberMin;
915
- const numMax = typeof item.exclusiveMaximum === "number" ? item.exclusiveMaximum : item.maximum ?? safeMockOptions.numberMax;
914
+ const specMin = safeNumericConstraint(item.minimum, "minimum");
915
+ const specMax = safeNumericConstraint(item.maximum, "maximum");
916
+ const numMin = typeof item.exclusiveMinimum === "number" ? safeNumericConstraint(item.exclusiveMinimum, "exclusiveMinimum") : specMin ?? safeMockOptions.numberMin;
917
+ const numMax = typeof item.exclusiveMaximum === "number" ? safeNumericConstraint(item.exclusiveMaximum, "exclusiveMaximum") : specMax ?? safeMockOptions.numberMax;
916
918
  const intParts = [];
917
919
  if (numMin !== void 0) intParts.push(`min: ${numMin}`);
918
920
  if (numMax !== void 0) intParts.push(`max: ${numMax}`);
919
- if (isFakerV9 && item.multipleOf !== void 0) intParts.push(`multipleOf: ${item.multipleOf}`);
921
+ if (isFakerV9 && item.multipleOf !== void 0) intParts.push(`multipleOf: ${safeNumericConstraint(item.multipleOf, "multipleOf")}`);
920
922
  let value = getNullable(`faker.number.${intFunction}(${intParts.length > 0 ? `{${intParts.join(", ")}}` : ""})`, isNullable, nonNullableOption);
921
923
  if (type === "number") {
922
924
  const floatParts = [];
923
925
  if (numMin !== void 0) floatParts.push(`min: ${numMin}`);
924
926
  if (numMax !== void 0) floatParts.push(`max: ${numMax}`);
925
- if (isFakerV9 && item.multipleOf !== void 0) floatParts.push(`multipleOf: ${item.multipleOf}`);
927
+ if (isFakerV9 && item.multipleOf !== void 0) floatParts.push(`multipleOf: ${safeNumericConstraint(item.multipleOf, "multipleOf")}`);
926
928
  else if (safeMockOptions.fractionDigits !== void 0) floatParts.push(`fractionDigits: ${safeMockOptions.fractionDigits}`);
927
929
  value = getNullable(`faker.number.float(${floatParts.length > 0 ? `{${floatParts.join(", ")}}` : ""})`, isNullable, nonNullableOption);
928
930
  }
@@ -1027,8 +1029,8 @@ function getMockScalar({ item, imports, mockOptions, operationId, tags, combine,
1027
1029
  });
1028
1030
  if (extractedItemCall) mapValue = extractedItemCall;
1029
1031
  if (combine && !value.startsWith("faker") && !value.startsWith("{") && !value.startsWith("Array.from")) mapValue = `{${value}}`;
1030
- const arrSchemaMin = item.minItems;
1031
- const arrSchemaMax = item.maxItems;
1032
+ const arrSchemaMin = safeNumericConstraint(item.minItems, "minItems");
1033
+ const arrSchemaMax = safeNumericConstraint(item.maxItems, "maxItems");
1032
1034
  const arrGlobalMin = safeMockOptions.arrayMin;
1033
1035
  const arrGlobalMax = safeMockOptions.arrayMax;
1034
1036
  let arrMin;
@@ -1051,8 +1053,8 @@ function getMockScalar({ item, imports, mockOptions, operationId, tags, combine,
1051
1053
  };
1052
1054
  }
1053
1055
  case "string": {
1054
- const schemaMin = item.minLength;
1055
- const schemaMax = item.maxLength;
1056
+ const schemaMin = safeNumericConstraint(item.minLength, "minLength");
1057
+ const schemaMax = safeNumericConstraint(item.maxLength, "maxLength");
1056
1058
  const globalMin = safeMockOptions.stringMin;
1057
1059
  const globalMax = safeMockOptions.stringMax;
1058
1060
  let strMin;
@@ -1137,9 +1139,23 @@ function getItemType(item) {
1137
1139
  if (!type) return;
1138
1140
  return ["string", "number"].includes(type) ? type : void 0;
1139
1141
  }
1142
+ /**
1143
+ * Renders one enum member as a literal for the generated faker array.
1144
+ *
1145
+ * The branch is on the member's own type, never on the schema's declared
1146
+ * `type`: both come from the document and nothing makes them agree. Keying off
1147
+ * the declared type meant a string member under `type: 'integer'` was spliced
1148
+ * in as a live expression, a numeric member under `type: 'string'` crashed the
1149
+ * generator, and an object member emitted `[object Object]`.
1150
+ */
1151
+ function formatEnumMember(value) {
1152
+ if (isString(value)) return `'${jsStringLiteralEscape(value)}'`;
1153
+ if (isNumber(value) || isBoolean(value)) return String(value);
1154
+ return JSON.stringify(value);
1155
+ }
1140
1156
  function getEnum(item, imports, context, existingReferencedProperties, type) {
1141
1157
  if (!item.enum) return "";
1142
- let enumValue = `[${item.enum.filter((e) => e !== null).map((e) => type === "string" || type === void 0 && isString(e) ? `'${jsStringLiteralEscape(e)}'` : e).join(",")}]`;
1158
+ let enumValue = `[${item.enum.filter((e) => e !== null).map((e) => formatEnumMember(e)).join(",")}]`;
1143
1159
  if (context.output.override.enumGenerationType === EnumGeneration.ENUM) {
1144
1160
  const isRootSchema = !item.parentName && existingReferencedProperties.at(-1) === item.name;
1145
1161
  if (item.isRef || existingReferencedProperties.length === 0 || isRootSchema) {
@@ -1153,7 +1169,7 @@ function getEnum(item, imports, context, existingReferencedProperties, type) {
1153
1169
  imports.push({ name: parentReference });
1154
1170
  }
1155
1171
  } else enumValue += " as const";
1156
- if (item.isRef && type === "string" && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
1172
+ if (item.isRef && type === "string" && item.enum.every((e) => isString(e)) && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
1157
1173
  enumValue = `Object.values(${item.name})`;
1158
1174
  imports.push({
1159
1175
  name: item.name,
@@ -1531,7 +1547,7 @@ function combineSchemasMock({ item, separator, mockOptions, operationId, tags, c
1531
1547
  }
1532
1548
  //#endregion
1533
1549
  //#region src/faker/getters/route.ts
1534
- const getRouteMSW = (route, baseUrl = "*") => `${baseUrl}${toColonRoutePath(route.replaceAll(":", String.raw`\\:`), camelPathParamName)}`;
1550
+ const getRouteMSW = (route, baseUrl = "*") => `${jsStringLiteralEscape(baseUrl)}${toColonRoutePath(jsStringLiteralEscape(route).replaceAll(":", String.raw`\\:`), camelPathParamName)}`;
1535
1551
  //#endregion
1536
1552
  //#region src/msw/mocks.ts
1537
1553
  function getMockPropertiesWithoutFunc(properties, spec) {
@@ -1649,7 +1665,7 @@ function getResponsesMockDefinition({ operationId, tags, returnType, responses,
1649
1665
  };
1650
1666
  else if (!originalSchema) continue;
1651
1667
  const resolvedSchema = resolveRef(originalSchema, context).schema;
1652
- const responseImports = imports ?? [];
1668
+ const responseImports = imports ? [...imports] : [];
1653
1669
  const importsBefore = responseImports.length;
1654
1670
  const scalar = getMockScalar({
1655
1671
  item: {
@@ -1697,6 +1713,24 @@ function getMockOptionsDataOverride(operationTags, operationId, override) {
1697
1713
  }
1698
1714
  //#endregion
1699
1715
  //#region src/msw/index.ts
1716
+ /**
1717
+ * Resolves an OpenAPI response key to the numeric status the generated handler
1718
+ * emits.
1719
+ *
1720
+ * The key comes straight from the document and lands in an unquoted expression
1721
+ * position (`{ status: 200 }`), so there is no quote to escape and no way to
1722
+ * make a non-numeric value safe there — a key like
1723
+ * `2,x:(<expression>)` would splice live code into the handler. Parse it, and
1724
+ * refuse the document rather than emitting whatever it says.
1725
+ *
1726
+ * `default` maps to 200 and a `NXX` wildcard to `N00`, matching what the
1727
+ * handler previously emitted for those keys.
1728
+ */
1729
+ function assertSafeStatusCode(status) {
1730
+ const normalized = status === "default" ? "200" : /^\dXX$/.test(status) ? `${status[0]}00` : status;
1731
+ if (!/^\d{3}$/.test(normalized)) throw new Error(`orval: refusing to generate a mock handler for an OpenAPI response key that is not a status code (got "${status}"). This value would otherwise be emitted verbatim into generated source.`);
1732
+ return Number(normalized);
1733
+ }
1700
1734
  function getMSWDependencies(options) {
1701
1735
  const locale = options?.locale;
1702
1736
  const fakerDependency = {
@@ -1775,7 +1809,7 @@ function generateDefinition(name, route, getResponseMockFunctionNameBase, handle
1775
1809
  const binaryTypeRewriteRegex = new RegExp(String.raw`\b(?:${["Blob", ...binaryRefNames].map((n) => escapeRegExp(n)).join("|")})\b`, "g");
1776
1810
  const mockReturnType = isBinaryResponse ? returnType.replaceAll(binaryTypeRewriteRegex, "ArrayBuffer") : returnType;
1777
1811
  const isVoidUnionType = mockReturnType !== "void" && mockReturnType.split("|").some((part) => part.trim() === "void");
1778
- const noContentStatusCode = isVoidUnionType ? responses.find((r) => r.value === "void")?.key ?? "204" : void 0;
1812
+ const noContentStatusCode = isVoidUnionType ? assertSafeStatusCode(responses.find((r) => r.value === "void")?.key ?? "204") : void 0;
1779
1813
  const nonVoidMockReturnType = isVoidUnionType ? mockReturnType.split("|").filter((part) => part.trim() !== "void").join(" | ").trim() : mockReturnType;
1780
1814
  const hasJsonContentType = contentTypesByPreference.some((ct) => ct.includes("json") || ct.includes("+json"));
1781
1815
  const hasStringReturnType = isTypeExactlyString(mockReturnType) || isUnionContainingString(mockReturnType);
@@ -1810,14 +1844,14 @@ function generateDefinition(name, route, getResponseMockFunctionNameBase, handle
1810
1844
  const resolvedResponseExpr = `overrideResponse !== undefined
1811
1845
  ? (typeof overrideResponse === "function" ? await overrideResponse(${infoParam}) : overrideResponse)
1812
1846
  : ${getResponseMockFunctionName}()`;
1813
- const statusCode = status === "default" ? 200 : status.replace(/XX$/, "00");
1847
+ const statusCode = assertSafeStatusCode(status);
1814
1848
  const binaryContentType = (preferredContentTypeMatch && isBinaryLikeContentType(preferredContentTypeMatch) ? preferredContentTypeMatch : contentTypes.find((ct) => isBinaryLikeContentType(ct))) ?? "application/octet-stream";
1815
1849
  const firstTextCt = isExactlyStringReturnType && !!preferredContentTypeMatch && !isTextLikeContentType(preferredContentTypeMatch) && hasTextLikeContentType ? contentTypes.find((ct) => isTextLikeContentType(ct)) : contentTypesByPreference.find((ct) => isTextLikeContentType(ct));
1816
1850
  const textHelper = firstTextCt === "application/xml" || firstTextCt?.endsWith("+xml") ? "xml" : firstTextCt === "text/html" ? "html" : "text";
1817
1851
  const firstJsonCt = contentTypesByPreference.find((ct) => ct.includes("json"));
1818
1852
  const textHelperDefaultContentType = textHelper === "xml" ? "text/xml" : textHelper === "html" ? "text/html" : "text/plain";
1819
- const jsonCtHeaderSuffix = firstJsonCt && firstJsonCt !== "application/json" ? `, headers: { 'Content-Type': '${firstJsonCt}' }` : preferredContentTypeMatch && !preferredContentTypeMatch.includes("json") ? `, headers: { 'Content-Type': '${preferredContentTypeMatch}' }` : "";
1820
- const textCtHeaderSuffix = firstTextCt && firstTextCt !== textHelperDefaultContentType ? `, headers: { 'Content-Type': '${firstTextCt}' }` : "";
1853
+ const jsonCtHeaderSuffix = firstJsonCt && firstJsonCt !== "application/json" ? `, headers: { 'Content-Type': '${jsStringLiteralEscape(firstJsonCt)}' }` : preferredContentTypeMatch && !preferredContentTypeMatch.includes("json") ? `, headers: { 'Content-Type': '${jsStringLiteralEscape(preferredContentTypeMatch)}' }` : "";
1854
+ const textCtHeaderSuffix = firstTextCt && firstTextCt !== textHelperDefaultContentType ? `, headers: { 'Content-Type': '${jsStringLiteralEscape(firstTextCt)}' }` : "";
1821
1855
  let responseBody;
1822
1856
  let responsePrelude = "";
1823
1857
  if (isReturnHttpResponse) {
@@ -1834,7 +1868,7 @@ function generateDefinition(name, route, getResponseMockFunctionNameBase, handle
1834
1868
  ? binaryBody
1835
1869
  : new ArrayBuffer(0),
1836
1870
  { status: ${statusCode},
1837
- headers: { 'Content-Type': '${binaryContentType}' }
1871
+ headers: { 'Content-Type': '${jsStringLiteralEscape(binaryContentType)}' }
1838
1872
  })`;
1839
1873
  else if (isVoidUnionType) {
1840
1874
  let nonVoidBody;