swaggie 1.5.3-beta.3 → 1.5.3-beta.5

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.
@@ -15,13 +15,13 @@ var _utils = require('../utils');
15
15
  }
16
16
 
17
17
  const viewData = {
18
- servicePrefix: clientOptions.servicePrefix || '',
18
+ servicePrefix: clientOptions.servicePrefix,
19
19
  clients: files
20
20
  .filter((c) => c)
21
21
  .map((c) => ({
22
- fileName: (clientOptions.servicePrefix || '') + c,
23
- className: `${(clientOptions.servicePrefix || '') + c}Client`,
24
- camelCaseName: _case.camel.call(void 0, `${(clientOptions.servicePrefix || '') + c}Client`),
22
+ fileName: clientOptions.servicePrefix + c,
23
+ className: `${clientOptions.servicePrefix + c}Client`,
24
+ camelCaseName: _case.camel.call(void 0, `${clientOptions.servicePrefix + c}Client`),
25
25
  })),
26
26
  };
27
27
 
@@ -25,7 +25,7 @@ var _jsDocs = require('./jsDocs');
25
25
  ) {
26
26
  const operations = _swagger.getOperations.call(void 0, spec);
27
27
  const groups = _utils.groupOperationsByGroupName.call(void 0, operations);
28
- const servicePrefix = _nullishCoalesce(options.servicePrefix, () => ( ''));
28
+ const servicePrefix = options.servicePrefix;
29
29
  let result = _utils.renderFile.call(void 0, 'baseClient.ejs', {
30
30
  servicePrefix,
31
31
  baseUrl: options.baseUrl,
@@ -34,7 +34,7 @@ var _jsDocs = require('./jsDocs');
34
34
 
35
35
  for (const name in groups) {
36
36
  const group = groups[name];
37
- const clientData = prepareClient(servicePrefix + name, group, options);
37
+ const clientData = prepareClient(servicePrefix + name, group, spec.components, options);
38
38
 
39
39
  if (!clientData) {
40
40
  continue;
@@ -56,9 +56,10 @@ var _jsDocs = require('./jsDocs');
56
56
  function prepareClient(
57
57
  name,
58
58
  operations,
59
+ components,
59
60
  options
60
61
  ) {
61
- const preparedOperations = prepareOperations(operations, options);
62
+ const preparedOperations = prepareOperations(operations, options, components);
62
63
 
63
64
  if (preparedOperations.length === 0) {
64
65
  return null;
@@ -86,7 +87,8 @@ function prepareClient(
86
87
  */
87
88
  function prepareOperations(
88
89
  operations,
89
- options
90
+ options,
91
+ components
90
92
  ) {
91
93
  let ops = fixDuplicateOperations(operations);
92
94
 
@@ -95,10 +97,10 @@ function prepareClient(
95
97
  }
96
98
 
97
99
  return ops.map((op) => {
98
- const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op);
100
+ const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op, components);
99
101
  const returnType = _swagger.getParameterType.call(void 0, respObject, options);
100
102
 
101
- const body = getRequestBody(op.requestBody, options);
103
+ const body = getRequestBody(op.requestBody, components, options);
102
104
  const queryParams = getParams(op.parameters , options, ['query']);
103
105
  const params = getParams(op.parameters , options);
104
106
 
@@ -279,23 +281,40 @@ function prepareUrl(path) {
279
281
  } exports.getParamName = getParamName;
280
282
 
281
283
  function getRequestBody(
282
- reqBody,
284
+ rawReqBody,
285
+ components,
283
286
  options
284
287
  ) {
285
- if (reqBody && 'content' in reqBody) {
286
- const [bodyContent, contentType] = _utils.getBestContentType.call(void 0, reqBody);
287
- const isFormData = contentType === 'form-data';
288
-
289
- if (bodyContent) {
290
- return {
291
- originalName: _nullishCoalesce(reqBody['x-name'], () => ( 'body')),
292
- name: getParamName(_nullishCoalesce(reqBody['x-name'], () => ( 'body'))),
293
- type: isFormData ? 'FormData' : _swagger.getParameterType.call(void 0, bodyContent, options),
294
- optional: !reqBody.required,
295
- original: reqBody,
296
- contentType,
297
- };
288
+ if (!rawReqBody) {
289
+ return null;
290
+ }
291
+
292
+ let reqBody;
293
+ if ('$ref' in rawReqBody) {
294
+ const refName = rawReqBody.$ref.replace('#/components/requestBodies/', '');
295
+ const resolved = _optionalChain([components, 'optionalAccess', _8 => _8.requestBodies, 'optionalAccess', _9 => _9[refName]]);
296
+ if (!resolved || '$ref' in resolved) {
297
+ console.error(`RequestBody $ref '${rawReqBody.$ref}' not found in components/requestBodies`);
298
+ return null;
298
299
  }
300
+ reqBody = resolved;
301
+ } else {
302
+ reqBody = rawReqBody;
299
303
  }
304
+
305
+ const [bodyContent, contentType] = _utils.getBestContentType.call(void 0, reqBody);
306
+ const isFormData = contentType === 'form-data';
307
+
308
+ if (bodyContent) {
309
+ return {
310
+ originalName: _nullishCoalesce(reqBody['x-name'], () => ( 'body')),
311
+ name: getParamName(_nullishCoalesce(reqBody['x-name'], () => ( 'body'))),
312
+ type: isFormData ? 'FormData' : _swagger.getParameterType.call(void 0, bodyContent, options),
313
+ optional: !reqBody.required,
314
+ original: reqBody,
315
+ contentType,
316
+ };
317
+ }
318
+
300
319
  return null;
301
320
  }
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  var _gen = require('./gen'); var _gen2 = _interopRequireDefault(_gen);
5
5
 
6
+ var _types = require('./types');
6
7
  var _utils = require('./utils');
7
8
 
8
9
  /**
@@ -33,7 +34,7 @@ function verifyOptions(options) {
33
34
  }
34
35
 
35
36
  function gen(spec, options) {
36
- _utils.loadAllTemplateFiles.call(void 0, options.template || 'axios');
37
+ _utils.loadAllTemplateFiles.call(void 0, options.template);
37
38
 
38
39
  return _gen2.default.call(void 0, spec, options);
39
40
  }
@@ -68,20 +69,16 @@ function readFile(filePath) {
68
69
 
69
70
 
70
71
 
71
- const defaultQueryParamsConfig = {
72
- allowDots: true,
73
- arrayFormat: 'repeat' ,
74
- };
75
-
76
72
  /**
77
73
  * CLI options are flat, but within the app we use nested objects.
78
74
  * This function converts flat options structure to the nested one and
79
- * merges it with the default values.
80
- * */
81
- function prepareAppOptions(cliOpts) {
75
+ * merges it with the default values, producing a fully-initialized AppOptions
76
+ * object where every defaultable field is guaranteed to be present.
77
+ */
78
+ function prepareAppOptions(cliOpts) {
82
79
  const { allowDots, arrayFormat, template, queryParamsSerialization = {}, ...rest } = cliOpts;
83
80
  const mergedQueryParamsSerialization = {
84
- ...defaultQueryParamsConfig,
81
+ ..._types.APP_DEFAULTS.queryParamsSerialization,
85
82
  ...Object.fromEntries(
86
83
  Object.entries(queryParamsSerialization).filter(([_, v]) => v !== undefined)
87
84
  ),
@@ -91,7 +88,9 @@ function prepareAppOptions(cliOpts) {
91
88
 
92
89
  return {
93
90
  ...rest,
91
+ template: _nullishCoalesce(template, () => ( _types.APP_DEFAULTS.template)),
92
+ servicePrefix: _nullishCoalesce(rest.servicePrefix, () => ( _types.APP_DEFAULTS.servicePrefix)),
93
+ nullableStrategy: _nullishCoalesce(rest.nullableStrategy, () => ( _types.APP_DEFAULTS.nullableStrategy)),
94
94
  queryParamsSerialization: mergedQueryParamsSerialization,
95
- template: _nullishCoalesce(template, () => ( 'axios')),
96
95
  };
97
- }
96
+ } exports.prepareAppOptions = prepareAppOptions;
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
  var _utils = require('../utils');
4
4
 
@@ -24,7 +24,7 @@ var _utils = require('../utils');
24
24
  return unknownType;
25
25
  }
26
26
 
27
- return getTypeFromSchema(param.schema, options);
27
+ return getTypeFromSchemaResolved(param.schema, options);
28
28
  } exports.getParameterType = getParameterType;
29
29
 
30
30
  /**
@@ -36,6 +36,17 @@ var _utils = require('../utils');
36
36
  function getTypeFromSchema(
37
37
  schema,
38
38
  options
39
+ ) {
40
+ return getTypeFromSchemaResolved(schema, options);
41
+ } exports.getTypeFromSchema = getTypeFromSchema;
42
+
43
+ /**
44
+ * Internal implementation of getTypeFromSchema that operates on fully-resolved AppOptions.
45
+ * All private functions in this module call this version directly to avoid redundant resolution.
46
+ */
47
+ function getTypeFromSchemaResolved(
48
+ schema,
49
+ options
39
50
  ) {
40
51
  const unknownType = options.preferAny ? 'any' : 'unknown';
41
52
 
@@ -59,25 +70,21 @@ var _utils = require('../utils');
59
70
 
60
71
  // OpenAPI 3.0 nullable: nullable: true
61
72
  const isNullable = 'nullable' in schema && schema.nullable === true;
62
- const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
63
- const isNullableSuffix = isNullable && strategy === 'include' ? ' | null' : '';
73
+ const isNullableSuffix = isNullable && options.nullableStrategy === 'include' ? ' | null' : '';
64
74
  const type = getTypeFromSchemaInternal(schema, options);
65
75
 
66
76
  if (isNullableSuffix && type.endsWith('| null')) {
67
77
  return type;
68
78
  }
69
79
  return type + isNullableSuffix;
70
- } exports.getTypeFromSchema = getTypeFromSchema;
80
+ }
71
81
 
72
82
  /**
73
83
  * Handles OpenAPI 3.1 schemas where `type` is an array (e.g. `["string", "null"]`).
74
84
  * The presence of `"null"` in the array is the OA3.1 way of marking a field as nullable.
75
85
  * Respects `nullableStrategy` the same way as OA3.0 `nullable: true`.
76
86
  */
77
- function getTypeFromOA31ArrayType(
78
- schema,
79
- options
80
- ) {
87
+ function getTypeFromOA31ArrayType(schema, options) {
81
88
  const unknownType = options.preferAny ? 'any' : 'unknown';
82
89
  const types = schema.type ;
83
90
  const isNullable = types.includes('null');
@@ -107,8 +114,7 @@ function getTypeFromOA31ArrayType(
107
114
  return 'null';
108
115
  }
109
116
 
110
- const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
111
- if (strategy === 'include') {
117
+ if (options.nullableStrategy === 'include') {
112
118
  // We don't want multiple nulls in the type string
113
119
  if (baseType.endsWith('| null')) {
114
120
  return baseType;
@@ -162,24 +168,22 @@ function getNestedTypeFromSchema(
162
168
  schema,
163
169
  options
164
170
  ) {
165
- const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
166
-
167
171
  // OA3.0 nullable: true
168
172
  const isOA30NullableAndActive =
169
- 'nullable' in schema && schema.nullable === true && strategy === 'include';
173
+ 'nullable' in schema && schema.nullable === true && options.nullableStrategy === 'include';
170
174
 
171
175
  // OA3.1 nullable: type array containing 'null'
172
176
  const isOA31NullableAndActive =
173
177
  'type' in schema &&
174
178
  Array.isArray(schema.type) &&
175
179
  schema.type.includes('null') &&
176
- strategy === 'include';
180
+ options.nullableStrategy === 'include';
177
181
 
178
182
  if (isOA30NullableAndActive || isOA31NullableAndActive || ('enum' in schema && schema.enum)) {
179
- return `(${getTypeFromSchema(schema, options)})`;
183
+ return `(${getTypeFromSchemaResolved(schema, options)})`;
180
184
  }
181
185
 
182
- return getTypeFromSchema(schema, options);
186
+ return getTypeFromSchemaResolved(schema, options);
183
187
  }
184
188
 
185
189
  /**
@@ -195,7 +199,7 @@ function getTypeFromObject(
195
199
  if (schema.additionalProperties) {
196
200
  const extraProps = schema.additionalProperties;
197
201
  return `{ [key: string]: ${
198
- extraProps === true ? 'any' : getTypeFromSchema(extraProps, options)
202
+ extraProps === true ? 'any' : getTypeFromSchemaResolved(extraProps, options)
199
203
  } }`;
200
204
  }
201
205
 
@@ -209,7 +213,7 @@ function getTypeFromObject(
209
213
  const isRequired = required.includes(prop);
210
214
  const safePropName = _utils.escapePropName.call(void 0, prop);
211
215
  result.push(
212
- `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchema(propDefinition, options)};`
216
+ `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchemaResolved(propDefinition, options)};`
213
217
  );
214
218
  }
215
219
 
@@ -225,7 +229,9 @@ function getTypeFromObject(
225
229
  function getTypeFromComposites(schema, options) {
226
230
  const composite = schema.allOf || schema.oneOf || schema.anyOf;
227
231
 
228
- return composite.map((s) => getTypeFromSchema(s, options)).join(schema.allOf ? ' & ' : ' | ');
232
+ return composite
233
+ .map((s) => getTypeFromSchemaResolved(s, options))
234
+ .join(schema.allOf ? ' & ' : ' | ');
229
235
  }
230
236
 
231
237
  /**
package/dist/types.d.ts CHANGED
@@ -50,6 +50,27 @@ export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head'
50
50
  export type DateSupport = 'string' | 'Date';
51
51
  export type ArrayFormat = 'indices' | 'repeat' | 'brackets';
52
52
  export type NullableStrategy = 'include' | 'nullableAsOptional' | 'ignore';
53
+ /**
54
+ * Internal options type used throughout the app after `prepareAppOptions` has run.
55
+ * All fields that have defaults are required here so the rest of the codebase never
56
+ * needs to perform its own `?? fallback` logic.
57
+ */
58
+ export interface AppOptions extends ClientOptions {
59
+ template: Template;
60
+ servicePrefix: string;
61
+ nullableStrategy: NullableStrategy;
62
+ queryParamsSerialization: {
63
+ allowDots: boolean;
64
+ arrayFormat: ArrayFormat;
65
+ };
66
+ }
67
+ /** Default values applied to every field of AppOptions that has a default. */
68
+ export declare const APP_DEFAULTS: Partial<AppOptions>;
69
+ /**
70
+ * Fills in all AppOptions defaults for a partial ClientOptions object.
71
+ * Used at the boundary between public API / test helpers and the internal pipeline.
72
+ */
73
+ export declare function resolveOptions(opts: Partial<ClientOptions>): AppOptions;
53
74
  /**
54
75
  * Local type that represent Operation as understood by Swaggie
55
76
  **/
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }var _nodefs = require('node:fs');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _nodefs = require('node:fs');
2
2
  var _nodepath = require('node:path');
3
3
 
4
4
 
@@ -171,11 +171,15 @@ const reservedKeywords = new Set([
171
171
  * Other media types are not supported at this time.
172
172
  * @returns Response or reference of the success response
173
173
  */
174
- function getBestResponse(op) {
174
+ function getBestResponse(
175
+ op,
176
+ components
177
+ ) {
175
178
  const NOT_FOUND = 100000;
176
179
  const lowestCode = _nullishCoalesce(Object.keys(op.responses).sort().shift(), () => ( NOT_FOUND));
177
180
 
178
- const resp = lowestCode === NOT_FOUND ? op.responses[0] : op.responses[lowestCode.toString()];
181
+ const rawResp = lowestCode === NOT_FOUND ? op.responses[0] : op.responses[lowestCode.toString()];
182
+ const resp = resolveResponseRef(rawResp, components);
179
183
 
180
184
  if (resp && 'content' in resp) {
181
185
  return getBestContentType(resp);
@@ -183,6 +187,33 @@ const reservedKeywords = new Set([
183
187
  return [null, null];
184
188
  } exports.getBestResponse = getBestResponse;
185
189
 
190
+ /**
191
+ * Resolves a $ref in a response object to the actual response object from components/responses.
192
+ * If the response is already an object (not a reference), it is returned as-is.
193
+ */
194
+ function resolveResponseRef(
195
+ resp,
196
+ components
197
+ ) {
198
+ if (!resp) {
199
+ return null;
200
+ }
201
+
202
+ if (!('$ref' in resp)) {
203
+ return resp;
204
+ }
205
+
206
+ const refName = resp.$ref.replace('#/components/responses/', '');
207
+ const resolved = _optionalChain([components, 'optionalAccess', _ => _.responses, 'optionalAccess', _2 => _2[refName]]);
208
+
209
+ if (!resolved) {
210
+ console.error(`Response $ref '${resp.$ref}' not found in components/responses`);
211
+ return null;
212
+ }
213
+
214
+ return resolved ;
215
+ }
216
+
186
217
  /** This method tries to fix potentially wrong out parameter given from commandline */
187
218
  function prepareOutputFilename(out) {
188
219
  if (!out) {
@@ -206,7 +237,7 @@ const reservedKeywords = new Set([
206
237
  return arr.concat().sort(sortByKey(key));
207
238
  } exports.orderBy = orderBy;
208
239
 
209
- const sortByKey = (key) => (a, b) => a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0;
240
+ const sortByKey = (key) => (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
210
241
 
211
242
  const orderedContentTypes = [
212
243
  'application/json',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.5.3-beta.3",
3
+ "version": "1.5.3-beta.5",
4
4
  "description": "Generate TypeScript REST client code from an OpenAPI spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",