@powerduck/openapi-codegen 0.4.2 → 0.4.3

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.
Files changed (2) hide show
  1. package/dist/index.js +218 -144
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -877,161 +877,235 @@ function form(value) {
877
877
  }
878
878
 
879
879
  // src/core/normalize.ts
880
- function assertIsRecord(v) {
881
- if (!isRecord(v)) throw new TypeError("expected record object");
880
+ var HTTP_METHODS = /* @__PURE__ */ new Set([
881
+ "get",
882
+ "put",
883
+ "post",
884
+ "delete",
885
+ "options",
886
+ "head",
887
+ "patch",
888
+ "trace"
889
+ ]);
890
+ var CONTENT_PREFERENCES = [
891
+ /^application\/(?:[a-z0-9.+-]+\+)?json(?:\s*;.*)?$/i,
892
+ /^application\/x-www-form-urlencoded(?:\s*;.*)?$/i,
893
+ /^multipart\/form-data(?:\s*;.*)?$/i,
894
+ /^text\/[a-z0-9.+-]+(?:\s*;.*)?$/i,
895
+ /^application\/octet-stream(?:\s*;.*)?$/i
896
+ ];
897
+ function own(object, key2) {
898
+ return Object.prototype.hasOwnProperty.call(object, key2);
899
+ }
900
+ function nonBlankString2(value) {
901
+ if (typeof value !== "string") return void 0;
902
+ const trimmed = value.trim();
903
+ return trimmed.length > 0 ? trimmed : void 0;
904
+ }
905
+ function normalizeMethod2(value) {
906
+ const method = nonBlankString2(value)?.toLowerCase();
907
+ if (!method || !HTTP_METHODS.has(method)) {
908
+ throw new Error(`Unsupported HTTP method: ${String(value)}`);
909
+ }
910
+ return method;
882
911
  }
883
912
  function pickContent(content) {
884
- if (!content) {
885
- return void 0;
886
- }
887
- const entries = Object.entries(content);
888
- const preferred = [
889
- /^application\/([a-z0-9.+-]+\+)?json(?:;.*)?$/i,
890
- /^application\/x-www-form-urlencoded(?:;.*)?$/i,
891
- /^multipart\/form-data(?:;.*)?$/i,
892
- /^text\//i,
893
- /^application\/octet-stream(?:;.*)?$/i
894
- ];
895
- for (const matcher of preferred) {
896
- const match = entries.find(([mediaType]) => matcher.test(mediaType));
897
- if (match) {
898
- return match;
899
- }
913
+ if (!isRecord(content)) return void 0;
914
+ const entries = Object.entries(content).filter(
915
+ ([mediaType]) => mediaType.trim().length > 0
916
+ );
917
+ for (const matcher of CONTENT_PREFERENCES) {
918
+ const match = entries.find(([mediaType]) => matcher.test(mediaType.trim()));
919
+ if (match) return match;
900
920
  }
901
921
  return entries[0];
902
922
  }
903
923
  function mergeParameters(pathParameters, operationParameters, resolver) {
904
924
  const merged = /* @__PURE__ */ new Map();
905
- for (const entry of [...pathParameters, ...operationParameters]) {
906
- const parameter2 = resolver.deref(entry);
907
- if (!isRecord(parameter2)) continue;
908
- const nameVal = parameter2.name;
909
- const inVal = parameter2.in;
910
- if (typeof nameVal !== "string" || typeof inVal !== "string") continue;
911
- const key2 = `${inVal}:${nameVal}`;
912
- if (!merged.has(key2)) {
913
- merged.set(key2, parameter2);
925
+ const add = (entries, overwrite) => {
926
+ if (!Array.isArray(entries)) return;
927
+ for (const entry of entries) {
928
+ const parameter2 = resolver.deref(entry);
929
+ if (!isRecord(parameter2)) continue;
930
+ const name = nonBlankString2(parameter2.name);
931
+ const location = nonBlankString2(parameter2.in)?.toLowerCase();
932
+ if (!name || !location) continue;
933
+ const key2 = `${location}\0${name}`;
934
+ if (overwrite || !merged.has(key2)) {
935
+ merged.set(key2, parameter2);
936
+ }
914
937
  }
915
- }
938
+ };
939
+ add(pathParameters, false);
940
+ add(operationParameters, true);
916
941
  return [...merged.values()];
917
942
  }
918
- function readExampleForMediaType(mediaTypeObject, resolver, ctx) {
919
- if (!isRecord(mediaTypeObject)) return void 0;
920
- if (mediaTypeObject.example !== void 0) {
921
- return mediaTypeObject.example;
922
- }
923
- if (isRecord(mediaTypeObject.examples)) {
924
- const first = Object.values(mediaTypeObject.examples)[0];
925
- if (first !== void 0) {
926
- const resolvedExampleObj = resolver.deref(first);
927
- if (isRecord(resolvedExampleObj) && resolvedExampleObj.value !== void 0) {
928
- return resolvedExampleObj.value;
929
- }
943
+ function readFirstExampleValue(examples, resolver) {
944
+ if (!isRecord(examples)) return void 0;
945
+ for (const entry of Object.values(examples)) {
946
+ const resolved = resolver.deref(entry);
947
+ if (isRecord(resolved) && own(resolved, "value")) {
948
+ return resolved.value;
949
+ }
950
+ if (!isRecord(resolved) && resolved !== void 0) {
951
+ return resolved;
930
952
  }
931
953
  }
932
- return example(mediaTypeObject.schema, resolver, void 0, 0, ctx);
954
+ return void 0;
933
955
  }
934
- function readFirstExampleValue(examples, resolver) {
935
- if (!isRecord(examples)) {
936
- return void 0;
937
- }
938
- const firstEntry = Object.values(examples)[0];
939
- if (firstEntry === void 0) {
940
- return void 0;
956
+ function readExampleForMediaType(mediaTypeObject, resolver, context) {
957
+ const resolved = resolver.deref(mediaTypeObject);
958
+ if (!isRecord(resolved)) return void 0;
959
+ if (own(resolved, "example")) {
960
+ return resolved.example;
961
+ }
962
+ const examplesValue = readFirstExampleValue(resolved.examples, resolver);
963
+ if (examplesValue !== void 0) {
964
+ return examplesValue;
965
+ }
966
+ return example(resolved.schema, resolver, void 0, 0, context);
967
+ }
968
+ function resolveServerUrl(server) {
969
+ if (!isRecord(server)) return void 0;
970
+ let url = nonBlankString2(server.url);
971
+ if (!url) return void 0;
972
+ const variables = isRecord(server.variables) ? server.variables : void 0;
973
+ url = url.replace(/\{([^{}]+)\}/g, (placeholder, variableName) => {
974
+ if (!variables) return placeholder;
975
+ const variable = variables[variableName];
976
+ if (!isRecord(variable)) return placeholder;
977
+ const value = firstDefined(variable.default, variable.example);
978
+ return value === void 0 ? placeholder : encodeURIComponent(String(value));
979
+ });
980
+ return url;
981
+ }
982
+ function firstServerUrl(servers) {
983
+ if (!Array.isArray(servers)) return void 0;
984
+ for (const server of servers) {
985
+ const url = resolveServerUrl(server);
986
+ if (url) return url;
941
987
  }
942
- const resolved = resolver.deref(firstEntry);
943
- return isRecord(resolved) ? resolved.value : void 0;
988
+ return void 0;
944
989
  }
945
- function normalize(options) {
946
- const root = options.document;
947
- const preWarnings = lightweightOpenAPIPreCheck(root);
948
- const resolver = new RefResolver(root, !!options.softRefMode);
949
- const rootRec = isRecord(root) ? root : void 0;
950
- const paths = rootRec?.paths;
951
- let pathItem;
952
- if (isRecord(paths)) {
953
- pathItem = resolver.deref(paths[options.path]);
954
- }
955
- const methodLower = String(options.method).toLowerCase();
956
- const operation = isRecord(pathItem) ? resolver.deref(pathItem[methodLower]) : void 0;
957
- if (!isRecord(operation)) {
958
- throw new Error("Operation not found");
959
- }
960
- const pathItemParams = isRecord(pathItem) && Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
961
- const opParams = Array.isArray(operation.parameters) ? operation.parameters : [];
962
- const mergedParameters = mergeParameters(pathItemParams, opParams, resolver);
963
- const parameterEntries = mergedParameters.map((parameter2) => {
964
- assertIsRecord(parameter2);
965
- return {
966
- name: String(parameter2.name),
967
- in: String(parameter2.in),
968
- value: firstDefined(
969
- parameter2.example,
970
- readFirstExampleValue(parameter2.examples, resolver),
971
- example(parameter2.schema, resolver)
972
- ),
973
- style: typeof parameter2.style === "string" ? parameter2.style : void 0,
974
- explode: typeof parameter2.explode === "boolean" ? parameter2.explode : void 0,
975
- allowReserved: typeof parameter2.allowReserved === "boolean" ? parameter2.allowReserved : void 0
976
- };
977
- });
978
- const requestBody = resolver.deref(operation.requestBody);
979
- const chosen = isRecord(requestBody) ? pickContent(requestBody.content) : void 0;
980
- const bodyCtx = { isRequestBody: true, isResponse: false };
981
- const body = chosen ? {
982
- mediaType: chosen[0],
983
- value: readExampleForMediaType(chosen[1], resolver, bodyCtx),
984
- encoding: isRecord(chosen[1]) ? chosen[1].encoding : void 0
985
- } : void 0;
986
- const security = [];
987
- let effectiveSecurity = [];
988
- if (Array.isArray(operation.security)) {
989
- effectiveSecurity = operation.security;
990
- } else if (rootRec && Array.isArray(rootRec.security)) {
991
- effectiveSecurity = rootRec.security;
992
- }
993
- const components = rootRec?.components;
994
- const securitySchemes = isRecord(components) ? components.securitySchemes : void 0;
995
- for (const requirement of effectiveSecurity) {
990
+ function resolveBaseUrl(explicitServerUrl, operation, pathItem, root) {
991
+ return nonBlankString2(explicitServerUrl) ?? firstServerUrl(operation.servers) ?? firstServerUrl(pathItem.servers) ?? firstServerUrl(root.servers) ?? "https://example.com";
992
+ }
993
+ function resolveSecuritySchemes(root) {
994
+ if (!isRecord(root.components)) return void 0;
995
+ return isRecord(root.components.securitySchemes) ? root.components.securitySchemes : void 0;
996
+ }
997
+ function normalizeSecurity(root, operation, resolver, securityValues) {
998
+ const requirements = Array.isArray(operation.security) ? operation.security : Array.isArray(root.security) ? root.security : [];
999
+ const schemes = resolveSecuritySchemes(root);
1000
+ if (!schemes) return [];
1001
+ const result = [];
1002
+ const seen = /* @__PURE__ */ new Set();
1003
+ for (const requirement of requirements) {
996
1004
  if (!isRecord(requirement)) continue;
997
1005
  for (const name of Object.keys(requirement)) {
998
- let scheme;
999
- if (isRecord(securitySchemes)) {
1000
- scheme = resolver.deref(securitySchemes[name]);
1001
- }
1006
+ if (seen.has(name)) continue;
1007
+ const scheme = resolver.deref(schemes[name]);
1002
1008
  if (!isRecord(scheme)) continue;
1003
- security.push({
1009
+ const type = nonBlankString2(scheme.type);
1010
+ if (!type) continue;
1011
+ seen.add(name);
1012
+ result.push({
1004
1013
  name,
1005
- type: String(scheme.type),
1006
- scheme: typeof scheme.scheme === "string" ? scheme.scheme : void 0,
1007
- in: typeof scheme.in === "string" ? scheme.in : void 0,
1008
- paramName: typeof scheme.name === "string" ? scheme.name : void 0,
1009
- value: options.securityValues?.[name] ?? `{{${name}}}`
1014
+ type,
1015
+ scheme: nonBlankString2(scheme.scheme)?.toLowerCase(),
1016
+ in: nonBlankString2(scheme.in)?.toLowerCase(),
1017
+ paramName: nonBlankString2(scheme.name),
1018
+ value: securityValues?.[name] ?? `{{${name}}}`
1010
1019
  });
1011
1020
  }
1012
1021
  }
1013
- let baseUrl = options.serverUrl;
1014
- if (!baseUrl && Array.isArray(operation.servers) && isRecord(operation.servers[0])) {
1015
- baseUrl = String(operation.servers[0].url);
1022
+ return result;
1023
+ }
1024
+ function normalize(options) {
1025
+ if (!isRecord(options)) {
1026
+ throw new TypeError("normalize options must be an object");
1027
+ }
1028
+ if (!isRecord(options.document)) {
1029
+ throw new TypeError("OpenAPI document root must be a plain object");
1016
1030
  }
1017
- if (!baseUrl && isRecord(pathItem) && Array.isArray(pathItem.servers) && isRecord(pathItem.servers[0])) {
1018
- baseUrl = String(pathItem.servers[0].url);
1031
+ const root = options.document;
1032
+ const preWarnings = lightweightOpenAPIPreCheck(root);
1033
+ const resolver = new RefResolver(root, options.softRefMode === true);
1034
+ const path = nonBlankString2(options.path);
1035
+ if (!path) {
1036
+ throw new TypeError("OpenAPI path must be a non-empty string");
1019
1037
  }
1020
- if (!baseUrl && Array.isArray(rootRec?.servers) && isRecord((rootRec?.servers)[0])) {
1021
- baseUrl = String(
1022
- (rootRec?.servers)[0].url
1023
- );
1038
+ const method = normalizeMethod2(options.method);
1039
+ if (!isRecord(root.paths)) {
1040
+ throw new Error("OpenAPI document does not define a valid paths object");
1024
1041
  }
1025
- if (!baseUrl) {
1026
- baseUrl = "https://example.com";
1042
+ if (!own(root.paths, path)) {
1043
+ throw new Error(`Path not found: ${path}`);
1027
1044
  }
1045
+ const pathItem = resolver.deref(root.paths[path]);
1046
+ if (!isRecord(pathItem)) {
1047
+ throw new Error(`Invalid Path Item Object: ${path}`);
1048
+ }
1049
+ const operation = resolver.deref(pathItem[method]);
1050
+ if (!isRecord(operation)) {
1051
+ throw new Error(`Operation not found: ${method.toUpperCase()} ${path}`);
1052
+ }
1053
+ const mergedParameters = mergeParameters(
1054
+ pathItem.parameters,
1055
+ operation.parameters,
1056
+ resolver
1057
+ );
1058
+ const parameters = mergedParameters.map(
1059
+ (parameter2) => {
1060
+ const name = nonBlankString2(parameter2.name);
1061
+ const location = nonBlankString2(parameter2.in)?.toLowerCase();
1062
+ if (!name || !location) {
1063
+ throw new TypeError("Resolved parameter must define name and in");
1064
+ }
1065
+ const parameterExample = own(parameter2, "example") ? parameter2.example : void 0;
1066
+ return {
1067
+ name,
1068
+ in: location,
1069
+ value: firstDefined(
1070
+ parameterExample,
1071
+ readFirstExampleValue(parameter2.examples, resolver),
1072
+ example(parameter2.schema, resolver)
1073
+ ),
1074
+ style: nonBlankString2(parameter2.style),
1075
+ explode: typeof parameter2.explode === "boolean" ? parameter2.explode : void 0,
1076
+ allowReserved: typeof parameter2.allowReserved === "boolean" ? parameter2.allowReserved : void 0
1077
+ };
1078
+ }
1079
+ );
1080
+ const requestBody = resolver.deref(operation.requestBody);
1081
+ const chosen = isRecord(requestBody) ? pickContent(requestBody.content) : void 0;
1082
+ const bodyContext = {
1083
+ isRequestBody: true,
1084
+ isResponse: false
1085
+ };
1086
+ let body;
1087
+ if (chosen) {
1088
+ const mediaTypeObject = resolver.deref(chosen[1]);
1089
+ body = {
1090
+ mediaType: chosen[0],
1091
+ value: readExampleForMediaType(mediaTypeObject, resolver, bodyContext),
1092
+ encoding: isRecord(mediaTypeObject) ? mediaTypeObject.encoding : void 0
1093
+ };
1094
+ }
1095
+ const security = normalizeSecurity(
1096
+ root,
1097
+ operation,
1098
+ resolver,
1099
+ options.securityValues
1100
+ );
1101
+ const baseUrl = resolveBaseUrl(options.serverUrl, operation, pathItem, root);
1028
1102
  return {
1029
1103
  preWarnings,
1030
- method: String(options.method).toUpperCase(),
1104
+ method: method.toUpperCase(),
1031
1105
  baseUrl,
1032
- path: options.path,
1033
- parameters: parameterEntries,
1034
- headers: parameterEntries.filter((parameter2) => parameter2.in === "header"),
1106
+ path,
1107
+ parameters,
1108
+ headers: parameters.filter((parameter2) => parameter2.in === "header"),
1035
1109
  body,
1036
1110
  security
1037
1111
  };
@@ -1075,7 +1149,7 @@ function escapeCString(value) {
1075
1149
  }
1076
1150
  return `${escaped}"`;
1077
1151
  }
1078
- function nonBlankString2(value, fallback) {
1152
+ function nonBlankString3(value, fallback) {
1079
1153
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
1080
1154
  }
1081
1155
  function safeComment(value) {
@@ -1157,9 +1231,9 @@ function emit(request) {
1157
1231
  );
1158
1232
  if (entry.file && isFileValue(entry.value)) {
1159
1233
  const fileValue = entry.value;
1160
- const path = nonBlankString2(fileValue.path, DEFAULT_FILE_PATH);
1161
- const name = nonBlankString2(fileValue.name, DEFAULT_FILE_NAME);
1162
- const contentType = nonBlankString2(
1234
+ const path = nonBlankString3(fileValue.path, DEFAULT_FILE_PATH);
1235
+ const name = nonBlankString3(fileValue.name, DEFAULT_FILE_NAME);
1236
+ const contentType = nonBlankString3(
1163
1237
  fileValue.contentType,
1164
1238
  DEFAULT_BINARY_MEDIA_TYPE
1165
1239
  );
@@ -1281,7 +1355,7 @@ function emit(request) {
1281
1355
  var libcurl_default = createGenerator("c", "libcurl", (request) => emit(request));
1282
1356
 
1283
1357
  // src/emitters/csharp/restsharp.ts
1284
- function nonBlankString3(value, fallback) {
1358
+ function nonBlankString4(value, fallback) {
1285
1359
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
1286
1360
  }
1287
1361
  function safeComment2(value) {
@@ -1313,9 +1387,9 @@ function emit2(request) {
1313
1387
  const fieldName = String(item.name);
1314
1388
  if (item.file && isFileValue(item.value)) {
1315
1389
  const fileValue = item.value;
1316
- const filePath = nonBlankString3(fileValue.path, DEFAULT_FILE_PATH);
1317
- const fileName = nonBlankString3(fileValue.name, DEFAULT_FILE_NAME);
1318
- const contentType = nonBlankString3(
1390
+ const filePath = nonBlankString4(fileValue.path, DEFAULT_FILE_PATH);
1391
+ const fileName = nonBlankString4(fileValue.name, DEFAULT_FILE_NAME);
1392
+ const contentType = nonBlankString4(
1319
1393
  fileValue.contentType,
1320
1394
  DEFAULT_BINARY_MEDIA_TYPE
1321
1395
  );
@@ -1604,7 +1678,7 @@ function emit4(request) {
1604
1678
  var http_default = createGenerator("dart", "http", (request) => emit4(request));
1605
1679
 
1606
1680
  // src/emitters/fsharp/httpclient.ts
1607
- function nonBlankString4(value, fallback) {
1681
+ function nonBlankString5(value, fallback) {
1608
1682
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
1609
1683
  }
1610
1684
  function safeComment3(value) {
@@ -1643,9 +1717,9 @@ function emit5(request) {
1643
1717
  const fieldName = String(item.name);
1644
1718
  if (item.file && isFileValue(item.value)) {
1645
1719
  const fileValue = item.value;
1646
- const filePath = nonBlankString4(fileValue.path, DEFAULT_FILE_PATH);
1647
- const fileName = nonBlankString4(fileValue.name, DEFAULT_FILE_NAME);
1648
- const contentType = nonBlankString4(
1720
+ const filePath = nonBlankString5(fileValue.path, DEFAULT_FILE_PATH);
1721
+ const fileName = nonBlankString5(fileValue.name, DEFAULT_FILE_NAME);
1722
+ const contentType = nonBlankString5(
1649
1723
  fileValue.contentType,
1650
1724
  DEFAULT_BINARY_MEDIA_TYPE
1651
1725
  );
@@ -1780,7 +1854,7 @@ function emit5(request) {
1780
1854
  var httpclient_default2 = createGenerator("fsharp", "httpclient", (request) => emit5(request));
1781
1855
 
1782
1856
  // src/emitters/go/new-request.ts
1783
- function nonBlankString5(value, fallback) {
1857
+ function nonBlankString6(value, fallback) {
1784
1858
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
1785
1859
  }
1786
1860
  function safeComment4(value) {
@@ -1833,9 +1907,9 @@ function emit6(request) {
1833
1907
  const fieldName = String(item.name);
1834
1908
  if (item.file && isFileValue(item.value)) {
1835
1909
  const fileValue = item.value;
1836
- const filePath = nonBlankString5(fileValue.path, DEFAULT_FILE_PATH);
1837
- const fileName = nonBlankString5(fileValue.name, DEFAULT_FILE_NAME);
1838
- const contentType = nonBlankString5(
1910
+ const filePath = nonBlankString6(fileValue.path, DEFAULT_FILE_PATH);
1911
+ const fileName = nonBlankString6(fileValue.name, DEFAULT_FILE_NAME);
1912
+ const contentType = nonBlankString6(
1839
1913
  fileValue.contentType,
1840
1914
  DEFAULT_BINARY_MEDIA_TYPE
1841
1915
  );
@@ -2603,7 +2677,7 @@ function emit10(request) {
2603
2677
  var okhttp_default = createGenerator("java", "okhttp", (request) => emit10(request));
2604
2678
 
2605
2679
  // src/emitters/java/unirest.ts
2606
- function nonBlankString6(value, fallback) {
2680
+ function nonBlankString7(value, fallback) {
2607
2681
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
2608
2682
  }
2609
2683
  function safeComment8(value) {
@@ -2665,7 +2739,7 @@ function emit11(request) {
2665
2739
  const fieldName = String(entry.name);
2666
2740
  if (entry.file && isFileValue(entry.value)) {
2667
2741
  const fileValue = entry.value;
2668
- const filePath = nonBlankString6(fileValue.path, DEFAULT_FILE_PATH);
2742
+ const filePath = nonBlankString7(fileValue.path, DEFAULT_FILE_PATH);
2669
2743
  if (typeof fileValue.path !== "string" || fileValue.path.trim().length === 0) {
2670
2744
  comments.push(`// ${safeComment8(fileComment(filePath, fieldName))}`);
2671
2745
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerduck/openapi-codegen",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Generate HTTP client examples from OpenAPI documents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",