oas 17.7.3 → 17.8.2

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.
@@ -1,6 +1,7 @@
1
1
  import type { RequestBodyExamples } from './operation/get-requestbody-examples';
2
2
  import type { CallbackExamples } from './operation/get-callback-examples';
3
3
  import type { ResponseExamples } from './operation/get-response-examples';
4
+ import type { SchemaWrapper } from './operation/get-parameters-as-json-schema';
4
5
  import * as RMOAS from './rmoas.types';
5
6
  declare type SecurityType = 'Basic' | 'Bearer' | 'Query' | 'Header' | 'Cookie' | 'OAuth2' | 'http' | 'apiKey';
6
7
  export default class Operation {
@@ -43,6 +44,10 @@ export default class Operation {
43
44
  request: string[];
44
45
  response: string[];
45
46
  };
47
+ /**
48
+ * All parameters and request bodies converted into JSON Schema.
49
+ */
50
+ parameterJsonSchema: SchemaWrapper[];
46
51
  constructor(api: RMOAS.OASDocument, path: string, method: RMOAS.HttpMethods, operation: RMOAS.OperationObject);
47
52
  getSummary(): string;
48
53
  getDescription(): string;
@@ -84,11 +89,15 @@ export default class Operation {
84
89
  */
85
90
  hasOperationId(): boolean;
86
91
  /**
87
- * Get an `operationId` for this operation. If one is not present (it's not required by the spec!) a hash of the path
88
- * and method will be returned instead.
92
+ * Get an `operationId` for this operation. If one is not present (it's not required by the spec!)
93
+ * a hash of the path and method will be returned instead.
89
94
  *
95
+ * @param opts
96
+ * @param opts.camelCase Generate a JS method-friendly operation ID when one isn't present.
90
97
  */
91
- getOperationId(): string;
98
+ getOperationId(opts?: {
99
+ camelCase: boolean;
100
+ }): string;
92
101
  /**
93
102
  * Return an array of all tags, and their metadata, that exist on this operation.
94
103
  *
@@ -109,13 +118,18 @@ export default class Operation {
109
118
  *
110
119
  */
111
120
  getParameters(): RMOAS.ParameterObject[];
121
+ /**
122
+ * Determine if this operation has any required parameters.
123
+ *
124
+ */
125
+ hasRequiredParameters(): boolean;
112
126
  /**
113
127
  * Convert the operation into an array of JSON Schema schemas for each available type of parameter available on the
114
128
  * operation.
115
129
  *
116
130
  * @param globalDefaults Contains an object of user defined schema defaults.
117
131
  */
118
- getParametersAsJsonSchema(globalDefaults?: Record<string, unknown>): import("./operation/get-parameters-as-json-schema").SchemaWrapper[];
132
+ getParametersAsJsonSchema(globalDefaults?: Record<string, unknown>): SchemaWrapper[];
119
133
  /**
120
134
  * Get a single response for this status code, formatted as JSON schema.
121
135
  *
@@ -144,6 +158,11 @@ export default class Operation {
144
158
  * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#mediaTypeObject}
145
159
  */
146
160
  getRequestBodyMediaTypes(): string[];
161
+ /**
162
+ * Determine if this operation has a required request body.
163
+ *
164
+ */
165
+ hasRequiredRequestBody(): boolean;
147
166
  /**
148
167
  * Retrieve a specific request body content schema off this operation.
149
168
  *
package/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## <small>17.8.2 (2022-03-21)</small>
2
+
3
+ * fix: issue where hostname server variables wouldn't match subdomains or ports (#623) ([0630600](https://github.com/readmeio/oas/commit/0630600)), closes [#623](https://github.com/readmeio/oas/issues/623)
4
+
5
+
6
+
7
+ ## <small>17.8.1 (2022-03-04)</small>
8
+
9
+ * fix: typo in the `--pattern` option ([42db80a](https://github.com/readmeio/oas/commit/42db80a))
10
+ * feat: adding new accessors for determining if an operation has required params (#615) ([c081d92](https://github.com/readmeio/oas/commit/c081d92)), closes [#615](https://github.com/readmeio/oas/issues/615)
11
+ * feat: optionally generate friendlier operationIds (#602) ([6826164](https://github.com/readmeio/oas/commit/6826164)), closes [#602](https://github.com/readmeio/oas/issues/602)
12
+
13
+
14
+
15
+ ## 17.8.0 (2022-03-02)
16
+
17
+ * chore(deps-dev): bump @commitlint/cli from 16.1.0 to 16.2.1 (#612) ([e7364dd](https://github.com/readmeio/oas/commit/e7364dd)), closes [#612](https://github.com/readmeio/oas/issues/612)
18
+ * chore(deps-dev): bump @commitlint/config-conventional (#607) ([5451921](https://github.com/readmeio/oas/commit/5451921)), closes [#607](https://github.com/readmeio/oas/issues/607)
19
+ * chore(deps-dev): bump @readme/eslint-config from 8.4.1 to 8.4.4 (#609) ([7d097f3](https://github.com/readmeio/oas/commit/7d097f3)), closes [#609](https://github.com/readmeio/oas/issues/609)
20
+ * chore(deps-dev): bump @types/jest from 27.4.0 to 27.4.1 (#611) ([ed2b0a3](https://github.com/readmeio/oas/commit/ed2b0a3)), closes [#611](https://github.com/readmeio/oas/issues/611)
21
+ * chore(deps-dev): bump eslint from 8.8.0 to 8.10.0 (#610) ([2382b87](https://github.com/readmeio/oas/commit/2382b87)), closes [#610](https://github.com/readmeio/oas/issues/610)
22
+ * chore(deps-dev): bump eslint-plugin-jsdoc from 37.7.1 to 37.9.5 (#613) ([456bc79](https://github.com/readmeio/oas/commit/456bc79)), closes [#613](https://github.com/readmeio/oas/issues/613)
23
+ * chore(deps-dev): bump jest from 27.4.7 to 27.5.1 (#608) ([41c09f6](https://github.com/readmeio/oas/commit/41c09f6)), closes [#608](https://github.com/readmeio/oas/issues/608)
24
+ * chore(deps-dev): bump typescript from 4.5.5 to 4.6.2 (#605) ([93216ea](https://github.com/readmeio/oas/commit/93216ea)), closes [#605](https://github.com/readmeio/oas/issues/605)
25
+ * chore(deps): bump actions/checkout from 2.4.0 to 3 (#606) ([ea8a28a](https://github.com/readmeio/oas/commit/ea8a28a)), closes [#606](https://github.com/readmeio/oas/issues/606)
26
+ * chore(deps): bump actions/setup-node from 2.5.1 to 3 (#604) ([a463050](https://github.com/readmeio/oas/commit/a463050)), closes [#604](https://github.com/readmeio/oas/issues/604)
27
+ * chore(style): upgrading our core typescript code standards (#598) ([f12d472](https://github.com/readmeio/oas/commit/f12d472)), closes [#598](https://github.com/readmeio/oas/issues/598)
28
+ * feat: adding a dereference option to preserve ref pointers as titles (#601) ([161aebf](https://github.com/readmeio/oas/commit/161aebf)), closes [#601](https://github.com/readmeio/oas/issues/601)
29
+ * feat: upgrading swagger-inline and adding new `pathGlob` and `pattern` options (#614) ([a7dbe6a](https://github.com/readmeio/oas/commit/a7dbe6a)), closes [#614](https://github.com/readmeio/oas/issues/614)
30
+ * build: 17.7.3 release ([0c9974c](https://github.com/readmeio/oas/commit/0c9974c))
31
+ * test: colocating the testing tsconfig with the tests (#599) ([8919177](https://github.com/readmeio/oas/commit/8919177)), closes [#599](https://github.com/readmeio/oas/issues/599)
32
+
33
+
34
+
1
35
  ## <small>17.7.3 (2022-02-15)</small>
2
36
 
3
37
  * feat: adding a dereference option to preserve ref pointers as titles (#601) ([161aebf](https://github.com/readmeio/oas/commit/161aebf)), closes [#601](https://github.com/readmeio/oas/issues/601)
package/dist/index.js CHANGED
@@ -12,7 +12,11 @@ var __assign = (this && this.__assign) || function () {
12
12
  };
13
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
14
  if (k2 === undefined) k2 = k;
15
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
16
20
  }) : (function(o, m, k, k2) {
17
21
  if (k2 === undefined) k2 = k;
18
22
  o[k2] = m[k];
@@ -80,6 +84,7 @@ exports.Callback = operation_1.Callback;
80
84
  exports.Webhook = operation_1.Webhook;
81
85
  var utils_1 = __importStar(require("./utils"));
82
86
  exports.utils = utils_1["default"];
87
+ var SERVER_VARIABLE_REGEX = /{([-_a-zA-Z0-9:.[\]]+)}/g;
83
88
  function ensureProtocol(url) {
84
89
  // Add protocol to urls starting with // e.g. //example.com
85
90
  // This is because httpsnippet throws a HARError when it doesnt have a protocol
@@ -133,12 +138,12 @@ function normalizedUrl(api, selected) {
133
138
  *
134
139
  * For example, when given `https://{region}.node.example.com/v14` this will return back:
135
140
  *
136
- * https://([-_a-zA-Z0-9[\\]]+).node.example.com/v14
141
+ * https://([-_a-zA-Z0-9:.[\\]]+).node.example.com/v14
137
142
  *
138
143
  * @param url URL to transform
139
144
  */
140
145
  function transformUrlIntoRegex(url) {
141
- return stripTrailingSlash(url.replace(/{([-_a-zA-Z0-9[\]]+)}/g, '([-_a-zA-Z0-9[\\]]+)'));
146
+ return stripTrailingSlash(url.replace(SERVER_VARIABLE_REGEX, '([-_a-zA-Z0-9:.[\\]]+)'));
142
147
  }
143
148
  /**
144
149
  * Normalize a path so that we can use it with `path-to-regexp` to do operation lookups.
@@ -358,7 +363,7 @@ var Oas = /** @class */ (function () {
358
363
  // way we'll be able to extract the parameter names and match them up with the matched server that we obtained
359
364
  // above.
360
365
  var variables = {};
361
- Array.from(server.url.matchAll(/{([-_a-zA-Z0-9[\]]+)}/g)).forEach(function (variable, y) {
366
+ Array.from(server.url.matchAll(SERVER_VARIABLE_REGEX)).forEach(function (variable, y) {
362
367
  variables[variable[1]] = found[y + 1];
363
368
  });
364
369
  return {
@@ -392,7 +397,7 @@ var Oas = /** @class */ (function () {
392
397
  if (variables === void 0) { variables = {}; }
393
398
  // When we're constructing URLs, server URLs with trailing slashes cause problems with doing lookups, so if we have
394
399
  // one here on, slice it off.
395
- return stripTrailingSlash(url.replace(/{([-_a-zA-Z0-9[\]]+)}/g, function (original, key) {
400
+ return stripTrailingSlash(url.replace(SERVER_VARIABLE_REGEX, function (original, key) {
396
401
  var userVariable = (0, get_user_variable_1["default"])(_this.user, key);
397
402
  if (userVariable) {
398
403
  return userVariable;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -12,7 +12,11 @@ var __assign = (this && this.__assign) || function () {
12
12
  };
13
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
14
  if (k2 === undefined) k2 = k;
15
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
16
20
  }) : (function(o, m, k, k2) {
17
21
  if (k2 === undefined) k2 = k;
18
22
  o[k2] = m[k];
@@ -12,7 +12,11 @@ var __assign = (this && this.__assign) || function () {
12
12
  };
13
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
14
  if (k2 === undefined) k2 = k;
15
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
16
20
  }) : (function(o, m, k, k2) {
17
21
  if (k2 === undefined) k2 = k;
18
22
  o[k2] = m[k];
package/dist/operation.js CHANGED
@@ -16,7 +16,11 @@ var __extends = (this && this.__extends) || (function () {
16
16
  })();
17
17
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
18
  if (k2 === undefined) k2 = k;
19
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
20
24
  }) : (function(o, m, k, k2) {
21
25
  if (k2 === undefined) k2 = k;
22
26
  o[k2] = m[k];
@@ -282,20 +286,33 @@ var Operation = /** @class */ (function () {
282
286
  return 'operationId' in this.schema;
283
287
  };
284
288
  /**
285
- * Get an `operationId` for this operation. If one is not present (it's not required by the spec!) a hash of the path
286
- * and method will be returned instead.
289
+ * Get an `operationId` for this operation. If one is not present (it's not required by the spec!)
290
+ * a hash of the path and method will be returned instead.
287
291
  *
292
+ * @param opts
293
+ * @param opts.camelCase Generate a JS method-friendly operation ID when one isn't present.
288
294
  */
289
- Operation.prototype.getOperationId = function () {
295
+ Operation.prototype.getOperationId = function (opts) {
290
296
  if ('operationId' in this.schema) {
291
297
  return this.schema.operationId;
292
298
  }
293
- var url = this.path
299
+ var method = this.method.toLowerCase();
300
+ var operationId = this.path
294
301
  .replace(/[^a-zA-Z0-9]/g, '-') // Remove weird characters
295
302
  .replace(/^-|-$/g, '') // Don't start or end with -
296
303
  .replace(/--+/g, '-') // Remove double --'s
297
304
  .toLowerCase();
298
- return "".concat(this.method.toLowerCase(), "_").concat(url);
305
+ if (opts === null || opts === void 0 ? void 0 : opts.camelCase) {
306
+ operationId = operationId.replace(/[^a-zA-Z0-9]+(.)/g, function (_, chr) { return chr.toUpperCase(); });
307
+ // If the generated operationId already starts with the method (eg. `getPets`) we don't want
308
+ // to double it up into `getGetPets`.
309
+ if (operationId.startsWith(method)) {
310
+ return operationId;
311
+ }
312
+ operationId = operationId.charAt(0).toUpperCase() + operationId.slice(1);
313
+ return "".concat(method).concat(operationId);
314
+ }
315
+ return "".concat(method, "_").concat(operationId);
299
316
  };
300
317
  /**
301
318
  * Return an array of all tags, and their metadata, that exist on this operation.
@@ -354,6 +371,13 @@ var Operation = /** @class */ (function () {
354
371
  }
355
372
  return parameters;
356
373
  };
374
+ /**
375
+ * Determine if this operation has any required parameters.
376
+ *
377
+ */
378
+ Operation.prototype.hasRequiredParameters = function () {
379
+ return this.getParameters().some(function (param) { return 'required' in param && param.required; });
380
+ };
357
381
  /**
358
382
  * Convert the operation into an array of JSON Schema schemas for each available type of parameter available on the
359
383
  * operation.
@@ -361,7 +385,11 @@ var Operation = /** @class */ (function () {
361
385
  * @param globalDefaults Contains an object of user defined schema defaults.
362
386
  */
363
387
  Operation.prototype.getParametersAsJsonSchema = function (globalDefaults) {
364
- return (0, get_parameters_as_json_schema_1["default"])(this, this.api, globalDefaults);
388
+ if (this.parameterJsonSchema) {
389
+ return this.parameterJsonSchema;
390
+ }
391
+ this.parameterJsonSchema = (0, get_parameters_as_json_schema_1["default"])(this, this.api, globalDefaults);
392
+ return this.parameterJsonSchema;
365
393
  };
366
394
  /**
367
395
  * Get a single response for this status code, formatted as JSON schema.
@@ -403,6 +431,33 @@ var Operation = /** @class */ (function () {
403
431
  }
404
432
  return Object.keys(requestBody.content);
405
433
  };
434
+ /**
435
+ * Determine if this operation has a required request body.
436
+ *
437
+ */
438
+ Operation.prototype.hasRequiredRequestBody = function () {
439
+ if (!this.hasRequestBody()) {
440
+ return false;
441
+ }
442
+ var requestBody = this.schema.requestBody;
443
+ if (RMOAS.isRef(requestBody)) {
444
+ return false;
445
+ }
446
+ if (requestBody.required) {
447
+ return true;
448
+ }
449
+ // The OpenAPI spec isn't clear on the differentiation between schema `required` and
450
+ // `requestBody.required` because you can have required top-level schema properties but a
451
+ // non-required requestBody that negates each other.
452
+ //
453
+ // To kind of work ourselves around this and present a better QOL for this accessor, if at this
454
+ // final point where we don't have a required request body, but the underlying Media Type Object
455
+ // schema says that it has required properties then we should ultimately recognize that this
456
+ // request body is required -- even as the request body description says otherwise.
457
+ return !!this.getParametersAsJsonSchema()
458
+ .filter(function (js) { return ['body', 'formData'].includes(js.type); })
459
+ .find(function (js) { return js.schema && Array.isArray(js.schema.required) && js.schema.required.length; });
460
+ };
406
461
  /**
407
462
  * Retrieve a specific request body content schema off this operation.
408
463
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oas",
3
- "version": "17.7.3",
3
+ "version": "17.8.2",
4
4
  "description": "Working with OpenAPI definitions is hard. This makes it easier.",
5
5
  "license": "MIT",
6
6
  "author": "ReadMe <support@readme.io> (https://readme.com)",
@@ -62,7 +62,7 @@
62
62
  "oas-normalize": "^5.1.1",
63
63
  "openapi-types": "^10.0.0",
64
64
  "path-to-regexp": "^6.2.0",
65
- "swagger-inline": "^5.0.2"
65
+ "swagger-inline": "^5.1.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@commitlint/cli": "^16.0.1",
@@ -44,17 +44,20 @@ exports.findSwagger = async function (info, cb) {
44
44
  process.exit(1);
45
45
  }
46
46
 
47
- let pathGlob = '**/*';
47
+ let pathGlob = info.opts.pathGlob || '**/*';
48
48
  if (info.opts.path) {
49
49
  pathGlob = `${info.opts.path.replace(/\/$/, '')}/*`;
50
50
  }
51
51
 
52
- const generatedDefinition = await swaggerInline(pathGlob, { format: '.json', scope: info.opts.scope, base }).catch(
53
- err => {
54
- console.error(err);
55
- process.exit(1);
56
- }
57
- );
52
+ const generatedDefinition = await swaggerInline(pathGlob, {
53
+ format: '.json',
54
+ scope: info.opts.scope,
55
+ base,
56
+ pattern: info.opts.pattern || null,
57
+ }).catch(err => {
58
+ console.error(err);
59
+ process.exit(1);
60
+ });
58
61
 
59
62
  let oas = new OASNormalize(generatedDefinition);
60
63
  const bundledDefinition = await oas.bundle().catch(err => {
package/src/index.ts CHANGED
@@ -24,6 +24,8 @@ type PathMatches = PathMatch[];
24
24
 
25
25
  type Variables = Record<string, string | number | { default?: string | number }[] | { default?: string | number }>;
26
26
 
27
+ const SERVER_VARIABLE_REGEX = /{([-_a-zA-Z0-9:.[\]]+)}/g;
28
+
27
29
  function ensureProtocol(url: string) {
28
30
  // Add protocol to urls starting with // e.g. //example.com
29
31
  // This is because httpsnippet throws a HARError when it doesnt have a protocol
@@ -84,12 +86,12 @@ function normalizedUrl(api: RMOAS.OASDocument, selected: number) {
84
86
  *
85
87
  * For example, when given `https://{region}.node.example.com/v14` this will return back:
86
88
  *
87
- * https://([-_a-zA-Z0-9[\\]]+).node.example.com/v14
89
+ * https://([-_a-zA-Z0-9:.[\\]]+).node.example.com/v14
88
90
  *
89
91
  * @param url URL to transform
90
92
  */
91
93
  function transformUrlIntoRegex(url: string) {
92
- return stripTrailingSlash(url.replace(/{([-_a-zA-Z0-9[\]]+)}/g, '([-_a-zA-Z0-9[\\]]+)'));
94
+ return stripTrailingSlash(url.replace(SERVER_VARIABLE_REGEX, '([-_a-zA-Z0-9:.[\\]]+)'));
93
95
  }
94
96
 
95
97
  /**
@@ -365,7 +367,7 @@ export default class Oas {
365
367
  // way we'll be able to extract the parameter names and match them up with the matched server that we obtained
366
368
  // above.
367
369
  const variables: Record<string, string | number> = {};
368
- Array.from(server.url.matchAll(/{([-_a-zA-Z0-9[\]]+)}/g)).forEach((variable, y) => {
370
+ Array.from(server.url.matchAll(SERVER_VARIABLE_REGEX)).forEach((variable, y) => {
369
371
  variables[variable[1]] = found[y + 1];
370
372
  });
371
373
 
@@ -401,7 +403,7 @@ export default class Oas {
401
403
  // When we're constructing URLs, server URLs with trailing slashes cause problems with doing lookups, so if we have
402
404
  // one here on, slice it off.
403
405
  return stripTrailingSlash(
404
- url.replace(/{([-_a-zA-Z0-9[\]]+)}/g, (original: string, key: string) => {
406
+ url.replace(SERVER_VARIABLE_REGEX, (original: string, key: string) => {
405
407
  const userVariable = getUserVariable(this.user, key);
406
408
  if (userVariable) {
407
409
  return userVariable as string;
package/src/operation.ts CHANGED
@@ -2,6 +2,7 @@ import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
2
2
  import type { RequestBodyExamples } from './operation/get-requestbody-examples';
3
3
  import type { CallbackExamples } from './operation/get-callback-examples';
4
4
  import type { ResponseExamples } from './operation/get-response-examples';
5
+ import type { SchemaWrapper } from './operation/get-parameters-as-json-schema';
5
6
 
6
7
  import * as RMOAS from './rmoas.types';
7
8
  import dedupeCommonParameters from './lib/dedupe-common-parameters';
@@ -65,6 +66,11 @@ export default class Operation {
65
66
  response: string[];
66
67
  };
67
68
 
69
+ /**
70
+ * All parameters and request bodies converted into JSON Schema.
71
+ */
72
+ parameterJsonSchema: SchemaWrapper[];
73
+
68
74
  constructor(api: RMOAS.OASDocument, path: string, method: RMOAS.HttpMethods, operation: RMOAS.OperationObject) {
69
75
  this.schema = operation;
70
76
  this.api = api;
@@ -323,22 +329,38 @@ export default class Operation {
323
329
  }
324
330
 
325
331
  /**
326
- * Get an `operationId` for this operation. If one is not present (it's not required by the spec!) a hash of the path
327
- * and method will be returned instead.
332
+ * Get an `operationId` for this operation. If one is not present (it's not required by the spec!)
333
+ * a hash of the path and method will be returned instead.
328
334
  *
335
+ * @param opts
336
+ * @param opts.camelCase Generate a JS method-friendly operation ID when one isn't present.
329
337
  */
330
- getOperationId(): string {
338
+ getOperationId(opts?: { camelCase: boolean }): string {
331
339
  if ('operationId' in this.schema) {
332
340
  return this.schema.operationId;
333
341
  }
334
342
 
335
- const url = this.path
343
+ const method = this.method.toLowerCase();
344
+ let operationId = this.path
336
345
  .replace(/[^a-zA-Z0-9]/g, '-') // Remove weird characters
337
346
  .replace(/^-|-$/g, '') // Don't start or end with -
338
347
  .replace(/--+/g, '-') // Remove double --'s
339
348
  .toLowerCase();
340
349
 
341
- return `${this.method.toLowerCase()}_${url}`;
350
+ if (opts?.camelCase) {
351
+ operationId = operationId.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase());
352
+
353
+ // If the generated operationId already starts with the method (eg. `getPets`) we don't want
354
+ // to double it up into `getGetPets`.
355
+ if (operationId.startsWith(method)) {
356
+ return operationId;
357
+ }
358
+
359
+ operationId = operationId.charAt(0).toUpperCase() + operationId.slice(1);
360
+ return `${method}${operationId}`;
361
+ }
362
+
363
+ return `${method}_${operationId}`;
342
364
  }
343
365
 
344
366
  /**
@@ -405,6 +427,14 @@ export default class Operation {
405
427
  return parameters;
406
428
  }
407
429
 
430
+ /**
431
+ * Determine if this operation has any required parameters.
432
+ *
433
+ */
434
+ hasRequiredParameters() {
435
+ return this.getParameters().some(param => 'required' in param && param.required);
436
+ }
437
+
408
438
  /**
409
439
  * Convert the operation into an array of JSON Schema schemas for each available type of parameter available on the
410
440
  * operation.
@@ -412,7 +442,12 @@ export default class Operation {
412
442
  * @param globalDefaults Contains an object of user defined schema defaults.
413
443
  */
414
444
  getParametersAsJsonSchema(globalDefaults?: Record<string, unknown>) {
415
- return getParametersAsJsonSchema(this, this.api, globalDefaults);
445
+ if (this.parameterJsonSchema) {
446
+ return this.parameterJsonSchema;
447
+ }
448
+
449
+ this.parameterJsonSchema = getParametersAsJsonSchema(this, this.api, globalDefaults);
450
+ return this.parameterJsonSchema;
416
451
  }
417
452
 
418
453
  /**
@@ -461,6 +496,37 @@ export default class Operation {
461
496
  return Object.keys(requestBody.content);
462
497
  }
463
498
 
499
+ /**
500
+ * Determine if this operation has a required request body.
501
+ *
502
+ */
503
+ hasRequiredRequestBody() {
504
+ if (!this.hasRequestBody()) {
505
+ return false;
506
+ }
507
+
508
+ const requestBody = this.schema.requestBody;
509
+ if (RMOAS.isRef(requestBody)) {
510
+ return false;
511
+ }
512
+
513
+ if (requestBody.required) {
514
+ return true;
515
+ }
516
+
517
+ // The OpenAPI spec isn't clear on the differentiation between schema `required` and
518
+ // `requestBody.required` because you can have required top-level schema properties but a
519
+ // non-required requestBody that negates each other.
520
+ //
521
+ // To kind of work ourselves around this and present a better QOL for this accessor, if at this
522
+ // final point where we don't have a required request body, but the underlying Media Type Object
523
+ // schema says that it has required properties then we should ultimately recognize that this
524
+ // request body is required -- even as the request body description says otherwise.
525
+ return !!this.getParametersAsJsonSchema()
526
+ .filter(js => ['body', 'formData'].includes(js.type))
527
+ .find(js => js.schema && Array.isArray(js.schema.required) && js.schema.required.length);
528
+ }
529
+
464
530
  /**
465
531
  * Retrieve a specific request body content schema off this operation.
466
532
  *
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "module": "es6",
5
- "moduleResolution": "node",
6
- "noImplicitAny": false,
7
- },
8
- "include": ["src/**/*", "__tests__/**/*"]
9
- }