docusaurus-plugin-openapi-docs 1.0.6 → 1.1.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.
Files changed (47) hide show
  1. package/README.md +1 -2
  2. package/lib/markdown/createSchemaDetails.js +325 -132
  3. package/lib/markdown/index.js +1 -0
  4. package/lib/markdown/schema.js +25 -9
  5. package/lib/markdown/utils.d.ts +1 -1
  6. package/lib/markdown/utils.js +4 -1
  7. package/lib/openapi/openapi.d.ts +3 -3
  8. package/lib/openapi/openapi.js +30 -26
  9. package/lib/openapi/types.d.ts +2 -1
  10. package/lib/openapi/utils/loadAndResolveSpec.d.ts +2 -0
  11. package/lib/openapi/utils/{loadAndBundleSpec.js → loadAndResolveSpec.js} +61 -28
  12. package/lib/openapi/utils/services/OpenAPIParser.d.ts +52 -0
  13. package/lib/openapi/utils/services/OpenAPIParser.js +342 -0
  14. package/lib/openapi/utils/services/RedocNormalizedOptions.d.ts +100 -0
  15. package/lib/openapi/utils/services/RedocNormalizedOptions.js +170 -0
  16. package/lib/openapi/utils/types/index.d.ts +2 -0
  17. package/lib/openapi/utils/types/index.js +23 -0
  18. package/lib/openapi/utils/types/open-api.d.ts +305 -0
  19. package/lib/openapi/utils/types/open-api.js +8 -0
  20. package/lib/openapi/utils/utils/JsonPointer.d.ts +51 -0
  21. package/lib/openapi/utils/utils/JsonPointer.js +95 -0
  22. package/lib/openapi/utils/utils/helpers.d.ts +43 -0
  23. package/lib/openapi/utils/utils/helpers.js +230 -0
  24. package/lib/openapi/utils/utils/index.d.ts +3 -0
  25. package/lib/openapi/utils/utils/index.js +25 -0
  26. package/lib/openapi/utils/utils/openapi.d.ts +40 -0
  27. package/lib/openapi/utils/utils/openapi.js +605 -0
  28. package/lib/sidebars/index.js +5 -3
  29. package/package.json +15 -11
  30. package/src/markdown/createSchemaDetails.ts +405 -159
  31. package/src/markdown/index.ts +1 -0
  32. package/src/markdown/schema.ts +28 -8
  33. package/src/markdown/utils.ts +5 -2
  34. package/src/openapi/openapi.ts +42 -38
  35. package/src/openapi/types.ts +2 -1
  36. package/src/openapi/utils/loadAndResolveSpec.ts +123 -0
  37. package/src/openapi/utils/services/OpenAPIParser.ts +433 -0
  38. package/src/openapi/utils/services/RedocNormalizedOptions.ts +330 -0
  39. package/src/openapi/utils/types/index.ts +10 -0
  40. package/src/openapi/utils/types/open-api.ts +303 -0
  41. package/src/openapi/utils/utils/JsonPointer.ts +99 -0
  42. package/src/openapi/utils/utils/helpers.ts +239 -0
  43. package/src/openapi/utils/utils/index.ts +11 -0
  44. package/src/openapi/utils/utils/openapi.ts +771 -0
  45. package/src/sidebars/index.ts +7 -4
  46. package/lib/openapi/utils/loadAndBundleSpec.d.ts +0 -3
  47. package/src/openapi/utils/loadAndBundleSpec.ts +0 -93
