Source: index.js

  1. /**
  2. * @copyright Copyright 2019-2020 Kevin Locke <kevin@kevinlocke.name>
  3. * @license MIT
  4. * @module openapi-transformer-base
  5. */
  6. 'use strict';
  7. const { METHODS } = require('http');
  8. const { debuglog } = require('util');
  9. const toJsonPointer = require('./lib/to-json-pointer.js');
  10. const visit = require('./visit.js');
  11. const { isArray } = Array;
  12. const debug = debuglog('openapi-transformer-base');
  13. /** HTTP method names which are properties of a Path Item Object that have
  14. * Operation Object values.
  15. *
  16. * @private
  17. */
  18. const httpMethodSet = new Set(METHODS.map((method) => method.toLowerCase()));
  19. /** Transforms a value which has type object<string,ValueType> but is not
  20. * defined as Map[string,ValueType] in OpenAPI.
  21. *
  22. * Note: This is currently used for schema properties, where #transformMap()
  23. * often complicates transformations due to differences with Map[string,Schema]
  24. * on definitions/components.schema and complicates optimizations.
  25. *
  26. * @private
  27. * @template ValueType, TransformedType
  28. * @this {!OpenApiTransformerBase}
  29. * @param {!object<string,ValueType>|*} obj Map-like object to transform.
  30. * @param {function(this:!OpenApiTransformerBase, ValueType): TransformedType
  31. * } transform Method which transforms values in obj.
  32. * @param {string} logName Name of object being transformed (for logging).
  33. * @param {boolean=} skipExtensions If true, do not call transform on {@link
  34. * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#specificationExtensions
  35. * Specification Extensions} (i.e. properties starting with "x-").
  36. * Such properties are copied to the returned object without transformation.
  37. * @returns {!object<string,TransformedType>|*} If obj is a Map, a plain object
  38. * with the same own enumerable string-keyed properties as obj with values
  39. * returned by transform. Otherwise, obj is returned unchanged.
  40. */
  41. function transformMapLike(obj, transform, logName, skipExtensions) {
  42. if (typeof obj !== 'object' || obj === null) {
  43. this.warn(`Ignoring non-object ${logName}`, obj);
  44. return obj;
  45. }
  46. if (isArray(obj)) {
  47. // Note: This function is only called for values specified as Map[X,Y]
  48. // in the OpenAPI Specification. Array values are invalid and it would
  49. // be unsafe to assume that their contents are type Y. Return unchanged.
  50. this.warn(`Ignoring non-object ${logName}`, obj);
  51. return obj;
  52. }
  53. const newObj = { ...obj };
  54. for (const [propName, propValue] of Object.entries(obj)) {
  55. if (propValue !== undefined
  56. && (!skipExtensions || !propName.startsWith('x-'))) {
  57. newObj[propName] = visit(this, transform, propName, propValue);
  58. }
  59. }
  60. return newObj;
  61. }
  62. /** Base class for traversing or transforming OpenAPI 2.x or 3.x documents
  63. * using a modified visitor design pattern to traverse object types within
  64. * the OpenAPI document tree.
  65. *
  66. * This class uses the following conventions:
  67. *
  68. * <ul>
  69. * <li>Objects passed as arguments are never modified by this class.
  70. * Subclasses are encouraged to maintain this invariant by returning modified
  71. * copies rather than modifying the argument objects.</li>
  72. * <li>JSON References, if present, are passed to the transform method for
  73. * the type required by their position. (e.g. a schema $ref is passed to
  74. * {@link transformSchema}).</li>
  75. * <li>Properties which are not defined in the OpenAPI specification are
  76. * preserved in returned objects unchanged. (e.g. <code>x-</code> extension
  77. * properties)</li>
  78. * <li>The order that properties are visited is not defined and may change
  79. * in future versions.</li>
  80. * <li>The behavior of this class is not conditional on the declared OpenAPI
  81. * version in the document. It will traverse properties which are present,
  82. * regardless of whether they are specified in the declared version.</li>
  83. * <li>Callers may begin traversal at any point in the document (e.g. by
  84. * calling {@link transformSchema} directly, instead of transitively through
  85. * {@link transformOpenApi}).</li>
  86. * <li>No validation is performed on the OpenAPI document and every attempt is
  87. * made to handle invalid values gracefully. Child classes should be
  88. * prepared for arguments with any type or value if the document may not be
  89. * valid.</li>
  90. * <li>Minimal effort is made to handle or preserve values which are not
  91. * representable in JSON (e.g. Date, RegExp, non-Object.prototype, Symbol
  92. * and non-enumerable properties, etc.), which are usually treated as generic
  93. * objects.</li>
  94. * <li>One exception to the previous point: transform methods are not called on
  95. * <code>undefined</code> values, which are treated like missing properties
  96. * except that they are copied to the transformed object to preserve the
  97. * object shape.</li>
  98. * <li>Returned values are added to the transformed object, regardless of value.
  99. * Therefore, unless overridden, returned objects will have the same
  100. * properties as the original object, some of which may be undefined.</li>
  101. * </ul>
  102. */
  103. class OpenApiTransformerBase {
  104. constructor() {
  105. /** Property names traversed in current transformation.
  106. *
  107. * @type {!Array<string>}
  108. */
  109. Object.defineProperty(this, 'transformPath', { value: [] });
  110. }
  111. /** Transforms an <code>Array[ValueType]</code> using a given transform
  112. * method.
  113. *
  114. * @template ValueType, TransformedType
  115. * @param {!Array<ValueType>|*} arr Array to transform.
  116. * @param {function(this:!OpenApiTransformerBase, ValueType): TransformedType
  117. * } transform Method which transforms values in arr.
  118. * @returns {!Array<TransformedType>|*} If arr is an Array, the result of
  119. * Array#map(transform). Otherwise, obj is returned unchanged.
  120. */
  121. transformArray(arr, transform) {
  122. if (!isArray(arr)) {
  123. this.warn('Ignoring non-Array', arr);
  124. return arr;
  125. }
  126. // eslint-disable-next-line unicorn/no-array-method-this-argument
  127. return arr.map(transform, this);
  128. }
  129. /** Transforms a <code>Map[string, ValueType]</code> using a given transform
  130. * method.
  131. *
  132. * Similar to modify-values and _.mapValues from lodash.
  133. *
  134. * Unlike the above:
  135. * <ul>
  136. * <li>If the first argument is not an object, it is returned unchanged.</li>
  137. * <li>If the first argument is an Array, it is returned unchanged.</li>
  138. * <li>If the first argument is a non-null object, the returned object will
  139. * be a new object with prototype Object.prototype and properties matching
  140. * the first argument with transformed values.</li>
  141. * </ul>
  142. *
  143. * @template ValueType, TransformedType
  144. * @param {!object<string,ValueType>|*} obj Map to transform.
  145. * @param {function(this:!OpenApiTransformerBase, ValueType): TransformedType
  146. * } transform Method which transforms values in obj.
  147. * @returns {!object<string,TransformedType>|*} If obj is a Map, a plain
  148. * object with the same own enumerable string-keyed properties as obj with
  149. * values returned by transform. Otherwise, obj is returned unchanged.
  150. */
  151. transformMap(obj, transform) {
  152. return transformMapLike.call(this, obj, transform, 'Map');
  153. }
  154. /** Transforms a {@link
  155. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminatorObject
  156. * Discriminator Object}.
  157. *
  158. * @param {!object} discriminator Discriminator Object.
  159. * @returns {!object} Transformed Discriminator Object.
  160. */
  161. // eslint-disable-next-line class-methods-use-this
  162. transformDiscriminator(discriminator) {
  163. return discriminator;
  164. }
  165. /** Transforms an {@link
  166. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#exampleObject
  167. * OpenAPI 2.0 Example Object}.
  168. *
  169. * @param {!object} example OpenAPI 2.0 Example Object.
  170. * @returns {!object} Transformed Example Object.
  171. */
  172. // eslint-disable-next-line class-methods-use-this
  173. transformExample(example) {
  174. return example;
  175. }
  176. /** Transforms an {@link
  177. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject
  178. * OpenAPI 3.x Example Object}.
  179. *
  180. * @param {!object} example OpenAPI 3.x Example Object.
  181. * @returns {!object} Transformed Example Object.
  182. */
  183. // eslint-disable-next-line class-methods-use-this
  184. transformExample3(example) {
  185. return example;
  186. }
  187. /** Transforms an {@link
  188. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#externalDocumentationObject
  189. * External Documentation Object}.
  190. *
  191. * @param {!object} externalDocs External Documentation Object.
  192. * @returns {!object} Transformed External Documentation Object.
  193. */
  194. // eslint-disable-next-line class-methods-use-this
  195. transformExternalDocs(externalDocs) {
  196. return externalDocs;
  197. }
  198. /** Transforms an {@link
  199. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#xmlObject
  200. * XML Object}.
  201. *
  202. * @param {!object} xml XML Object.
  203. * @returns {!object} Transformed XML Object.
  204. */
  205. // eslint-disable-next-line class-methods-use-this
  206. transformXml(xml) {
  207. return xml;
  208. }
  209. /** Transforms a {@link
  210. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
  211. * Schema Object}.
  212. *
  213. * Note: For OpenAPI 2.0 documents, consider overriding
  214. * {@see transformParameter}, {@see transformItems}, and
  215. * {@see transformHeader} to transform all schema-like objects.
  216. *
  217. * @param {!object} schema Schema Object.
  218. * @returns {!object} Transformed Schema Object.
  219. */
  220. transformSchema(schema) {
  221. if (typeof schema !== 'object' || schema === null || isArray(schema)) {
  222. // Note: JSON Schema Core draft-handrews-json-schema-02 (referenced by
  223. // current/ OpenAPI 3.1 drafts) defines true and false as valid schemas
  224. // with true equivalent to {} and false equivalent to {not:{}}
  225. // https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.4.3.2
  226. if (typeof schema !== 'boolean') {
  227. this.warn('Ignoring non-object Schema', schema);
  228. }
  229. return schema;
  230. }
  231. const newSchema = { ...schema };
  232. const {
  233. contains,
  234. dependentSchemas,
  235. discriminator,
  236. externalDocs,
  237. items,
  238. patternProperties,
  239. properties,
  240. propertyNames,
  241. unevaluatedItems,
  242. unevaluatedProperties,
  243. xml,
  244. } = schema;
  245. if (discriminator !== undefined) {
  246. newSchema.discriminator = visit(
  247. this,
  248. this.transformDiscriminator,
  249. 'discriminator',
  250. discriminator,
  251. );
  252. }
  253. if (externalDocs !== undefined) {
  254. newSchema.externalDocs = visit(
  255. this,
  256. this.transformExternalDocs,
  257. 'externalDocs',
  258. externalDocs,
  259. );
  260. }
  261. if (xml !== undefined) {
  262. newSchema.xml = visit(this, this.transformXml, 'xml', xml);
  263. }
  264. if (items !== undefined) {
  265. // Note: OpenAPI 3.0 disallows Arrays, 2.0 and 3.1 drafts allow it
  266. if (isArray(items)) {
  267. newSchema.items = this.transformArray(items, this.transformSchema);
  268. } else {
  269. newSchema.items = visit(this, this.transformSchema, 'items', items);
  270. }
  271. }
  272. for (const schemaProp of ['if', 'then', 'else', 'not']) {
  273. const subSchema = schema[schemaProp];
  274. if (subSchema !== undefined) {
  275. newSchema[schemaProp] = visit(
  276. this,
  277. this.transformSchema,
  278. 'subSchema',
  279. subSchema,
  280. );
  281. }
  282. }
  283. if (properties !== undefined) {
  284. newSchema.properties = visit(
  285. this,
  286. this.transformSchemaProperties,
  287. 'properties',
  288. properties,
  289. );
  290. }
  291. if (patternProperties !== undefined) {
  292. newSchema.patternProperties = visit(
  293. this,
  294. transformMapLike,
  295. 'patternProperties',
  296. patternProperties,
  297. this.transformSchema,
  298. 'Schema',
  299. );
  300. }
  301. if (unevaluatedProperties !== undefined) {
  302. newSchema.unevaluatedProperties = visit(
  303. this,
  304. this.transformSchema,
  305. 'unevaluatedProperties',
  306. unevaluatedProperties,
  307. );
  308. }
  309. if (propertyNames !== undefined) {
  310. newSchema.propertyNames = visit(
  311. this,
  312. this.transformSchema,
  313. 'propertyNames',
  314. propertyNames,
  315. );
  316. }
  317. // Note: JSON Schema Core draft-handrews-json-schema-02 (referenced by
  318. // current/ OpenAPI 3.1 drafts) defines true and false as valid schemas
  319. // with true equivalent to {} and false equivalent to {not:{}}
  320. // https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.4.3.2
  321. // so they are now passed to transformSchema.
  322. for (const schemaProp of ['additionalItems', 'additionalProperties']) {
  323. const additionalItemsProps = schema[schemaProp];
  324. if (additionalItemsProps !== undefined) {
  325. newSchema[schemaProp] = visit(
  326. this,
  327. this.transformSchema,
  328. schemaProp,
  329. additionalItemsProps,
  330. );
  331. }
  332. }
  333. if (unevaluatedItems !== undefined) {
  334. newSchema.unevaluatedItems =
  335. visit(this, this.transformSchema, 'unevaluatedItems', unevaluatedItems);
  336. }
  337. if (dependentSchemas !== undefined) {
  338. newSchema.dependentSchemas = visit(
  339. this,
  340. transformMapLike,
  341. 'dependentSchemas',
  342. dependentSchemas,
  343. this.transformSchema,
  344. 'Schema',
  345. );
  346. }
  347. if (contains !== undefined) {
  348. newSchema.contains = visit(
  349. this,
  350. this.transformSchema,
  351. 'contains',
  352. contains,
  353. );
  354. }
  355. for (const schemaProp of ['allOf', 'anyOf', 'oneOf']) {
  356. const subSchemas = schema[schemaProp];
  357. if (subSchemas !== undefined) {
  358. newSchema[schemaProp] = visit(
  359. this,
  360. this.transformArray,
  361. schemaProp,
  362. subSchemas,
  363. this.transformSchema,
  364. );
  365. }
  366. }
  367. // Note: The examples property is an Array of values, as defined by JSON
  368. // Schema, and is therefore not suitable for any transformExample* method.
  369. // See https://github.com/OAI/OpenAPI-Specification/issues/2094
  370. return newSchema;
  371. }
  372. /** Transforms {@link
  373. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
  374. * Schema Object} properties.
  375. *
  376. * @param {!object} properties Schema Object properties.
  377. * @returns {!object} Transformed Schema Object properties.
  378. */
  379. transformSchemaProperties(properties) {
  380. return transformMapLike.call(
  381. this,
  382. properties,
  383. this.transformSchema,
  384. 'Schema properties',
  385. );
  386. }
  387. /** Transforms an {@link
  388. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#itemsObject
  389. * Items Object}.
  390. *
  391. * Note: Items Object is a subset of Schema Object with the addition of
  392. * collectionFormat.
  393. *
  394. * @param {!object} items Items Object.
  395. * @returns {!object} Transformed Items Object.
  396. */
  397. transformItems(items) {
  398. if (typeof items !== 'object' || items === null || isArray(items)) {
  399. this.warn('Ignoring non-object Items', items);
  400. return items;
  401. }
  402. if (items.items === undefined) {
  403. return items;
  404. }
  405. return {
  406. ...items,
  407. items: visit(this, this.transformItems, 'items', items.items),
  408. };
  409. }
  410. /** Transforms a {@link
  411. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#headerObject
  412. * Header Object}.
  413. *
  414. * @param {!object} header Header Object.
  415. * @returns {!object} Transformed Header Object.
  416. */
  417. transformHeader(header) {
  418. if (typeof header !== 'object'
  419. || header === null
  420. || isArray(header)) {
  421. this.warn('Ignoring non-object Header', header);
  422. return header;
  423. }
  424. const newHeader = { ...header };
  425. if (header.items !== undefined) {
  426. newHeader.items = visit(this, this.transformItems, 'items', header.items);
  427. }
  428. if (header.schema !== undefined) {
  429. newHeader.schema = visit(
  430. this,
  431. this.transformSchema,
  432. 'schema',
  433. header.schema,
  434. );
  435. }
  436. return newHeader;
  437. }
  438. /** Transforms a {@link
  439. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encodingObject
  440. * Encoding Object}.
  441. *
  442. * @param {!object} encoding Encoding Object.
  443. * @returns {!object} Transformed Encoding Object.
  444. */
  445. transformEncoding(encoding) {
  446. if (typeof encoding !== 'object'
  447. || encoding === null
  448. || isArray(encoding)) {
  449. this.warn('Ignoring non-object Encoding', encoding);
  450. return encoding;
  451. }
  452. if (encoding.headers === undefined) {
  453. return encoding;
  454. }
  455. return {
  456. ...encoding,
  457. headers: visit(
  458. this,
  459. this.transformMap,
  460. 'headers',
  461. encoding.headers,
  462. this.transformHeader,
  463. ),
  464. };
  465. }
  466. /** Transforms an {@link
  467. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#linkObject
  468. * Link Object}.
  469. *
  470. * @param {!object} link Link Object.
  471. * @returns {!object} Transformed Link Object.
  472. */
  473. transformLink(link) {
  474. if (typeof link !== 'object'
  475. || link === null
  476. || isArray(link)) {
  477. this.warn('Ignoring non-object Link', link);
  478. return link;
  479. }
  480. if (link.server === undefined) {
  481. return link;
  482. }
  483. return {
  484. ...link,
  485. server: visit(this, this.transformServer, 'server', link.server),
  486. };
  487. }
  488. /** Transforms a {@link
  489. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#mediaTypeObject
  490. * Media Type Object}.
  491. *
  492. * @param {!object} mediaType Media Type Object.
  493. * @returns {!object} Transformed Media Type Object.
  494. */
  495. transformMediaType(mediaType) {
  496. if (typeof mediaType !== 'object'
  497. || mediaType === null
  498. || isArray(mediaType)) {
  499. this.warn('Ignoring non-object Media Type', mediaType);
  500. return mediaType;
  501. }
  502. const newMediaType = { ...mediaType };
  503. if (mediaType.schema !== undefined) {
  504. newMediaType.schema = visit(
  505. this,
  506. this.transformSchema,
  507. 'schema',
  508. mediaType.schema,
  509. );
  510. }
  511. if (mediaType.examples !== undefined) {
  512. newMediaType.examples = visit(
  513. this,
  514. this.transformMap,
  515. 'examples',
  516. mediaType.examples,
  517. this.transformExample3,
  518. );
  519. }
  520. if (mediaType.encoding !== undefined) {
  521. newMediaType.encoding = visit(
  522. this,
  523. this.transformMap,
  524. 'encoding',
  525. mediaType.encoding,
  526. this.transformEncoding,
  527. );
  528. }
  529. return newMediaType;
  530. }
  531. /** Transforms a {@link
  532. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject
  533. * Response Object}.
  534. *
  535. * @param {!object} response Response Object.
  536. * @returns {!object} Transformed Response Object.
  537. */
  538. transformResponse(response) {
  539. if (typeof response !== 'object'
  540. || response === null
  541. || isArray(response)) {
  542. this.warn('Ignoring non-object Response', response);
  543. return response;
  544. }
  545. const newResponse = { ...response };
  546. if (response.headers !== undefined) {
  547. newResponse.headers = visit(
  548. this,
  549. this.transformMap,
  550. 'headers',
  551. response.headers,
  552. this.transformHeader,
  553. );
  554. }
  555. if (response.content !== undefined) {
  556. newResponse.content = visit(
  557. this,
  558. this.transformMap,
  559. 'content',
  560. response.content,
  561. this.transformMediaType,
  562. );
  563. }
  564. if (response.links !== undefined) {
  565. newResponse.links = visit(
  566. this,
  567. this.transformMap,
  568. 'links',
  569. response.links,
  570. this.transformLink,
  571. );
  572. }
  573. if (response.schema !== undefined) {
  574. newResponse.schema = visit(
  575. this,
  576. this.transformSchema,
  577. 'schema',
  578. response.schema,
  579. );
  580. }
  581. if (response.examples !== undefined) {
  582. newResponse.examples = visit(
  583. this,
  584. this.transformExample,
  585. 'examples',
  586. response.examples,
  587. );
  588. }
  589. return newResponse;
  590. }
  591. /** Transforms a {@link
  592. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
  593. * Parameter Object}.
  594. *
  595. * Note: In OpenAPI 2.0, Parameter Object shares many properties with
  596. * Schema Object (when <code>.in !== 'body'</code>).
  597. *
  598. * @param {!object} parameter Parameter Object.
  599. * @returns {!object} Transformed Parameter Object.
  600. */
  601. transformParameter(parameter) {
  602. if (typeof parameter !== 'object'
  603. || parameter === null
  604. || isArray(parameter)) {
  605. this.warn('Ignoring non-object Parameter', parameter);
  606. return parameter;
  607. }
  608. const newParameter = { ...parameter };
  609. if (parameter.content !== undefined) {
  610. newParameter.content = visit(
  611. this,
  612. this.transformMap,
  613. 'content',
  614. parameter.content,
  615. this.transformMediaType,
  616. );
  617. }
  618. if (parameter.schema !== undefined) {
  619. newParameter.schema = visit(
  620. this,
  621. this.transformSchema,
  622. 'schema',
  623. parameter.schema,
  624. );
  625. }
  626. if (parameter.items !== undefined) {
  627. newParameter.items = visit(
  628. this,
  629. this.transformItems,
  630. 'items',
  631. parameter.items,
  632. );
  633. }
  634. if (parameter.examples !== undefined) {
  635. newParameter.examples = visit(
  636. this,
  637. this.transformMap,
  638. 'examples',
  639. parameter.examples,
  640. this.transformExample3,
  641. );
  642. }
  643. return newParameter;
  644. }
  645. /** Transforms a {@link
  646. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject
  647. * Responses Object}.
  648. *
  649. * @param {!object} responses Responses Object.
  650. * @returns {!object} Transformed Response Object.
  651. */
  652. transformResponses(responses) {
  653. if (!responses || typeof responses !== 'object' || isArray(responses)) {
  654. this.warn('Ignoring non-object Responses', responses);
  655. return responses;
  656. }
  657. const newResponses = { ...responses };
  658. for (const prop of Object.keys(responses)) {
  659. // Only "default", HTTP response codes, and HTTP response code patterns
  660. // are defined to contain Response Object. Other properties may be
  661. // extensions or defined as something else in future OpenAPI versions.
  662. //
  663. // Match using pattern similar to one mentioned in
  664. // https://github.com/OAI/OpenAPI-Specification/issues/2471#issuecomment-781362295
  665. // Be lenient about upper/lowercase, and single x in last 2 positions.
  666. // Although lowercase and single x are not valid, the risk of being
  667. // anything other than a response object is low enough to justify.
  668. if (prop === 'default' || /^[1-5][0-9Xx][0-9Xx]$/.test(prop)) {
  669. const response = responses[prop];
  670. if (response !== undefined) {
  671. newResponses[prop] = visit(
  672. this,
  673. this.transformResponse,
  674. prop,
  675. response,
  676. );
  677. }
  678. } else if (prop !== '$ref' && !prop.startsWith('x-')) {
  679. this.warn('Ignoring unrecognized property of Responses', prop);
  680. }
  681. }
  682. return newResponses;
  683. }
  684. /** Transforms a {@link
  685. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#callbackObject
  686. * Callback Object}.
  687. *
  688. * @param {!object} callback Callback Object.
  689. * @returns {!object} Transformed Callback Object.
  690. */
  691. transformCallback(callback) {
  692. return transformMapLike.call(
  693. this,
  694. callback,
  695. this.transformPathItem,
  696. 'Callback',
  697. true,
  698. );
  699. }
  700. /** Transforms a {@link
  701. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#requestBodyObject
  702. * Request Body Object}.
  703. *
  704. * @param {!object} requestBody Request Body Object.
  705. * @returns {!object} Transformed Request Body Object.
  706. */
  707. transformRequestBody(requestBody) {
  708. if (typeof requestBody !== 'object'
  709. || requestBody === null
  710. || isArray(requestBody)) {
  711. this.warn('Ignoring non-object Request Body', requestBody);
  712. return requestBody;
  713. }
  714. if (requestBody.content === undefined) {
  715. return requestBody;
  716. }
  717. return {
  718. ...requestBody,
  719. content: visit(
  720. this,
  721. this.transformMap,
  722. 'content',
  723. requestBody.content,
  724. this.transformMediaType,
  725. ),
  726. };
  727. }
  728. /** Transforms a {@link
  729. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
  730. * Operation Object}.
  731. *
  732. * @param {!object} operation Operation Object.
  733. * @returns {!object} Transformed Operation Object.
  734. */
  735. transformOperation(operation) {
  736. if (typeof operation !== 'object'
  737. || operation === null
  738. || isArray(operation)) {
  739. this.warn('Ignoring non-object Operation', operation);
  740. return operation;
  741. }
  742. const newOperation = { ...operation };
  743. if (operation.externalDocs !== undefined) {
  744. newOperation.externalDocs = visit(
  745. this,
  746. this.transformExternalDocs,
  747. 'externalDocs',
  748. operation.externalDocs,
  749. );
  750. }
  751. if (operation.parameters !== undefined) {
  752. newOperation.parameters =
  753. this.transformArray(operation.parameters, this.transformParameter);
  754. }
  755. if (operation.requestBody !== undefined) {
  756. newOperation.requestBody = visit(
  757. this,
  758. this.transformRequestBody,
  759. 'requestBody',
  760. operation.requestBody,
  761. );
  762. }
  763. if (operation.responses !== undefined) {
  764. newOperation.responses = visit(
  765. this,
  766. this.transformResponses,
  767. 'responses',
  768. operation.responses,
  769. );
  770. }
  771. if (operation.callbacks !== undefined) {
  772. newOperation.callbacks = visit(
  773. this,
  774. this.transformMap,
  775. 'callbacks',
  776. operation.callbacks,
  777. this.transformCallback,
  778. );
  779. }
  780. if (operation.security !== undefined) {
  781. newOperation.security = this.transformArray(
  782. operation.security,
  783. this.transformSecurityRequirement,
  784. );
  785. }
  786. if (operation.servers !== undefined) {
  787. newOperation.servers =
  788. this.transformArray(operation.servers, this.transformServer);
  789. }
  790. return newOperation;
  791. }
  792. /** Transforms a {@link
  793. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject
  794. * Path Item Object}.
  795. *
  796. * @param {!object} pathItem Path Item Object.
  797. * @returns {!object} Transformed Path Item Object.
  798. */
  799. transformPathItem(pathItem) {
  800. if (typeof pathItem !== 'object'
  801. || pathItem === null
  802. || isArray(pathItem)) {
  803. this.warn('Ignoring non-object Path Item', pathItem);
  804. return pathItem;
  805. }
  806. const newPathItem = { ...pathItem };
  807. if (pathItem.servers !== undefined) {
  808. newPathItem.servers =
  809. this.transformArray(pathItem.servers, this.transformServer);
  810. }
  811. if (pathItem.parameters !== undefined) {
  812. newPathItem.parameters =
  813. this.transformArray(pathItem.parameters, this.transformParameter);
  814. }
  815. for (const [method, operation] of Object.entries(pathItem)) {
  816. if (operation !== undefined && httpMethodSet.has(method.toLowerCase())) {
  817. newPathItem[method] = visit(
  818. this,
  819. this.transformOperation,
  820. method,
  821. operation,
  822. );
  823. } else if (method !== '$ref'
  824. && method !== 'description'
  825. && method !== 'parameters'
  826. && method !== 'servers'
  827. && method !== 'summary'
  828. && !method.startsWith('x-')) {
  829. this.warn('Ignoring unrecognized property of Path Item', method);
  830. }
  831. }
  832. return newPathItem;
  833. }
  834. /** Transforms a {@link
  835. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathsObject
  836. * Paths Object}.
  837. *
  838. * Note: Paths Object may be traversed from the x-ms-paths property in
  839. * addition to the paths property of the OpenAPI Object.
  840. *
  841. * @param {!object} paths Paths Object.
  842. * @returns {!object} Transformed Paths Object.
  843. */
  844. transformPaths(paths) {
  845. return transformMapLike.call(
  846. this,
  847. paths,
  848. this.transformPathItem,
  849. 'Paths',
  850. true,
  851. );
  852. }
  853. /** Transforms a {@link
  854. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
  855. * Components Object}.
  856. *
  857. * @param {!object} components Components Object.
  858. * @returns {!object} Transformed Components Object.
  859. */
  860. transformComponents(components) {
  861. if (typeof components !== 'object'
  862. || components === null
  863. || isArray(components)) {
  864. this.warn('Ignoring non-object Components', components);
  865. return components;
  866. }
  867. const newComponents = { ...components };
  868. if (components.schemas !== undefined) {
  869. newComponents.schemas = visit(
  870. this,
  871. this.transformMap,
  872. 'schemas',
  873. components.schemas,
  874. this.transformSchema,
  875. );
  876. }
  877. if (components.responses !== undefined) {
  878. newComponents.responses = visit(
  879. this,
  880. this.transformMap,
  881. 'responses',
  882. components.responses,
  883. this.transformResponse,
  884. );
  885. }
  886. if (components.parameters !== undefined) {
  887. newComponents.parameters = visit(
  888. this,
  889. this.transformMap,
  890. 'parameters',
  891. components.parameters,
  892. this.transformParameter,
  893. );
  894. }
  895. if (components.examples !== undefined) {
  896. newComponents.examples = visit(
  897. this,
  898. this.transformMap,
  899. 'examples',
  900. components.examples,
  901. this.transformExample3,
  902. );
  903. }
  904. if (components.requestBodies !== undefined) {
  905. newComponents.requestBodies = visit(
  906. this,
  907. this.transformMap,
  908. 'requestBodies',
  909. components.requestBodies,
  910. this.transformRequestBody,
  911. );
  912. }
  913. if (components.headers !== undefined) {
  914. newComponents.headers = visit(
  915. this,
  916. this.transformMap,
  917. 'headers',
  918. components.headers,
  919. this.transformHeader,
  920. );
  921. }
  922. if (components.securitySchemes !== undefined) {
  923. newComponents.securitySchemes = visit(
  924. this,
  925. this.transformMap,
  926. 'securitySchemes',
  927. components.securitySchemes,
  928. this.transformSecurityScheme,
  929. );
  930. }
  931. if (components.links !== undefined) {
  932. newComponents.links = visit(
  933. this,
  934. this.transformMap,
  935. 'links',
  936. components.links,
  937. this.transformLink,
  938. );
  939. }
  940. if (components.callbacks !== undefined) {
  941. newComponents.callbacks = visit(
  942. this,
  943. this.transformMap,
  944. 'callbacks',
  945. components.callbacks,
  946. this.transformCallback,
  947. );
  948. }
  949. if (components.pathItems !== undefined) {
  950. newComponents.pathItems = visit(
  951. this,
  952. this.transformMap,
  953. 'pathItems',
  954. components.pathItems,
  955. this.transformPathItem,
  956. );
  957. }
  958. return newComponents;
  959. }
  960. /** Transforms a {@link
  961. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverVariableObject
  962. * Server Variable Object}.
  963. *
  964. * @param {!object} serverVariable Server Variable Object.
  965. * @returns {!object} Transformed Server Variable Object.
  966. */
  967. // eslint-disable-next-line class-methods-use-this
  968. transformServerVariable(serverVariable) {
  969. return serverVariable;
  970. }
  971. /** Transforms a {@link
  972. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverObject
  973. * Server Object}.
  974. *
  975. * @param {!object} server Server Object.
  976. * @returns {!object} Transformed Server Object.
  977. */
  978. transformServer(server) {
  979. if (typeof server !== 'object' || server === null || isArray(server)) {
  980. this.warn('Ignoring non-object Server', server);
  981. return server;
  982. }
  983. if (server.variables === undefined) {
  984. return server;
  985. }
  986. return {
  987. ...server,
  988. variables: visit(
  989. this,
  990. this.transformMap,
  991. 'variables',
  992. server.variables,
  993. this.transformServerVariable,
  994. ),
  995. };
  996. }
  997. /** Transforms an {@link
  998. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowObject
  999. * OAuth Flow Object}.
  1000. *
  1001. * @param {!object} flow OAuth Flow Object.
  1002. * @returns {!object} Transformed OAuth Flow Object.
  1003. */
  1004. // eslint-disable-next-line class-methods-use-this
  1005. transformOAuthFlow(flow) {
  1006. return flow;
  1007. }
  1008. /** Transforms an {@link
  1009. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowsObject
  1010. * OAuth Flows Object}.
  1011. *
  1012. * @param {!object} flows OAuth Flows Object.
  1013. * @returns {!object} Transformed OAuth Flows Object.
  1014. */
  1015. transformOAuthFlows(flows) {
  1016. if (typeof flows !== 'object' || flows === null || isArray(flows)) {
  1017. this.warn('Ignoring non-object OAuth Flows', flows);
  1018. return flows;
  1019. }
  1020. const newFlows = { ...flows };
  1021. if (flows.implicit) {
  1022. newFlows.implicit = visit(
  1023. this,
  1024. this.transformOAuthFlow,
  1025. 'implicit',
  1026. flows.implicit,
  1027. );
  1028. }
  1029. if (flows.password) {
  1030. newFlows.password = visit(
  1031. this,
  1032. this.transformOAuthFlow,
  1033. 'password',
  1034. flows.password,
  1035. );
  1036. }
  1037. if (flows.clientCredentials) {
  1038. newFlows.clientCredentials = visit(
  1039. this,
  1040. this.transformOAuthFlow,
  1041. 'clientCredentials',
  1042. flows.clientCredentials,
  1043. );
  1044. }
  1045. if (flows.authorizationCode) {
  1046. newFlows.authorizationCode = visit(
  1047. this,
  1048. this.transformOAuthFlow,
  1049. 'authorizationCode',
  1050. flows.authorizationCode,
  1051. );
  1052. }
  1053. return newFlows;
  1054. }
  1055. /** Transforms a {@link
  1056. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securitySchemeObject
  1057. * Security Scheme Object}.
  1058. *
  1059. * @param {!object} securityScheme Security Scheme Object.
  1060. * @returns {!object} Transformed Security Scheme Object.
  1061. */
  1062. transformSecurityScheme(securityScheme) {
  1063. if (typeof securityScheme !== 'object'
  1064. || securityScheme === null
  1065. || isArray(securityScheme)) {
  1066. this.warn('Ignoring non-object Security Scheme', securityScheme);
  1067. return securityScheme;
  1068. }
  1069. if (securityScheme.flows === undefined) {
  1070. return securityScheme;
  1071. }
  1072. return {
  1073. ...securityScheme,
  1074. flows: visit(
  1075. this,
  1076. this.transformOAuthFlows,
  1077. 'flows',
  1078. securityScheme.flows,
  1079. ),
  1080. };
  1081. }
  1082. /** Transforms a {@link
  1083. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securityRequirementObject
  1084. * Security Requirement Object}.
  1085. *
  1086. * @param {!object} securityRequirement Security Requirement Object.
  1087. * @returns {!object} Transformed Security Requirement Object.
  1088. */
  1089. // eslint-disable-next-line class-methods-use-this
  1090. transformSecurityRequirement(securityRequirement) {
  1091. return securityRequirement;
  1092. }
  1093. /** Transforms a {@link
  1094. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#tagObject
  1095. * Tag Object}.
  1096. *
  1097. * @param {!object} tag Tag Object.
  1098. * @returns {!object} Transformed Tag Object.
  1099. */
  1100. transformTag(tag) {
  1101. if (typeof tag !== 'object' || tag === null || isArray(tag)) {
  1102. this.warn('Ignoring non-object Tag', tag);
  1103. return tag;
  1104. }
  1105. if (tag.externalDocs === undefined) {
  1106. return tag;
  1107. }
  1108. return {
  1109. ...tag,
  1110. externalDocs: visit(
  1111. this,
  1112. this.transformExternalDocs,
  1113. 'externalDocs',
  1114. tag.externalDocs,
  1115. ),
  1116. };
  1117. }
  1118. /** Transforms a {@link
  1119. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#contactObject
  1120. * Contact Object}.
  1121. *
  1122. * @param {!object} contact Contact Object.
  1123. * @returns {!object} Transformed Contact Object.
  1124. */
  1125. // eslint-disable-next-line class-methods-use-this
  1126. transformContact(contact) {
  1127. return contact;
  1128. }
  1129. /** Transforms a {@link
  1130. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#licenseObject
  1131. * License Object}.
  1132. *
  1133. * @param {!object} license License Object.
  1134. * @returns {!object} Transformed License Object.
  1135. */
  1136. // eslint-disable-next-line class-methods-use-this
  1137. transformLicense(license) {
  1138. return license;
  1139. }
  1140. /** Transforms an {@link
  1141. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#infoObject
  1142. * Info Object}.
  1143. *
  1144. * @param {!object} info Info Object.
  1145. * @returns {!object} Transformed Info Object.
  1146. */
  1147. transformInfo(info) {
  1148. if (typeof info !== 'object' || info === null || isArray(info)) {
  1149. this.warn('Ignoring non-object Info', info);
  1150. return info;
  1151. }
  1152. const newInfo = { ...info };
  1153. if (info.contact !== undefined) {
  1154. newInfo.contact = visit(
  1155. this,
  1156. this.transformContact,
  1157. 'contact',
  1158. info.contact,
  1159. );
  1160. }
  1161. if (info.license !== undefined) {
  1162. newInfo.license = visit(
  1163. this,
  1164. this.transformLicense,
  1165. 'license',
  1166. info.license,
  1167. );
  1168. }
  1169. return info;
  1170. }
  1171. /** Transforms an {@link
  1172. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oasObject
  1173. * OpenAPI 3.0 Object} or {@link
  1174. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
  1175. * OpenAPI 2.0 (fka Swagger) Object}.
  1176. *
  1177. * @param {!object} openApi OpenAPI Object.
  1178. * @returns {!object} Transformed OpenAPI Object.
  1179. */
  1180. transformOpenApi(openApi) {
  1181. if (typeof openApi !== 'object' || openApi === null || isArray(openApi)) {
  1182. this.warn('Ignoring non-object OpenAPI', openApi);
  1183. return openApi;
  1184. }
  1185. const newOpenApi = {
  1186. ...openApi,
  1187. };
  1188. if (openApi.info !== undefined) {
  1189. newOpenApi.info = visit(this, this.transformInfo, 'info', openApi.info);
  1190. }
  1191. if (openApi.servers !== undefined) {
  1192. newOpenApi.servers =
  1193. this.transformArray(openApi.servers, this.transformServer);
  1194. }
  1195. // https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-parameterized-host
  1196. const xMsParameterizedHost = openApi['x-ms-parameterized-host'];
  1197. if (typeof xMsParameterizedHost === 'object'
  1198. && xMsParameterizedHost !== null) {
  1199. const { parameters } = xMsParameterizedHost;
  1200. if (parameters !== undefined) {
  1201. newOpenApi['x-ms-parameterized-host'] = {
  1202. ...xMsParameterizedHost,
  1203. parameters: this.transformArray(parameters, this.transformParameter),
  1204. };
  1205. }
  1206. }
  1207. // Note: Transform components and definitions before properties likely
  1208. // to have $refs pointing to them (to simplify renaming).
  1209. // TODO: Guarantee this as part of the API? Document in JSDoc comment.
  1210. if (openApi.components !== undefined) {
  1211. newOpenApi.components = visit(
  1212. this,
  1213. this.transformComponents,
  1214. 'components',
  1215. openApi.components,
  1216. );
  1217. }
  1218. if (openApi.definitions !== undefined) {
  1219. newOpenApi.definitions = visit(
  1220. this,
  1221. this.transformMap,
  1222. 'definitions',
  1223. openApi.definitions,
  1224. this.transformSchema,
  1225. );
  1226. }
  1227. if (openApi.parameters !== undefined) {
  1228. newOpenApi.parameters = visit(
  1229. this,
  1230. this.transformMap,
  1231. 'parameters',
  1232. openApi.parameters,
  1233. this.transformParameter,
  1234. );
  1235. }
  1236. if (openApi.responses !== undefined) {
  1237. newOpenApi.responses = visit(
  1238. this,
  1239. this.transformMap,
  1240. 'responses',
  1241. openApi.responses,
  1242. this.transformResponse,
  1243. );
  1244. }
  1245. if (openApi.paths !== undefined) {
  1246. newOpenApi.paths = visit(
  1247. this,
  1248. this.transformPaths,
  1249. 'paths',
  1250. openApi.paths,
  1251. );
  1252. }
  1253. // https://github.com/Azure/autorest/tree/master/docs/extensions#x-ms-paths
  1254. if (openApi['x-ms-paths'] !== undefined) {
  1255. newOpenApi['x-ms-paths'] = visit(
  1256. this,
  1257. this.transformPaths,
  1258. 'x-ms-paths',
  1259. openApi['x-ms-paths'],
  1260. );
  1261. }
  1262. if (openApi.webhooks !== undefined) {
  1263. newOpenApi.webhooks = visit(
  1264. this,
  1265. this.transformMap,
  1266. 'webhooks',
  1267. openApi.webhooks,
  1268. this.transformPathItem,
  1269. );
  1270. }
  1271. if (openApi.security !== undefined) {
  1272. newOpenApi.security = this.transformArray(
  1273. openApi.security,
  1274. this.transformSecurityRequirement,
  1275. );
  1276. }
  1277. if (openApi.tags !== undefined) {
  1278. newOpenApi.tags = this.transformArray(openApi.tags, this.transformTag);
  1279. }
  1280. if (openApi.externalDocs !== undefined) {
  1281. newOpenApi.externalDocs = visit(
  1282. this,
  1283. this.transformExternalDocs,
  1284. 'externalDocs',
  1285. openApi.externalDocs,
  1286. );
  1287. }
  1288. return newOpenApi;
  1289. }
  1290. /** Logs a warning about the transformation.
  1291. *
  1292. * Logs to util.debuglog('openapi-transformer-base') by default. Designed
  1293. * to be overridden and/or reassigned to log as appropriate for projects
  1294. * which use this class.
  1295. *
  1296. * @param {string|*} message Message with zero or more substitution strings,
  1297. * or first value to log.
  1298. * @param {*} values Additional values to log. Applied to substitution
  1299. * string in message, if one matches, otherwise appended.
  1300. */
  1301. warn(message, ...values) {
  1302. // Note: debug.enabled defined on Node.js v14.9.0 and later
  1303. if (debug.enabled !== false) {
  1304. debug(message, ...values, 'at', toJsonPointer(this.transformPath));
  1305. }
  1306. }
  1307. }
  1308. module.exports = OpenApiTransformerBase;