@@ -16,18 +16,10 @@ const openapi_to_postmanv2_1 = __importDefault(require("@paloaltonetworks/openap
16
16
  const postman_collection_1 = __importDefault(require("@paloaltonetworks/postman-collection"));
17
17
  const chalk_1 = __importDefault(require("chalk"));
18
18
  const fs_extra_1 = __importDefault(require("fs-extra"));
19
- const json_refs_1 = __importDefault(require("json-refs"));
20
19
  const lodash_1 = require("lodash");
21
20
  const index_1 = require("../index");
22
21
  const createExample_1 = require("./createExample");
23
- const loadAndBundleSpec_1 = require("./utils/loadAndBundleSpec");
24
- /**
25
- * Finds any reference objects in the OpenAPI definition and resolves them to a finalized value.
26
- */
27
- async function resolveRefs(openapiData) {
28
- const { resolved } = await json_refs_1.default.resolveRefs(openapiData);
29
- return resolved;
30
- }
22
+ const loadAndResolveSpec_1 = require("./utils/loadAndResolveSpec");
31
23
  /**
32
24
  * Convenience function for converting raw JSON to a Postman Collection object.
33
25
  */
@@ -48,7 +40,8 @@ function jsonToCollection(data) {
48
40
  */
49
41
  async function createPostmanCollection(openapiData) {
50
42
  var _a, _b, _c, _d, _e, _f, _g, _h;
51
- const data = JSON.parse(JSON.stringify(openapiData));
43
+ // Create copy of openapiData
44
+ const data = Object.assign({}, openapiData);
52
45
  // Including `servers` breaks postman, so delete all of them.
53
46
  delete data.servers;
54
47
  for (let pathItemObject of Object.values(data.paths)) {
@@ -122,7 +115,9 @@ function createItems(openapiData, sidebarOptions) {
122
115
  operationObject.description =
123
116
  (_h = (_g = operationObject.summary) !== null && _g !== void 0 ? _g : operationObject.operationId) !== null && _h !== void 0 ? _h : "";
124
117
  }
125
- const baseId = (0, lodash_1.kebabCase)(title);
118
+ const baseId = operationObject.operationId
119
+ ? (0, lodash_1.kebabCase)(operationObject.operationId)
120
+ : (0, lodash_1.kebabCase)(operationObject.summary);
126
121
  const servers = (_k = (_j = operationObject.servers) !== null && _j !== void 0 ? _j : pathObject.servers) !== null && _k !== void 0 ? _k : openapiData.servers;
127
122
  const security = (_l = operationObject.security) !== null && _l !== void 0 ? _l : openapiData.security;
128
123
  // Add security schemes so we know how to handle security.
@@ -189,9 +184,6 @@ function bindCollectionToApiItems(items, postmanCollection) {
189
184
  });
190
185
  }
191
186
  async function readOpenapiFiles(openapiPath, options) {
192
- // TODO: determine if this should be an API option
193
- // Forces the json-schema-ref-parser
194
- const parseJsonRefs = true;
195
187
  if (!(0, index_1.isURL)(openapiPath)) {
196
188
  const stat = await fs_extra_1.default.lstat(openapiPath);
197
189
  if (stat.isDirectory()) {
@@ -206,7 +198,7 @@ async function readOpenapiFiles(openapiPath, options) {
206
198
  return Promise.all(sources.map(async (source) => {
207
199
  // TODO: make a function for this
208
200
  const fullPath = path_1.default.join(openapiPath, source);
209
- const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(fullPath, parseJsonRefs));
201
+ const data = (await (0, loadAndResolveSpec_1.loadAndResolveSpec)(fullPath));
210
202
  return {
211
203
  source: fullPath,
212
204
  sourceDirName: path_1.default.dirname(source),
@@ -215,7 +207,7 @@ async function readOpenapiFiles(openapiPath, options) {
215
207
  }));
216
208
  }
217
209
  }
218
- const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(openapiPath, parseJsonRefs));
210
+ const data = (await (0, loadAndResolveSpec_1.loadAndResolveSpec)(openapiPath));
219
211
  return [
220
212
  {
221
213
  source: openapiPath,
@@ -227,27 +219,39 @@ async function readOpenapiFiles(openapiPath, options) {
227
219
  exports.readOpenapiFiles = readOpenapiFiles;
228
220
  async function processOpenapiFiles(files, sidebarOptions) {
229
221
  const promises = files.map(async (file) => {
230
- const processedFile = await processOpenapiFile(file.data, sidebarOptions);
231
- const itemsObjectsArray = processedFile[0].map((item) => ({
232
- ...item,
233
- }));
234
- const tags = processedFile[1];
235
- return [itemsObjectsArray, tags];
222
+ if (file.data !== undefined) {
223
+ const processedFile = await processOpenapiFile(file.data, sidebarOptions);
224
+ const itemsObjectsArray = processedFile[0].map((item) => ({
225
+ ...item,
226
+ }));
227
+ const tags = processedFile[1];
228
+ return [itemsObjectsArray, tags];
229
+ }
230
+ console.warn(chalk_1.default.yellow(`WARNING: the following OpenAPI spec returned undefined: ${file.source}`));
231
+ return [];
236
232
  });
237
233
  const metadata = await Promise.all(promises);
238
234
  const items = metadata
239
235
  .map(function (x) {
240
236
  return x[0];
241
237
  })
242
- .flat();
243
- const tags = metadata.map(function (x) {
238
+ .flat()
239
+ .filter(function (x) {
240
+ // Remove undefined items due to transient parsing errors
241
+ return x !== undefined;
242
+ });
243
+ const tags = metadata
244
+ .map(function (x) {
244
245
  return x[1];
246
+ })
247
+ .filter(function (x) {
248
+ // Remove undefined tags due to transient parsing errors
249
+ return x !== undefined;
245
250
  });
246
251
  return [items, tags];
247
252
  }
248
253
  exports.processOpenapiFiles = processOpenapiFiles;
249
- async function processOpenapiFile(openapiDataWithRefs, sidebarOptions) {
250
- const openapiData = await resolveRefs(openapiDataWithRefs);
254
+ async function processOpenapiFile(openapiData, sidebarOptions) {
251
255
  const postmanCollection = await createPostmanCollection(openapiData);
252
256
  const items = createItems(openapiData, sidebarOptions);
253
257
  bindCollectionToApiItems(items, postmanCollection);
@@ -11,6 +11,7 @@ export interface OpenApiObject {
11
11
  security?: SecurityRequirementObject[];
12
12
  tags?: TagObject[];
13
13
  externalDocs?: ExternalDocumentationObject;
14
+ swagger?: string;
14
15
  }
15
16
  export interface OpenApiObjectWithRef {
16
17
  openapi: string;
@@ -255,7 +256,7 @@ export declare type SchemaObject = Omit<JSONSchema, "type" | "allOf" | "oneOf" |
255
256
  not?: SchemaObject;
256
257
  items?: SchemaObject;
257
258
  properties?: Map<SchemaObject>;
258
- additionalProperties?: boolean | SchemaObject;
259
+ additionalProperties?: Map<SchemaObject>;
259
260
  nullable?: boolean;
260
261
  discriminator?: DiscriminatorObject;
261
262
  readOnly?: boolean;
@@ -0,0 +1,2 @@
1
+ export declare function convertSwagger2OpenAPI(spec: object): Promise<unknown>;
2
+ export declare function loadAndResolveSpec(specUrlOrObject: object | string): Promise<any>;
@@ -9,25 +9,62 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.convertSwagger2OpenAPI = exports.loadAndBundleSpec = void 0;
13
- // @ts-nocheck
12
+ exports.loadAndResolveSpec = exports.convertSwagger2OpenAPI = void 0;
14
13
  const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-schema-ref-parser"));
15
- const bundle_1 = require("@redocly/openapi-core/lib/bundle");
16
- const config_1 = require("@redocly/openapi-core/lib/config/config");
14
+ const openapi_core_1 = require("@redocly/openapi-core");
17
15
  const chalk_1 = __importDefault(require("chalk"));
16
+ // @ts-ignore
18
17
  const swagger2openapi_1 = require("swagger2openapi");
18
+ function serializer(replacer, cycleReplacer) {
19
+ var stack = [], keys = [];
20
+ if (cycleReplacer === undefined)
21
+ cycleReplacer = function (key, value) {
22
+ if (stack[0] === value)
23
+ return "circular()";
24
+ return value.title ? `circular(${value.title})` : "circular()";
25
+ };
26
+ return function (key, value) {
27
+ if (stack.length > 0) {
28
+ // @ts-ignore
29
+ var thisPos = stack.indexOf(this);
30
+ // @ts-ignore
31
+ ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
32
+ ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
33
+ // @ts-ignore
34
+ if (~stack.indexOf(value))
35
+ value = cycleReplacer.call(this, key, value);
36
+ }
37
+ else
38
+ stack.push(value);
39
+ // @ts-ignore
40
+ return replacer === undefined ? value : replacer.call(this, key, value);
41
+ };
42
+ }
43
+ function convertSwagger2OpenAPI(spec) {
44
+ console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0");
45
+ return new Promise((resolve, reject) => (0, swagger2openapi_1.convertObj)(spec, { patch: true, warnOnly: true, text: "{}", anchors: true }, (err, res) => {
46
+ // TODO: log any warnings
47
+ if (err) {
48
+ return reject(err);
49
+ }
50
+ resolve(res && res.openapi);
51
+ }));
52
+ }
53
+ exports.convertSwagger2OpenAPI = convertSwagger2OpenAPI;
19
54
  async function resolveJsonRefs(specUrlOrObject) {
20
55
  var _a, _b;
21
56
  try {
22
57
  let schema = await json_schema_ref_parser_1.default.dereference(specUrlOrObject, {
23
58
  continueOnError: true,
24
59
  resolve: {
60
+ file: true,
61
+ external: true,
25
62
  http: {
26
63
  timeout: 15000, // 15 sec timeout
27
64
  },
28
65
  },
29
66
  dereference: {
30
- circular: "ignore",
67
+ circular: true,
31
68
  },
32
69
  });
33
70
  return schema;
@@ -37,8 +74,8 @@ async function resolveJsonRefs(specUrlOrObject) {
37
74
  return;
38
75
  }
39
76
  }
40
- async function loadAndBundleSpec(specUrlOrObject, parseJsonRefs) {
41
- const config = new config_1.Config({});
77
+ async function loadAndResolveSpec(specUrlOrObject) {
78
+ const config = new openapi_core_1.Config({});
42
79
  const bundleOpts = {
43
80
  config,
44
81
  base: process.cwd(),
@@ -54,26 +91,22 @@ async function loadAndBundleSpec(specUrlOrObject, parseJsonRefs) {
54
91
  }
55
92
  // Force dereference ?
56
93
  // bundleOpts["dereference"] = true;
57
- const { bundle: { parsed }, } = await (0, bundle_1.bundle)(bundleOpts);
58
- if (parseJsonRefs) {
59
- const resolved = resolveJsonRefs(parsed);
60
- return typeof resolved === Object
61
- ? resolved.swagger !== undefined
62
- ? convertSwagger2OpenAPI(resolved)
63
- : resolved
64
- : parsed;
94
+ const { bundle: { parsed }, } = await (0, openapi_core_1.bundle)(bundleOpts);
95
+ const resolved = await resolveJsonRefs(parsed);
96
+ // Force serialization and replace circular $ref pointers
97
+ // @ts-ignore
98
+ const serialized = JSON.stringify(resolved, serializer());
99
+ let decycled;
100
+ try {
101
+ decycled = JSON.parse(serialized);
65
102
  }
66
- return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
67
- }
68
- exports.loadAndBundleSpec = loadAndBundleSpec;
69
- function convertSwagger2OpenAPI(spec) {
70
- console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0");
71
- return new Promise((resolve, reject) => (0, swagger2openapi_1.convertObj)(spec, { patch: true, warnOnly: true, text: "{}", anchors: true }, (err, res) => {
72
- // TODO: log any warnings
73
- if (err) {
74
- return reject(err);
75
- }
76
- resolve(res && res.openapi);
77
- }));
103
+ catch (err) {
104
+ console.error(chalk_1.default.yellow(err));
105
+ }
106
+ return decycled !== undefined && typeof decycled === "object"
107
+ ? decycled.swagger !== undefined
108
+ ? convertSwagger2OpenAPI(decycled)
109
+ : decycled
110
+ : resolved;
78
111
  }
79
- exports.convertSwagger2OpenAPI = convertSwagger2OpenAPI;
112
+ exports.loadAndResolveSpec = loadAndResolveSpec;
@@ -0,0 +1,52 @@
1
+ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from "../types";
2
+ export declare type MergedOpenAPISchema = OpenAPISchema & {
3
+ parentRefs?: string[];
4
+ };
5
+ /**
6
+ * Loads and keeps spec. Provides raw spec operations
7
+ */
8
+ export declare class OpenAPIParser {
9
+ private options;
10
+ specUrl?: string;
11
+ spec: OpenAPISpec;
12
+ private _refCounter;
13
+ private allowMergeRefs;
14
+ constructor(spec: OpenAPISpec, specUrl?: string, options?: {});
15
+ validate(spec: any): void;
16
+ /**
17
+ * get spec part by JsonPointer ($ref)
18
+ */
19
+ byRef: <T extends unknown = any>(ref: string) => T | undefined;
20
+ /**
21
+ * checks if the object is OpenAPI reference (contains $ref property)
22
+ */
23
+ isRef(obj: any): obj is OpenAPIRef;
24
+ /**
25
+ * resets visited endpoints. should be run after
26
+ */
27
+ resetVisited(): void;
28
+ exitRef<T>(ref: Referenced<T>): void;
29
+ /**
30
+ * Resolve given reference object or return as is if it is not a reference
31
+ * @param obj object to dereference
32
+ * @param forceCircular whether to dereference even if it is circular ref
33
+ */
34
+ deref<T extends object>(obj: OpenAPIRef | T, forceCircular?: boolean, mergeAsAllOf?: boolean): T;
35
+ shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T;
36
+ mergeRefs(ref: any, resolved: any, mergeAsAllOf: boolean): any;
37
+ /**
38
+ * Merge allOf constraints.
39
+ * @param schema schema with allOF
40
+ * @param $ref pointer of the schema
41
+ * @param forceCircular whether to dereference children even if it is a circular ref
42
+ */
43
+ mergeAllOf(schema: OpenAPISchema, $ref?: string, forceCircular?: boolean, used$Refs?: Set<string>): MergedOpenAPISchema;
44
+ /**
45
+ * Find all derived definitions among #/components/schemas from any of $refs
46
+ * returns map of definition pointer to definition name
47
+ * @param $refs array of references to find derived from
48
+ */
49
+ findDerived($refs: string[]): Record<string, string[] | string>;
50
+ exitParents(shema: MergedOpenAPISchema): void;
51
+ private hoistOneOfs;
52
+ }
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ /* ============================================================================
3
+ * Copyright (c) Palo Alto Networks
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ * ========================================================================== */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.OpenAPIParser = void 0;
10
+ const helpers_1 = require("../utils/helpers");
11
+ const JsonPointer_1 = require("../utils/JsonPointer");
12
+ const openapi_1 = require("../utils/openapi");
13
+ const RedocNormalizedOptions_1 = require("./RedocNormalizedOptions");
14
+ /**
15
+ * Helper class to keep track of visited references to avoid
16
+ * endless recursion because of circular refs
17
+ */
18
+ class RefCounter {
19
+ constructor() {
20
+ this._counter = {};
21
+ }
22
+ reset() {
23
+ this._counter = {};
24
+ }
25
+ visit(ref) {
26
+ this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1;
27
+ }
28
+ exit(ref) {
29
+ this._counter[ref] = this._counter[ref] && this._counter[ref] - 1;
30
+ }
31
+ visited(ref) {
32
+ return !!this._counter[ref];
33
+ }
34
+ }
35
+ /**
36
+ * Loads and keeps spec. Provides raw spec operations
37
+ */
38
+ class OpenAPIParser {
39
+ constructor(spec, specUrl, options = new RedocNormalizedOptions_1.RedocNormalizedOptions()) {
40
+ this.options = options;
41
+ this._refCounter = new RefCounter();
42
+ this.allowMergeRefs = false;
43
+ /**
44
+ * get spec part by JsonPointer ($ref)
45
+ */
46
+ this.byRef = (ref) => {
47
+ let res;
48
+ if (!this.spec) {
49
+ return;
50
+ }
51
+ if (ref.charAt(0) !== "#") {
52
+ ref = "#" + ref;
53
+ }
54
+ ref = decodeURIComponent(ref);
55
+ try {
56
+ res = JsonPointer_1.JsonPointer.get(this.spec, ref);
57
+ }
58
+ catch (e) {
59
+ // do nothing
60
+ }
61
+ return res || {};
62
+ };
63
+ this.validate(spec);
64
+ this.spec = spec;
65
+ this.allowMergeRefs = spec.openapi.startsWith("3.1");
66
+ const href = undefined;
67
+ if (typeof specUrl === "string") {
68
+ this.specUrl = new URL(specUrl, href).href;
69
+ }
70
+ }
71
+ validate(spec) {
72
+ if (spec.openapi === undefined) {
73
+ throw new Error("Document must be valid OpenAPI 3.0.0 definition");
74
+ }
75
+ }
76
+ /**
77
+ * checks if the object is OpenAPI reference (contains $ref property)
78
+ */
79
+ isRef(obj) {
80
+ if (!obj) {
81
+ return false;
82
+ }
83
+ return obj.$ref !== undefined && obj.$ref !== null;
84
+ }
85
+ /**
86
+ * resets visited endpoints. should be run after
87
+ */
88
+ resetVisited() {
89
+ if (process.env.NODE_ENV !== "production") {
90
+ // check in dev mode
91
+ for (const k in this._refCounter._counter) {
92
+ if (this._refCounter._counter[k] > 0) {
93
+ console.warn("Not exited reference: " + k);
94
+ }
95
+ }
96
+ }
97
+ this._refCounter = new RefCounter();
98
+ }
99
+ exitRef(ref) {
100
+ if (!this.isRef(ref)) {
101
+ return;
102
+ }
103
+ this._refCounter.exit(ref.$ref);
104
+ }
105
+ /**
106
+ * Resolve given reference object or return as is if it is not a reference
107
+ * @param obj object to dereference
108
+ * @param forceCircular whether to dereference even if it is circular ref
109
+ */
110
+ deref(obj, forceCircular = false, mergeAsAllOf = false) {
111
+ if (this.isRef(obj)) {
112
+ const schemaName = (0, openapi_1.getDefinitionName)(obj.$ref);
113
+ if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
114
+ return { type: "object", title: schemaName };
115
+ }
116
+ const resolved = this.byRef(obj.$ref);
117
+ const visited = this._refCounter.visited(obj.$ref);
118
+ this._refCounter.visit(obj.$ref);
119
+ if (visited && !forceCircular) {
120
+ // circular reference detected
121
+ // tslint:disable-next-line
122
+ return Object.assign({}, resolved, { "x-circular-ref": true });
123
+ }
124
+ // deref again in case one more $ref is here
125
+ let result = resolved;
126
+ if (this.isRef(resolved)) {
127
+ result = this.deref(resolved, false, mergeAsAllOf);
128
+ this.exitRef(resolved);
129
+ }
130
+ return this.allowMergeRefs
131
+ ? this.mergeRefs(obj, resolved, mergeAsAllOf)
132
+ : result;
133
+ }
134
+ return obj;
135
+ }
136
+ shallowDeref(obj) {
137
+ if (this.isRef(obj)) {
138
+ const schemaName = (0, openapi_1.getDefinitionName)(obj.$ref);
139
+ if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
140
+ return { type: "object", title: schemaName };
141
+ }
142
+ const resolved = this.byRef(obj.$ref);
143
+ return this.allowMergeRefs
144
+ ? this.mergeRefs(obj, resolved, false)
145
+ : resolved;
146
+ }
147
+ return obj;
148
+ }
149
+ mergeRefs(ref, resolved, mergeAsAllOf) {
150
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
151
+ const { $ref, ...rest } = ref;
152
+ const keys = Object.keys(rest);
153
+ if (keys.length === 0) {
154
+ if (this.isRef(resolved)) {
155
+ return this.shallowDeref(resolved);
156
+ }
157
+ return resolved;
158
+ }
159
+ if (mergeAsAllOf &&
160
+ keys.some((k) => k !== "description" && k !== "title" && k !== "externalDocs")) {
161
+ return {
162
+ allOf: [rest, resolved],
163
+ };
164
+ }
165
+ else {
166
+ // small optimization
167
+ return {
168
+ ...resolved,
169
+ ...rest,
170
+ };
171
+ }
172
+ }
173
+ /**
174
+ * Merge allOf constraints.
175
+ * @param schema schema with allOF
176
+ * @param $ref pointer of the schema
177
+ * @param forceCircular whether to dereference children even if it is a circular ref
178
+ */
179
+ mergeAllOf(schema, $ref, forceCircular = false, used$Refs = new Set()) {
180
+ if ($ref) {
181
+ used$Refs.add($ref);
182
+ }
183
+ schema = this.hoistOneOfs(schema);
184
+ if (schema.allOf === undefined) {
185
+ return schema;
186
+ }
187
+ let receiver = {
188
+ ...schema,
189
+ allOf: undefined,
190
+ parentRefs: [],
191
+ title: schema.title || (0, openapi_1.getDefinitionName)($ref),
192
+ };
193
+ // avoid mutating inner objects
194
+ if (receiver.properties !== undefined &&
195
+ typeof receiver.properties === "object") {
196
+ receiver.properties = { ...receiver.properties };
197
+ }
198
+ if (receiver.items !== undefined && typeof receiver.items === "object") {
199
+ receiver.items = { ...receiver.items };
200
+ }
201
+ const allOfSchemas = schema.allOf
202
+ .map((subSchema) => {
203
+ if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
204
+ return undefined;
205
+ }
206
+ const resolved = this.deref(subSchema, forceCircular, true);
207
+ const subRef = subSchema.$ref || undefined;
208
+ const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
209
+ receiver.parentRefs.push(...(subMerged.parentRefs || []));
210
+ return {
211
+ $ref: subRef,
212
+ schema: subMerged,
213
+ };
214
+ })
215
+ .filter((child) => child !== undefined);
216
+ for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
217
+ const { type, enum: enumProperty, properties, items, required, oneOf, anyOf, title, ...otherConstraints } = subSchema;
218
+ if (receiver.type !== type &&
219
+ receiver.type !== undefined &&
220
+ type !== undefined) {
221
+ console.warn(`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${type}"`);
222
+ }
223
+ if (type !== undefined) {
224
+ if (Array.isArray(type) && Array.isArray(receiver.type)) {
225
+ receiver.type = [...type, ...receiver.type];
226
+ }
227
+ else {
228
+ receiver.type = type;
229
+ }
230
+ }
231
+ if (enumProperty !== undefined) {
232
+ if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) {
233
+ receiver.enum = [...enumProperty, ...receiver.enum];
234
+ }
235
+ else {
236
+ receiver.enum = enumProperty;
237
+ }
238
+ }
239
+ if (properties !== undefined) {
240
+ receiver.properties = receiver.properties || {};
241
+ for (const prop in properties) {
242
+ if (!receiver.properties[prop]) {
243
+ receiver.properties[prop] = properties[prop];
244
+ }
245
+ else {
246
+ // merge inner properties
247
+ const mergedProp = this.mergeAllOf({ allOf: [receiver.properties[prop], properties[prop]] }, $ref + "/properties/" + prop);
248
+ receiver.properties[prop] = mergedProp;
249
+ this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
250
+ }
251
+ }
252
+ }
253
+ if (items !== undefined) {
254
+ const receiverItems = (0, helpers_1.isBoolean)(receiver.items)
255
+ ? { items: receiver.items }
256
+ : receiver.items
257
+ ? Object.assign({}, receiver.items)
258
+ : {};
259
+ const subSchemaItems = (0, helpers_1.isBoolean)(items)
260
+ ? { items }
261
+ : Object.assign({}, items);
262
+ // merge inner properties
263
+ receiver.items = this.mergeAllOf({ allOf: [receiverItems, subSchemaItems] }, $ref + "/items");
264
+ }
265
+ if (required !== undefined) {
266
+ receiver.required = (receiver.required || []).concat(required);
267
+ }
268
+ if (oneOf !== undefined) {
269
+ receiver.oneOf = oneOf;
270
+ }
271
+ if (anyOf !== undefined) {
272
+ receiver.anyOf = anyOf;
273
+ }
274
+ // merge rest of constraints
275
+ // TODO: do more intelligent merge
276
+ receiver = {
277
+ ...receiver,
278
+ title: receiver.title || title,
279
+ ...otherConstraints,
280
+ };
281
+ if (subSchemaRef) {
282
+ receiver.parentRefs.push(subSchemaRef);
283
+ if (receiver.title === undefined && (0, openapi_1.isNamedDefinition)(subSchemaRef)) {
284
+ // this is not so correct behaviour. commented out for now
285
+ // ref: https://github.com/Redocly/redoc/issues/601
286
+ // receiver.title = JsonPointer.baseName(subSchemaRef);
287
+ }
288
+ }
289
+ }
290
+ return receiver;
291
+ }
292
+ /**
293
+ * Find all derived definitions among #/components/schemas from any of $refs
294
+ * returns map of definition pointer to definition name
295
+ * @param $refs array of references to find derived from
296
+ */
297
+ findDerived($refs) {
298
+ const res = {};
299
+ const schemas = (this.spec.components && this.spec.components.schemas) || {};
300
+ for (const defName in schemas) {
301
+ const def = this.deref(schemas[defName]);
302
+ if (def.allOf !== undefined &&
303
+ def.allOf.find((obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)) {
304
+ res["#/components/schemas/" + defName] = [
305
+ def["x-discriminator-value"] || defName,
306
+ ];
307
+ }
308
+ }
309
+ return res;
310
+ }
311
+ exitParents(shema) {
312
+ for (const parent$ref of shema.parentRefs || []) {
313
+ this.exitRef({ $ref: parent$ref });
314
+ }
315
+ }
316
+ hoistOneOfs(schema) {
317
+ if (schema.allOf === undefined) {
318
+ return schema;
319
+ }
320
+ const allOf = schema.allOf;
321
+ for (let i = 0; i < allOf.length; i++) {
322
+ const sub = allOf[i];
323
+ if ((0, helpers_1.isArray)(sub.oneOf)) {
324
+ const beforeAllOf = allOf.slice(0, i);
325
+ const afterAllOf = allOf.slice(i + 1);
326
+ return {
327
+ oneOf: sub.oneOf.map((part) => {
328
+ const merged = this.mergeAllOf({
329
+ allOf: [...beforeAllOf, part, ...afterAllOf],
330
+ });
331
+ // each oneOf should be independent so exiting all the parent refs
332
+ // otherwise it will cause false-positive recursive detection
333
+ this.exitParents(merged);
334
+ return merged;
335
+ }),
336
+ };
337
+ }
338
+ }
339
+ return schema;
340
+ }
341
+ }
342
+ exports.OpenAPIParser = OpenAPIParser;