rapidoc 11.0.1 → 12.1.0
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.
- package/README.md +171 -54
- package/dist/rapidoc-flow-min.js +580 -0
- package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
- package/dist/rapidoc-min.js +645 -202
- package/dist/rapidoc-min.js.LICENSE.txt +381 -0
- package/package.json +57 -42
- package/src/README.md +150 -0
- package/src/components/README.md +91 -0
- package/src/components/api-request.ts +912 -0
- package/src/components/api-response.ts +514 -0
- package/src/components/json-tree.ts +171 -0
- package/src/components/schema-base-element.ts +329 -0
- package/src/components/schema-table.ts +475 -0
- package/src/components/schema-tree.ts +393 -0
- package/src/components/tag-input.ts +180 -0
- package/src/flow.js +3 -0
- package/src/index.js +6 -0
- package/src/oauth-receiver.ts +29 -0
- package/src/rapi-flow.ts +1178 -0
- package/src/rapidoc-mini.ts +603 -0
- package/src/rapidoc.ts +1854 -0
- package/src/styles/README.md +96 -0
- package/src/styles/auth-styles.js +318 -0
- package/src/styles/border-styles.js +24 -0
- package/src/styles/css-bundle.js +129 -0
- package/src/styles/custom-styles.js +8 -0
- package/src/styles/dialog-box-styles.js +102 -0
- package/src/styles/endpoint-styles.js +564 -0
- package/src/styles/flex-styles.js +16 -0
- package/src/styles/flow-styles.js +248 -0
- package/src/styles/font-styles.js +432 -0
- package/src/styles/info-styles.js +36 -0
- package/src/styles/input-styles.js +555 -0
- package/src/styles/layout-styles.js +396 -0
- package/src/styles/microlighter-styles.js +99 -0
- package/src/styles/nav-styles.js +633 -0
- package/src/styles/param-list-styles.js +261 -0
- package/src/styles/schema-styles.js +296 -0
- package/src/styles/tab-styles.js +106 -0
- package/src/styles/theme-tokens.js +582 -0
- package/src/templates/README.md +89 -0
- package/src/templates/advanced-search-dialog-template.js +94 -0
- package/src/templates/api-response-template.js +223 -0
- package/src/templates/callback-template.js +86 -0
- package/src/templates/code-samples-template.js +52 -0
- package/src/templates/components-template.js +86 -0
- package/src/templates/downloads-template.js +70 -0
- package/src/templates/endpoint-template.js +377 -0
- package/src/templates/expanded-endpoint-template.js +270 -0
- package/src/templates/focused-endpoint-template.js +146 -0
- package/src/templates/header-template.js +92 -0
- package/src/templates/logo-template.js +21 -0
- package/src/templates/main-body-template.js +102 -0
- package/src/templates/navbar-template.js +498 -0
- package/src/templates/overview-template.js +104 -0
- package/src/templates/request-body-template.js +673 -0
- package/src/templates/request-params-template.js +485 -0
- package/src/templates/security-scheme-template.js +1272 -0
- package/src/templates/server-template.js +153 -0
- package/src/utils/README.md +146 -0
- package/src/utils/arazzo/README.md +119 -0
- package/src/utils/arazzo/components.js +15 -0
- package/src/utils/arazzo/graph.js +269 -0
- package/src/utils/arazzo/index.js +52 -0
- package/src/utils/arazzo/operations.js +106 -0
- package/src/utils/arazzo/parse.js +196 -0
- package/src/utils/arazzo/refs.js +40 -0
- package/src/utils/arazzo/svg.js +707 -0
- package/src/utils/auto-color-scheme.js +99 -0
- package/src/utils/bind-navbar.js +175 -0
- package/src/utils/color-utils.js +59 -0
- package/src/utils/common-utils.js +191 -0
- package/src/utils/curl-utils.js +126 -0
- package/src/utils/highlighter.js +419 -0
- package/src/utils/markdown-utils.js +110 -0
- package/src/utils/mcp/README.md +155 -0
- package/src/utils/mcp/common.js +166 -0
- package/src/utils/mcp/manifest.js +382 -0
- package/src/utils/mcp/project/server.ts +53 -0
- package/src/utils/mcp/project.js +88 -0
- package/src/utils/mcp/runtime.js +289 -0
- package/src/utils/mcp/schema.js +98 -0
- package/src/utils/mcp/workflow-runtime.js +374 -0
- package/src/utils/mcp/workflows.js +240 -0
- package/src/utils/mcp/zip.js +102 -0
- package/src/utils/mcp-generator.js +47 -0
- package/src/utils/mock-interceptor.js +557 -0
- package/src/utils/oauth-utils.js +87 -0
- package/src/utils/request-utils.js +264 -0
- package/src/utils/response-utils.js +203 -0
- package/src/utils/sanitize.js +57 -0
- package/src/utils/schema-utils.js +1954 -0
- package/src/utils/slugger.js +187 -0
- package/src/utils/spec-parser.js +941 -0
- package/src/utils/url-utils.js +246 -0
- package/src/utils/xml-utils.js +61 -0
|
@@ -0,0 +1,1954 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a deterministic sample string matching common regex patterns in OpenAPI schemas.
|
|
3
|
+
* Falls back to the pattern string if an unexpected error occurs.
|
|
4
|
+
*/
|
|
5
|
+
export function patternSampleGenerator(pattern) {
|
|
6
|
+
if (!pattern || typeof pattern !== "string") {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
let p = pattern.trim();
|
|
12
|
+
if (p.startsWith("^")) p = p.slice(1);
|
|
13
|
+
if (p.endsWith("$") && !p.endsWith("\\$")) p = p.slice(0, -1);
|
|
14
|
+
|
|
15
|
+
// Resolve simple group alternations: (opt1|opt2|opt3) -> opt1
|
|
16
|
+
p = p.replace(/\((?:\?:)?([^()|]+)(?:\|[^()]+)*\)/g, "$1");
|
|
17
|
+
|
|
18
|
+
// Tokenizer matching
|
|
19
|
+
const tokenRegex =
|
|
20
|
+
/(\[[^\]]+\]|\\[dwsDWS]|\\[^]|\.|[^\\[\]{}()+*?|])(?:\{(\d+)(?:,\d*)?\}|([+*?]))?/g;
|
|
21
|
+
|
|
22
|
+
let result = "";
|
|
23
|
+
let match;
|
|
24
|
+
|
|
25
|
+
while ((match = tokenRegex.exec(p)) !== null) {
|
|
26
|
+
const token = match[1];
|
|
27
|
+
const count = match[2];
|
|
28
|
+
const quantifier = match[3];
|
|
29
|
+
|
|
30
|
+
let rep = 1;
|
|
31
|
+
if (count !== undefined) {
|
|
32
|
+
rep = parseInt(count, 10);
|
|
33
|
+
} else if (quantifier === "+") {
|
|
34
|
+
rep = 1;
|
|
35
|
+
} else if (quantifier === "*" || quantifier === "?") {
|
|
36
|
+
rep = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
rep = Math.min(Math.max(0, rep), 50);
|
|
40
|
+
|
|
41
|
+
let sampleChar = "a";
|
|
42
|
+
if (token === "\\d") {
|
|
43
|
+
sampleChar = "0";
|
|
44
|
+
} else if (token === "\\D") {
|
|
45
|
+
sampleChar = "a";
|
|
46
|
+
} else if (token === "\\w") {
|
|
47
|
+
sampleChar = "a";
|
|
48
|
+
} else if (token === "\\W") {
|
|
49
|
+
sampleChar = "_";
|
|
50
|
+
} else if (token === "\\s") {
|
|
51
|
+
sampleChar = " ";
|
|
52
|
+
} else if (token === "\\S") {
|
|
53
|
+
sampleChar = "a";
|
|
54
|
+
} else if (token === ".") {
|
|
55
|
+
sampleChar = "a";
|
|
56
|
+
} else if (token.startsWith("\\")) {
|
|
57
|
+
sampleChar = token.slice(1);
|
|
58
|
+
} else if (token.startsWith("[")) {
|
|
59
|
+
const inner = token.slice(1, -1);
|
|
60
|
+
if (inner.startsWith("^")) {
|
|
61
|
+
sampleChar = inner.includes("a") ? "0" : "a";
|
|
62
|
+
} else if (/[1-9]/.test(inner) && !inner.includes("0")) {
|
|
63
|
+
sampleChar = "1";
|
|
64
|
+
} else if (/[0-9]/.test(inner) && !/[a-zA-Z]/.test(inner)) {
|
|
65
|
+
sampleChar = "0";
|
|
66
|
+
} else if (/[0-9]/.test(inner) && /[a-fA-F]/.test(inner) && !/[g-zG-Z]/.test(inner)) {
|
|
67
|
+
sampleChar = "0";
|
|
68
|
+
} else if (/[A-Z]/.test(inner) && !/[a-z]/.test(inner)) {
|
|
69
|
+
sampleChar = "A";
|
|
70
|
+
} else if (/[a-z]/.test(inner)) {
|
|
71
|
+
sampleChar = "a";
|
|
72
|
+
} else if (inner.length > 0) {
|
|
73
|
+
sampleChar = inner[0];
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
sampleChar = token;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
result += sampleChar.repeat(rep);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result || pattern;
|
|
83
|
+
} catch {
|
|
84
|
+
return pattern;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Takes a value as input and provides a printable string to replresent null values, spaces, blankstring etc
|
|
89
|
+
export function getPrintableVal(val) {
|
|
90
|
+
if (val === undefined) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
if (val === null) {
|
|
94
|
+
return "null";
|
|
95
|
+
}
|
|
96
|
+
if (val === "") {
|
|
97
|
+
return "∅";
|
|
98
|
+
}
|
|
99
|
+
if (typeof val === "boolean" || typeof val === "number") {
|
|
100
|
+
return `${val}`;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(val)) {
|
|
103
|
+
return val
|
|
104
|
+
.map((v) =>
|
|
105
|
+
v === null
|
|
106
|
+
? "null"
|
|
107
|
+
: v === ""
|
|
108
|
+
? "∅"
|
|
109
|
+
: v.toString().replace(/^ +| +$/g, (m) => "●".repeat(m.length)) || "",
|
|
110
|
+
)
|
|
111
|
+
.join(", ");
|
|
112
|
+
}
|
|
113
|
+
if (typeof val === "object") {
|
|
114
|
+
const keys = Object.keys(val);
|
|
115
|
+
return `{ ${keys[0]}:${val[keys[0]]}${keys.length > 1 ? "," : ""} ... }`;
|
|
116
|
+
}
|
|
117
|
+
return val.toString().replace(/^ +| +$/g, (m) => "●".repeat(m.length)) || "";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Helper to detect binary file fields across OpenAPI 3.0 and 3.1 */
|
|
121
|
+
export function isBinaryFileField(schema) {
|
|
122
|
+
if (!schema) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return (
|
|
126
|
+
schema.format === "binary" ||
|
|
127
|
+
schema.contentEncoding === "binary" ||
|
|
128
|
+
(Boolean(schema.contentMediaType) && schema.contentEncoding !== "base64")
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Keywords that only annotate a schema, without telling anything about the type of the value
|
|
133
|
+
const ANNOTATION_KEYWORDS = new Set([
|
|
134
|
+
"title",
|
|
135
|
+
"description",
|
|
136
|
+
"example",
|
|
137
|
+
"examples",
|
|
138
|
+
"default",
|
|
139
|
+
"deprecated",
|
|
140
|
+
"readOnly",
|
|
141
|
+
"writeOnly",
|
|
142
|
+
"externalDocs",
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
function isAnnotationOnly(schema) {
|
|
146
|
+
return Object.keys(schema).every((key) => ANNOTATION_KEYWORDS.has(key) || key.startsWith("x-"));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Generates an schema object containing type and constraint info */
|
|
150
|
+
export function getTypeInfo(schema) {
|
|
151
|
+
if (!schema) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let dataType = "";
|
|
155
|
+
let constrain = "";
|
|
156
|
+
// let examples;
|
|
157
|
+
|
|
158
|
+
if (schema.$ref) {
|
|
159
|
+
const n = schema.$ref.lastIndexOf("/");
|
|
160
|
+
const schemaNode = schema.$ref.substring(n + 1);
|
|
161
|
+
dataType = `{recursive: ${schemaNode}} `;
|
|
162
|
+
} else if (schema.type) {
|
|
163
|
+
dataType = Array.isArray(schema.type) ? schema.type.join("┃") : schema.type;
|
|
164
|
+
if (schema.format || schema.enum || schema.const !== undefined) {
|
|
165
|
+
dataType = dataType.replace(
|
|
166
|
+
"string",
|
|
167
|
+
schema.enum ? "enum" : schema.const !== undefined ? "const" : schema.format,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (schema.nullable) {
|
|
171
|
+
dataType += "┃null";
|
|
172
|
+
}
|
|
173
|
+
} else if (schema.const !== undefined) {
|
|
174
|
+
dataType = "const";
|
|
175
|
+
} else if (schema.anyOf || schema.oneOf) {
|
|
176
|
+
const subSchemas = (schema.anyOf || schema.oneOf).filter(Boolean);
|
|
177
|
+
const subTypes = [];
|
|
178
|
+
subSchemas.forEach((s) => {
|
|
179
|
+
const sInfo = getTypeInfo(s);
|
|
180
|
+
if (sInfo?.type && sInfo.type !== "{missing-type-info}") {
|
|
181
|
+
sInfo.type.split("┃").forEach((t) => {
|
|
182
|
+
const trimmed = t.trim();
|
|
183
|
+
if (trimmed && !subTypes.includes(trimmed)) {
|
|
184
|
+
subTypes.push(trimmed);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
dataType = subTypes.length > 0 ? subTypes.join("┃") : "{missing-type-info}";
|
|
190
|
+
} else if (isAnnotationOnly(schema)) {
|
|
191
|
+
dataType = "any";
|
|
192
|
+
} else {
|
|
193
|
+
dataType = "{missing-type-info}";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const effectiveSchema =
|
|
197
|
+
schema.anyOf || schema.oneOf
|
|
198
|
+
? {
|
|
199
|
+
...((schema.anyOf || schema.oneOf).find((s) => s && s.type && s.type !== "null") ||
|
|
200
|
+
(schema.anyOf || schema.oneOf)[0]),
|
|
201
|
+
...schema,
|
|
202
|
+
}
|
|
203
|
+
: schema;
|
|
204
|
+
|
|
205
|
+
const info = {
|
|
206
|
+
type: dataType,
|
|
207
|
+
format: schema.format || effectiveSchema.format || "",
|
|
208
|
+
contentMediaType: schema.contentMediaType || effectiveSchema.contentMediaType || "",
|
|
209
|
+
contentEncoding: schema.contentEncoding || effectiveSchema.contentEncoding || "",
|
|
210
|
+
contentSchema: schema.contentSchema || effectiveSchema.contentSchema || null,
|
|
211
|
+
pattern:
|
|
212
|
+
(schema.pattern || effectiveSchema.pattern) && !schema.enum && !effectiveSchema.enum
|
|
213
|
+
? schema.pattern || effectiveSchema.pattern
|
|
214
|
+
: "",
|
|
215
|
+
readOrWriteOnly: schema.readOnly
|
|
216
|
+
? "🆁"
|
|
217
|
+
: schema.writeOnly
|
|
218
|
+
? "🆆"
|
|
219
|
+
: effectiveSchema.readOnly
|
|
220
|
+
? "🆁"
|
|
221
|
+
: effectiveSchema.writeOnly
|
|
222
|
+
? "🆆"
|
|
223
|
+
: "",
|
|
224
|
+
deprecated: schema.deprecated || effectiveSchema.deprecated ? "❌" : "",
|
|
225
|
+
examples:
|
|
226
|
+
schema.examples || schema.example || effectiveSchema.examples || effectiveSchema.example,
|
|
227
|
+
default: getPrintableVal(
|
|
228
|
+
schema.default !== undefined ? schema.default : effectiveSchema.default,
|
|
229
|
+
),
|
|
230
|
+
description: schema.description || effectiveSchema.description || "",
|
|
231
|
+
constrain: "",
|
|
232
|
+
allowedValues: "",
|
|
233
|
+
arrayType: "",
|
|
234
|
+
html: "",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (info.type === "{missing-type-info}" || info.type === "any") {
|
|
238
|
+
info.description = info.description || "";
|
|
239
|
+
}
|
|
240
|
+
// Set Allowed Values
|
|
241
|
+
info.allowedValues =
|
|
242
|
+
schema.const !== undefined
|
|
243
|
+
? getPrintableVal(schema.const)
|
|
244
|
+
: Array.isArray(schema.enum)
|
|
245
|
+
? schema.enum.map((v) => getPrintableVal(v)).join("┃")
|
|
246
|
+
: effectiveSchema.const !== undefined
|
|
247
|
+
? getPrintableVal(effectiveSchema.const)
|
|
248
|
+
: Array.isArray(effectiveSchema.enum)
|
|
249
|
+
? effectiveSchema.enum.map((v) => getPrintableVal(v)).join("┃")
|
|
250
|
+
: "";
|
|
251
|
+
|
|
252
|
+
if (!info.allowedValues && (schema.anyOf || schema.oneOf)) {
|
|
253
|
+
const subValues = [];
|
|
254
|
+
(schema.anyOf || schema.oneOf).forEach((s) => {
|
|
255
|
+
const sVal =
|
|
256
|
+
s.const !== undefined
|
|
257
|
+
? getPrintableVal(s.const)
|
|
258
|
+
: Array.isArray(s.enum)
|
|
259
|
+
? s.enum.map((v) => getPrintableVal(v)).join("┃")
|
|
260
|
+
: "";
|
|
261
|
+
if (sVal && !subValues.includes(sVal)) {
|
|
262
|
+
subValues.push(sVal);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
if (subValues.length > 0) {
|
|
266
|
+
info.allowedValues = subValues.join("┃");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const itemsSchema = schema.items || effectiveSchema.items;
|
|
271
|
+
if ((dataType === "array" || dataType.split("┃").includes("array")) && itemsSchema) {
|
|
272
|
+
const arrayItemType =
|
|
273
|
+
itemsSchema.format ||
|
|
274
|
+
itemsSchema.contentMediaType ||
|
|
275
|
+
(Array.isArray(itemsSchema?.type) ? itemsSchema.type.join("┃") : itemsSchema?.type) ||
|
|
276
|
+
"";
|
|
277
|
+
const arrayItemDefault = getPrintableVal(itemsSchema.default);
|
|
278
|
+
|
|
279
|
+
info.arrayType = arrayItemType ? `array of ${arrayItemType}` : `${schema.type || "array"}`;
|
|
280
|
+
if (!info.default) {
|
|
281
|
+
info.default = arrayItemDefault;
|
|
282
|
+
}
|
|
283
|
+
if (!info.allowedValues) {
|
|
284
|
+
info.allowedValues =
|
|
285
|
+
itemsSchema.const !== undefined
|
|
286
|
+
? getPrintableVal(itemsSchema.const)
|
|
287
|
+
: Array.isArray(itemsSchema?.enum)
|
|
288
|
+
? itemsSchema.enum.map((v) => getPrintableVal(v)).join("┃")
|
|
289
|
+
: "";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (dataType.match(/integer|number/g)) {
|
|
293
|
+
const minVal = schema.minimum !== undefined ? schema.minimum : effectiveSchema.minimum;
|
|
294
|
+
const excMinVal =
|
|
295
|
+
schema.exclusiveMinimum !== undefined
|
|
296
|
+
? schema.exclusiveMinimum
|
|
297
|
+
: effectiveSchema.exclusiveMinimum;
|
|
298
|
+
if (minVal !== undefined || excMinVal !== undefined) {
|
|
299
|
+
constrain += minVal !== undefined ? `Min ${minVal}` : `More than ${excMinVal}`;
|
|
300
|
+
}
|
|
301
|
+
const maxVal = schema.maximum !== undefined ? schema.maximum : effectiveSchema.maximum;
|
|
302
|
+
const excMaxVal =
|
|
303
|
+
schema.exclusiveMaximum !== undefined
|
|
304
|
+
? schema.exclusiveMaximum
|
|
305
|
+
: effectiveSchema.exclusiveMaximum;
|
|
306
|
+
if (maxVal !== undefined || excMaxVal !== undefined) {
|
|
307
|
+
constrain +=
|
|
308
|
+
maxVal !== undefined
|
|
309
|
+
? `${constrain ? "┃" : ""}Max ${maxVal}`
|
|
310
|
+
: `${constrain ? "┃" : ""}Less than ${excMaxVal}`;
|
|
311
|
+
}
|
|
312
|
+
const multVal =
|
|
313
|
+
schema.multipleOf !== undefined ? schema.multipleOf : effectiveSchema.multipleOf;
|
|
314
|
+
if (multVal !== undefined) {
|
|
315
|
+
constrain += `${constrain ? "┃" : ""} multiple of ${multVal}`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// dataType shows the format or enum in place of 'string' (e.g. 'email', 'uri'), so check the declared type too
|
|
319
|
+
const declaredTypes = [].concat(effectiveSchema.type || []);
|
|
320
|
+
if (declaredTypes.includes("string") || dataType.match(/string/g)) {
|
|
321
|
+
const minLen = schema.minLength !== undefined ? schema.minLength : effectiveSchema.minLength;
|
|
322
|
+
const maxLen = schema.maxLength !== undefined ? schema.maxLength : effectiveSchema.maxLength;
|
|
323
|
+
if (minLen !== undefined && maxLen !== undefined) {
|
|
324
|
+
constrain += `${constrain ? "┃" : ""}${minLen} to ${maxLen} chars`;
|
|
325
|
+
} else if (minLen !== undefined) {
|
|
326
|
+
constrain += `${constrain ? "┃" : ""}Min ${minLen} chars`;
|
|
327
|
+
} else if (maxLen !== undefined) {
|
|
328
|
+
constrain += `${constrain ? "┃" : ""}Max ${maxLen} chars`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
info.constrain = constrain;
|
|
332
|
+
info.html = `${info.type}~|~${info.readOrWriteOnly}~|~${info.constrain}~|~${info.default}~|~${info.allowedValues}~|~${info.pattern}~|~${info.description}~|~${schema.title || ""}~|~${info.deprecated ? "deprecated" : ""}`;
|
|
333
|
+
return info;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
*
|
|
338
|
+
* @param {*} ex if the value
|
|
339
|
+
* - Is an Object with 'value' property like
|
|
340
|
+
* { 'value': 'example_val1', 'description': 'some description' }
|
|
341
|
+
* Returns >>>
|
|
342
|
+
* {
|
|
343
|
+
* 'Example': { 'value' : 'example_val1', 'description': 'some description' },
|
|
344
|
+
* }
|
|
345
|
+
* - Is an object where each key represents a valid example object (i,e has a value property)
|
|
346
|
+
* {
|
|
347
|
+
* 'example1': { 'value' : 'example_val1', 'description': 'some description' },
|
|
348
|
+
* 'example2': { 'value' : 'example_val2', 'description': 'some other description' },
|
|
349
|
+
* 'invalid': { 'description': 'invalid example object without any value property' }
|
|
350
|
+
* }
|
|
351
|
+
* Returns >>>
|
|
352
|
+
* {
|
|
353
|
+
* 'example1': { 'value' : 'example_val1', 'description': 'some description' },
|
|
354
|
+
* 'example2': { 'value' : 'example_val2', 'description': 'some other description' }
|
|
355
|
+
* }
|
|
356
|
+
* if none of the keys represents an object with 'value' property then return undefined
|
|
357
|
+
* - Is an array of premitive values
|
|
358
|
+
* ['example_val1', 'example_val2']
|
|
359
|
+
* Returns >>>
|
|
360
|
+
* {
|
|
361
|
+
* 'Example1': {value:'value1'}
|
|
362
|
+
* 'Example2': {value:'value2'}
|
|
363
|
+
* }
|
|
364
|
+
* - Is a premitive value
|
|
365
|
+
* 'example_val1'
|
|
366
|
+
* Returns >>>
|
|
367
|
+
* {
|
|
368
|
+
* 'Example': { 'value': 'example_val1' }
|
|
369
|
+
* }
|
|
370
|
+
* - Is undefined
|
|
371
|
+
* returns undefined
|
|
372
|
+
* @returns
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
export function standardizeExample(ex) {
|
|
376
|
+
if (typeof ex === "object" && ex !== null && !Array.isArray(ex)) {
|
|
377
|
+
if (ex.value !== undefined) {
|
|
378
|
+
// Case 1: Single object with 'value' property
|
|
379
|
+
return { Example: { ...ex } };
|
|
380
|
+
}
|
|
381
|
+
// Case 2: Object where each key is an object with a 'value' property
|
|
382
|
+
const filteredEntries = Object.entries(ex).filter(([_, obj]) => obj?.value !== undefined); // oxlint-disable-line no-unused-vars
|
|
383
|
+
// If no valid entries found, return JSON.stringify of the input
|
|
384
|
+
if (filteredEntries.length === 0) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
return Object.fromEntries(filteredEntries);
|
|
388
|
+
}
|
|
389
|
+
if (Array.isArray(ex)) {
|
|
390
|
+
// Case 3: Array of primitive values
|
|
391
|
+
return ex.reduce((acc, value, index) => {
|
|
392
|
+
acc[`Example${index + 1}`] = { value };
|
|
393
|
+
return acc;
|
|
394
|
+
}, {});
|
|
395
|
+
}
|
|
396
|
+
// Case 4: Single primitive value
|
|
397
|
+
return ex ? { Example: { value: ex } } : undefined;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Normalize example object in the following format (List of object which is used to render example links and fill the input boxes)
|
|
402
|
+
* [{
|
|
403
|
+
* exampleVal : 'value to be rendered on the input control (text-box)',
|
|
404
|
+
* exampleList : [
|
|
405
|
+
* value : '',
|
|
406
|
+
* printableValue: '',
|
|
407
|
+
* summary : '',
|
|
408
|
+
* description : ''
|
|
409
|
+
* ]
|
|
410
|
+
* }]
|
|
411
|
+
* */
|
|
412
|
+
export function normalizeExamples(examples, dataType = "string") {
|
|
413
|
+
if (!examples) {
|
|
414
|
+
return {
|
|
415
|
+
exampleVal: "",
|
|
416
|
+
exampleList: [],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (examples.constructor === Object) {
|
|
420
|
+
const exampleList = Object.values(examples)
|
|
421
|
+
.filter((v) => v["x-example-show-value"] !== false)
|
|
422
|
+
.map((v) => ({
|
|
423
|
+
value:
|
|
424
|
+
typeof v.value === "boolean" || typeof v.value === "number"
|
|
425
|
+
? `${v.value}`
|
|
426
|
+
: v.value || "",
|
|
427
|
+
printableValue: getPrintableVal(v.value),
|
|
428
|
+
summary: v.summary || "",
|
|
429
|
+
description: v.description || "",
|
|
430
|
+
}));
|
|
431
|
+
const exampleVal = exampleList.length > 0 ? exampleList[0].value : "";
|
|
432
|
+
return { exampleVal, exampleList };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// This is non-standard way to provide example but will support for now
|
|
436
|
+
if (!Array.isArray(examples)) {
|
|
437
|
+
examples = examples ? [examples] : [];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (examples.length === 0) {
|
|
441
|
+
return {
|
|
442
|
+
exampleVal: "",
|
|
443
|
+
exampleList: [],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (dataType === "array") {
|
|
448
|
+
const [exampleVal] = examples;
|
|
449
|
+
const exampleList = examples.map((v) => ({
|
|
450
|
+
value: v,
|
|
451
|
+
printableValue: getPrintableVal(v),
|
|
452
|
+
}));
|
|
453
|
+
return { exampleVal, exampleList };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const exampleVal = examples[0].toString();
|
|
457
|
+
const exampleList = examples.map((v) => ({
|
|
458
|
+
value: v.toString(),
|
|
459
|
+
printableValue: getPrintableVal(v),
|
|
460
|
+
}));
|
|
461
|
+
return { exampleVal, exampleList };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function anyExampleWithSummaryOrDescription(examples) {
|
|
465
|
+
return examples.some((x) => x.summary?.length > 0 || x.description?.length > 0);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The 'example' property was deprecated in 3.1.0 in favor of the JSON Schema 'examples' keyword. However RapiDoc supports both. in the following order:
|
|
470
|
+
* - examples
|
|
471
|
+
* - example
|
|
472
|
+
*
|
|
473
|
+
* Returns the first example in the given schema.
|
|
474
|
+
* Returns `undefined` if `schema` is undefined or if there are no examples at the top level of the `schema`.
|
|
475
|
+
*/
|
|
476
|
+
function getFirstExample(schema) {
|
|
477
|
+
let firstExample;
|
|
478
|
+
if (schema) {
|
|
479
|
+
if (schema.examples && schema.examples.length >= 1) {
|
|
480
|
+
firstExample = schema.examples[0];
|
|
481
|
+
} else {
|
|
482
|
+
firstExample = schema.example;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return firstExample;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function getSampleValueByType(schemaObj) {
|
|
489
|
+
const example = getFirstExample(schemaObj);
|
|
490
|
+
if (example === "") {
|
|
491
|
+
return "";
|
|
492
|
+
}
|
|
493
|
+
if (example === null) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
if (example === 0) {
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
if (example === false) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
if (example instanceof Date) {
|
|
503
|
+
switch (schemaObj.format.toLowerCase()) {
|
|
504
|
+
case "date":
|
|
505
|
+
return example.toISOString().split("T")[0];
|
|
506
|
+
case "time":
|
|
507
|
+
return example.toISOString().split("T")[1];
|
|
508
|
+
default:
|
|
509
|
+
return example.toISOString();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (example) {
|
|
513
|
+
return example;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (Object.keys(schemaObj).length === 0) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
if (schemaObj.$ref) {
|
|
520
|
+
// Indicates a Circular ref
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
523
|
+
if (
|
|
524
|
+
schemaObj.const === false ||
|
|
525
|
+
schemaObj.const === 0 ||
|
|
526
|
+
schemaObj.const === null ||
|
|
527
|
+
schemaObj.const === ""
|
|
528
|
+
) {
|
|
529
|
+
return schemaObj.const;
|
|
530
|
+
}
|
|
531
|
+
if (schemaObj.const) {
|
|
532
|
+
return schemaObj.const;
|
|
533
|
+
}
|
|
534
|
+
if (schemaObj.default) {
|
|
535
|
+
return schemaObj.default;
|
|
536
|
+
}
|
|
537
|
+
const typeValue = Array.isArray(schemaObj.type) ? schemaObj.type[0] : schemaObj.type;
|
|
538
|
+
if (!typeValue) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
if (typeValue.match(/^integer|^number/g)) {
|
|
542
|
+
const multipleOf = Number.isNaN(Number(schemaObj.multipleOf))
|
|
543
|
+
? undefined
|
|
544
|
+
: Number(schemaObj.multipleOf);
|
|
545
|
+
const maximum = Number.isNaN(Number(schemaObj.maximum)) ? undefined : Number(schemaObj.maximum);
|
|
546
|
+
const minimumPossibleVal = Number.isNaN(Number(schemaObj.minimum))
|
|
547
|
+
? Number.isNaN(Number(schemaObj.exclusiveMinimum))
|
|
548
|
+
? maximum || 0
|
|
549
|
+
: Number(schemaObj.exclusiveMinimum) + (typeValue.startsWith("integer") ? 1 : 0.001)
|
|
550
|
+
: Number(schemaObj.minimum);
|
|
551
|
+
const finalVal = multipleOf
|
|
552
|
+
? multipleOf >= minimumPossibleVal
|
|
553
|
+
? multipleOf
|
|
554
|
+
: minimumPossibleVal % multipleOf === 0
|
|
555
|
+
? minimumPossibleVal
|
|
556
|
+
: Math.ceil(minimumPossibleVal / multipleOf) * multipleOf
|
|
557
|
+
: minimumPossibleVal;
|
|
558
|
+
return finalVal;
|
|
559
|
+
}
|
|
560
|
+
if (typeValue.match(/^boolean/g)) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
if (typeValue.match(/^null/g)) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
if (typeValue.match(/^string/g)) {
|
|
567
|
+
if (schemaObj.enum) {
|
|
568
|
+
return schemaObj.enum[0];
|
|
569
|
+
}
|
|
570
|
+
if (schemaObj.const) {
|
|
571
|
+
return schemaObj.const;
|
|
572
|
+
}
|
|
573
|
+
if (schemaObj.pattern) {
|
|
574
|
+
try {
|
|
575
|
+
return patternSampleGenerator(schemaObj.pattern);
|
|
576
|
+
} catch {
|
|
577
|
+
return schemaObj.pattern;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (schemaObj.contentEncoding) {
|
|
581
|
+
switch (schemaObj.contentEncoding.toLowerCase()) {
|
|
582
|
+
case "base64":
|
|
583
|
+
return "ZXhhbXBsZQ=="; // 'example' base64 encoded
|
|
584
|
+
case "binary":
|
|
585
|
+
case "7bit":
|
|
586
|
+
case "8bit":
|
|
587
|
+
case "quoted-printable":
|
|
588
|
+
return "string";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (schemaObj.contentMediaType) {
|
|
592
|
+
if (schemaObj.contentMediaType.toLowerCase() === "application/json") {
|
|
593
|
+
if (schemaObj.contentSchema) {
|
|
594
|
+
const sample = schemaToSampleObj(schemaObj.contentSchema);
|
|
595
|
+
const sampleVal = sample?.["example-0"] ?? sample;
|
|
596
|
+
return JSON.stringify(sampleVal);
|
|
597
|
+
}
|
|
598
|
+
return "{}";
|
|
599
|
+
}
|
|
600
|
+
if (schemaObj.contentMediaType.startsWith("text/")) {
|
|
601
|
+
return "text content";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (schemaObj.format) {
|
|
605
|
+
switch (schemaObj.format.toLowerCase()) {
|
|
606
|
+
case "url":
|
|
607
|
+
case "uri":
|
|
608
|
+
return "http://example.com";
|
|
609
|
+
case "date":
|
|
610
|
+
return new Date(0).toISOString().split("T")[0];
|
|
611
|
+
case "time":
|
|
612
|
+
return new Date(0).toISOString().split("T")[1];
|
|
613
|
+
case "date-time":
|
|
614
|
+
return new Date(0).toISOString();
|
|
615
|
+
case "duration":
|
|
616
|
+
return "P3Y6M4DT12H30M5S"; // P=Period 3-Years 6-Months 4-Days 12-Hours 30-Minutes 5-Seconds
|
|
617
|
+
case "email":
|
|
618
|
+
case "idn-email":
|
|
619
|
+
return "user@example.com";
|
|
620
|
+
case "hostname":
|
|
621
|
+
case "idn-hostname":
|
|
622
|
+
return "www.example.com";
|
|
623
|
+
case "ipv4":
|
|
624
|
+
return "198.51.100.42";
|
|
625
|
+
case "ipv6":
|
|
626
|
+
return "2001:0db8:5b96:0000:0000:426f:8e17:642a";
|
|
627
|
+
case "uuid":
|
|
628
|
+
return "3fa85f64-5717-4562-b3fc-2c963f66afa6";
|
|
629
|
+
case "byte":
|
|
630
|
+
case "base64":
|
|
631
|
+
return "ZXhhbXBsZQ=="; // 'example' base64 encoded. See https://spec.openapis.org/oas/v3.0.0#data-types
|
|
632
|
+
case "binary":
|
|
633
|
+
return "binary";
|
|
634
|
+
default:
|
|
635
|
+
return "";
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
const minLength = Number.isNaN(schemaObj.minLength) ? undefined : Number(schemaObj.minLength);
|
|
639
|
+
const maxLength = Number.isNaN(schemaObj.maxLength) ? undefined : Number(schemaObj.maxLength);
|
|
640
|
+
const finalLength = minLength || (maxLength > 6 ? 6 : maxLength || undefined);
|
|
641
|
+
return finalLength ? "A".repeat(finalLength) : "string";
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// If type cannot be determined
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/*
|
|
649
|
+
json2xml- TestCase
|
|
650
|
+
{
|
|
651
|
+
'prop1' : 'one',
|
|
652
|
+
'prop2' : 'two',
|
|
653
|
+
'prop3' : [ 'a', 'b', 'c' ],
|
|
654
|
+
'prop4' : {
|
|
655
|
+
'ob1' : 'val-1',
|
|
656
|
+
'ob2' : 'val-2'
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
<root>
|
|
660
|
+
<prop1>simple</prop1>
|
|
661
|
+
<prop2>
|
|
662
|
+
<0>a</0>
|
|
663
|
+
<1>b</1>
|
|
664
|
+
<2>c</2>
|
|
665
|
+
</prop2>
|
|
666
|
+
<prop3>
|
|
667
|
+
<ob1>val-1</ob1>
|
|
668
|
+
<ob2>val-2</ob2>
|
|
669
|
+
</prop3>
|
|
670
|
+
</root>
|
|
671
|
+
*/
|
|
672
|
+
export function json2xml(obj, level = 1) {
|
|
673
|
+
if (obj === null || obj === undefined) {
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
const indent = " ".repeat(level);
|
|
677
|
+
let xmlText = "";
|
|
678
|
+
if (typeof obj !== "object") {
|
|
679
|
+
return `\n${indent}${obj.toString()}`;
|
|
680
|
+
}
|
|
681
|
+
for (const prop in obj) {
|
|
682
|
+
if (prop.startsWith("::")) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const val = obj[prop];
|
|
686
|
+
let tagName = prop;
|
|
687
|
+
if (Array.isArray(val)) {
|
|
688
|
+
tagName = val["::XML_TAG"] || val[0]?.["::XML_TAG"] || prop;
|
|
689
|
+
} else if (val && typeof val === "object") {
|
|
690
|
+
tagName = val["::XML_TAG"] || prop;
|
|
691
|
+
} else if (Array.isArray(obj) && obj["::XML_TAG"]) {
|
|
692
|
+
tagName = obj["::XML_TAG"];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (val === null || val === undefined) {
|
|
696
|
+
xmlText = `${xmlText}\n${indent}<${tagName}></${tagName}>`;
|
|
697
|
+
} else if (Array.isArray(val)) {
|
|
698
|
+
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(val, level + 1)}\n${indent}</${tagName}>`;
|
|
699
|
+
} else if (typeof val === "object") {
|
|
700
|
+
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(val, level + 1)}\n${indent}</${tagName}>`;
|
|
701
|
+
} else {
|
|
702
|
+
xmlText = `${xmlText}\n${indent}<${tagName}>${val.toString()}</${tagName}>`;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return xmlText;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function addSchemaInfoToExample(schema, obj) {
|
|
709
|
+
if (typeof obj !== "object" || obj === null) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (schema.title) {
|
|
713
|
+
obj["::TITLE"] = schema.title;
|
|
714
|
+
}
|
|
715
|
+
if (schema.description) {
|
|
716
|
+
obj["::DESCRIPTION"] = schema.description;
|
|
717
|
+
}
|
|
718
|
+
if (schema.xml?.name) {
|
|
719
|
+
obj["::XML_TAG"] = schema.xml?.name;
|
|
720
|
+
}
|
|
721
|
+
if (schema.xml?.wrapped) {
|
|
722
|
+
obj["::XML_WRAP"] = schema.xml?.wrapped.toString();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function removeTitlesAndDescriptions(obj) {
|
|
727
|
+
if (typeof obj !== "object" || obj === null) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
delete obj["::TITLE"];
|
|
731
|
+
delete obj["::DESCRIPTION"];
|
|
732
|
+
delete obj["::XML_TAG"];
|
|
733
|
+
delete obj["::XML_WRAP"];
|
|
734
|
+
for (const k in obj) {
|
|
735
|
+
removeTitlesAndDescriptions(obj[k]);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function mergePropertyExamples(obj, propertyName, propExamples) {
|
|
740
|
+
if (!obj || typeof obj !== "object") {
|
|
741
|
+
return obj;
|
|
742
|
+
}
|
|
743
|
+
const propVal =
|
|
744
|
+
propExamples && typeof propExamples === "object" && "example-0" in propExamples
|
|
745
|
+
? propExamples["example-0"]
|
|
746
|
+
: propExamples &&
|
|
747
|
+
typeof propExamples === "object" &&
|
|
748
|
+
Object.keys(propExamples).length > 0 &&
|
|
749
|
+
!Array.isArray(propExamples)
|
|
750
|
+
? Object.values(propExamples)[0]
|
|
751
|
+
: propExamples;
|
|
752
|
+
|
|
753
|
+
for (const exampleKey in obj) {
|
|
754
|
+
if (obj[exampleKey] && typeof obj[exampleKey] === "object") {
|
|
755
|
+
obj[exampleKey][propertyName] = propVal;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return obj;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* For changing JSON-Schema to a Sample Object, as per the schema (to generate examples based on schema) */
|
|
762
|
+
export function schemaToSampleObj(schema, config = {}, level = 0) {
|
|
763
|
+
let obj = {};
|
|
764
|
+
if (!schema || level > 8) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (schema.allOf) {
|
|
768
|
+
const mergedObj = {};
|
|
769
|
+
|
|
770
|
+
if (schema.allOf.length === 1 && !schema.allOf[0]?.properties && !schema.allOf[0]?.items) {
|
|
771
|
+
// If allOf has single item and the type is not an object or array, then its a primitive
|
|
772
|
+
if (schema.allOf[0].$ref) {
|
|
773
|
+
return { "example-0": {} };
|
|
774
|
+
}
|
|
775
|
+
if (schema.allOf[0].readOnly && config.includeReadOnly) {
|
|
776
|
+
const tempSchema = schema.allOf[0];
|
|
777
|
+
return { "example-0": getSampleValueByType(tempSchema) };
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
schema.allOf.forEach((v) => {
|
|
783
|
+
if (!v) return;
|
|
784
|
+
if (v.type === "object" || v.properties || v.allOf || v.anyOf || v.oneOf) {
|
|
785
|
+
const partialSamples = schemaToSampleObj(v, config, level + 1);
|
|
786
|
+
const partialVal = partialSamples?.["example-0"] ?? partialSamples;
|
|
787
|
+
if (partialVal && typeof partialVal === "object" && !Array.isArray(partialVal)) {
|
|
788
|
+
Object.assign(mergedObj, partialVal);
|
|
789
|
+
}
|
|
790
|
+
} else if (v.type === "array" || v.items) {
|
|
791
|
+
const partialSamples = schemaToSampleObj(v, config, level + 1);
|
|
792
|
+
const partialVal = partialSamples?.["example-0"] ?? partialSamples;
|
|
793
|
+
if (Array.isArray(partialVal)) {
|
|
794
|
+
mergedObj[Object.keys(mergedObj).length] = partialVal;
|
|
795
|
+
}
|
|
796
|
+
} else if (v.type) {
|
|
797
|
+
const prop = `prop${Object.keys(mergedObj).length}`;
|
|
798
|
+
mergedObj[prop] = getSampleValueByType(v);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
obj = { "example-0": mergedObj };
|
|
803
|
+
addSchemaInfoToExample(schema, obj["example-0"]);
|
|
804
|
+
} else if (schema.oneOf) {
|
|
805
|
+
// 1. First create example with schema.properties
|
|
806
|
+
const objWithSchemaProps = {};
|
|
807
|
+
if (schema.properties) {
|
|
808
|
+
for (const propertyName in schema.properties) {
|
|
809
|
+
const propSchema = schema.properties[propertyName];
|
|
810
|
+
if (propSchema?.properties || propSchema?.items) {
|
|
811
|
+
const propSamples = schemaToSampleObj(propSchema, config, level + 1);
|
|
812
|
+
objWithSchemaProps[propertyName] = propSamples?.["example-0"] ?? propSamples;
|
|
813
|
+
} else {
|
|
814
|
+
objWithSchemaProps[propertyName] = getSampleValueByType(propSchema);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (schema.oneOf.length > 0) {
|
|
820
|
+
// If at root (level === 0), expand all oneOf variants as distinct examples.
|
|
821
|
+
// If nested (level > 0), only pick the first oneOf variant to avoid combinatorial duplication.
|
|
822
|
+
const variantsToProcess = level === 0 ? schema.oneOf : [schema.oneOf[0]];
|
|
823
|
+
let i = 0;
|
|
824
|
+
for (let k = 0; k < variantsToProcess.length; k++) {
|
|
825
|
+
const variantSchema = variantsToProcess[k];
|
|
826
|
+
const oneOfSamples = schemaToSampleObj(variantSchema, config, level + 1);
|
|
827
|
+
for (const sampleKey in oneOfSamples) {
|
|
828
|
+
let finalExample = oneOfSamples[sampleKey];
|
|
829
|
+
if (
|
|
830
|
+
Object.keys(objWithSchemaProps).length > 0 &&
|
|
831
|
+
finalExample &&
|
|
832
|
+
typeof finalExample === "object" &&
|
|
833
|
+
!Array.isArray(finalExample)
|
|
834
|
+
) {
|
|
835
|
+
finalExample = { ...objWithSchemaProps, ...finalExample };
|
|
836
|
+
}
|
|
837
|
+
obj[`example-${i}`] = finalExample;
|
|
838
|
+
addSchemaInfoToExample(variantSchema, obj[`example-${i}`]);
|
|
839
|
+
i++;
|
|
840
|
+
if (level > 0) {
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} else if (schema.anyOf) {
|
|
847
|
+
// First generate values for regular properties
|
|
848
|
+
let commonObj = { "example-0": {} };
|
|
849
|
+
if (schema.type === "object" || schema.properties) {
|
|
850
|
+
for (const propertyName in schema.properties) {
|
|
851
|
+
const propSchema = schema.properties[propertyName];
|
|
852
|
+
if (propSchema?.deprecated && !config.includeDeprecated) continue;
|
|
853
|
+
if (propSchema?.readOnly && !config.includeReadOnly) continue;
|
|
854
|
+
if (propSchema?.writeOnly && !config.includeWriteOnly) continue;
|
|
855
|
+
const propExample = getFirstExample(propSchema);
|
|
856
|
+
if (propExample !== undefined) {
|
|
857
|
+
commonObj["example-0"][propertyName] = propExample;
|
|
858
|
+
} else {
|
|
859
|
+
mergePropertyExamples(
|
|
860
|
+
commonObj,
|
|
861
|
+
propertyName,
|
|
862
|
+
schemaToSampleObj(propSchema, config, level + 1),
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (schema.anyOf.length > 0) {
|
|
869
|
+
const variantsToProcess = level === 0 ? schema.anyOf : [schema.anyOf[0]];
|
|
870
|
+
let i = 0;
|
|
871
|
+
for (let k = 0; k < variantsToProcess.length; k++) {
|
|
872
|
+
const variantSchema = variantsToProcess[k];
|
|
873
|
+
const anyOfSamples = schemaToSampleObj(variantSchema, config, level + 1);
|
|
874
|
+
for (const sampleKey in anyOfSamples) {
|
|
875
|
+
const sampleVal = anyOfSamples[sampleKey];
|
|
876
|
+
if (commonObj?.["example-0"] && Object.keys(commonObj["example-0"]).length > 0) {
|
|
877
|
+
obj[`example-${i}`] = {
|
|
878
|
+
...commonObj["example-0"],
|
|
879
|
+
...(typeof sampleVal === "object" ? sampleVal : {}),
|
|
880
|
+
};
|
|
881
|
+
} else {
|
|
882
|
+
obj[`example-${i}`] = sampleVal;
|
|
883
|
+
}
|
|
884
|
+
addSchemaInfoToExample(variantSchema, obj[`example-${i}`]);
|
|
885
|
+
i++;
|
|
886
|
+
if (level > 0) {
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} else if (schema.type === "object" || schema.properties) {
|
|
893
|
+
obj["example-0"] = {};
|
|
894
|
+
addSchemaInfoToExample(schema, obj["example-0"]);
|
|
895
|
+
const firstExample = getFirstExample(schema);
|
|
896
|
+
if (firstExample !== undefined) {
|
|
897
|
+
obj["example-0"] = firstExample;
|
|
898
|
+
} else {
|
|
899
|
+
for (const propertyName in schema.properties) {
|
|
900
|
+
const propSchema = schema.properties[propertyName];
|
|
901
|
+
if (!propSchema) continue;
|
|
902
|
+
if (propSchema.deprecated && !config.includeDeprecated) continue;
|
|
903
|
+
if (propSchema.readOnly && !config.includeReadOnly) continue;
|
|
904
|
+
if (propSchema.writeOnly && !config.includeWriteOnly) continue;
|
|
905
|
+
|
|
906
|
+
if (propSchema.type === "array" || propSchema.items) {
|
|
907
|
+
const propExample = getFirstExample(propSchema);
|
|
908
|
+
if (propExample !== undefined) {
|
|
909
|
+
obj["example-0"][propertyName] = propExample;
|
|
910
|
+
} else if (getFirstExample(propSchema.items) !== undefined) {
|
|
911
|
+
obj["example-0"][propertyName] = [getFirstExample(propSchema.items)];
|
|
912
|
+
} else {
|
|
913
|
+
const itemSamples = schemaToSampleObj(propSchema.items, config, level + 1);
|
|
914
|
+
const itemSampleVal = itemSamples?.["example-0"] ?? itemSamples;
|
|
915
|
+
if (config.useXmlTagForProp) {
|
|
916
|
+
const xmlTagName = propSchema.xml?.name || propertyName;
|
|
917
|
+
if (propSchema.xml?.wrapped) {
|
|
918
|
+
obj["example-0"][xmlTagName] = { [xmlTagName]: itemSampleVal };
|
|
919
|
+
} else {
|
|
920
|
+
obj["example-0"][xmlTagName] = [itemSampleVal];
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
obj["example-0"][propertyName] = [itemSampleVal];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
mergePropertyExamples(obj, propertyName, schemaToSampleObj(propSchema, config, level + 1));
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) {
|
|
933
|
+
const propName = schema.additionalProperties["x-additionalPropertiesName"] || "property";
|
|
934
|
+
mergePropertyExamples(
|
|
935
|
+
obj,
|
|
936
|
+
`${propName}1`,
|
|
937
|
+
schemaToSampleObj(schema.additionalProperties, config, level + 1),
|
|
938
|
+
);
|
|
939
|
+
mergePropertyExamples(
|
|
940
|
+
obj,
|
|
941
|
+
`${propName}2`,
|
|
942
|
+
schemaToSampleObj(schema.additionalProperties, config, level + 1),
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
} else if (schema.type === "array" || schema.items) {
|
|
947
|
+
const firstExample = getFirstExample(schema);
|
|
948
|
+
if (firstExample !== undefined) {
|
|
949
|
+
obj["example-0"] = firstExample;
|
|
950
|
+
} else if (schema.items) {
|
|
951
|
+
const itemsExample = getFirstExample(schema.items);
|
|
952
|
+
if (itemsExample !== undefined) {
|
|
953
|
+
obj["example-0"] = [itemsExample];
|
|
954
|
+
} else {
|
|
955
|
+
const samples = schemaToSampleObj(schema.items, config, level + 1);
|
|
956
|
+
const itemVal = samples?.["example-0"] ?? samples;
|
|
957
|
+
obj["example-0"] = [itemVal];
|
|
958
|
+
addSchemaInfoToExample(schema.items, obj["example-0"]);
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
obj["example-0"] = [];
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
return { "example-0": getSampleValueByType(schema) };
|
|
965
|
+
}
|
|
966
|
+
return obj;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function generateMarkdownForArrayAndObjectDescription(schema, level = 0) {
|
|
970
|
+
let mainText = "";
|
|
971
|
+
if (schema.title) {
|
|
972
|
+
if (schema.description) {
|
|
973
|
+
mainText = `<b>${schema.title}:</b> ${schema.description}`;
|
|
974
|
+
} else {
|
|
975
|
+
mainText = schema.title;
|
|
976
|
+
}
|
|
977
|
+
} else if (schema.description) {
|
|
978
|
+
mainText = schema.description;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const limits = [];
|
|
982
|
+
if (schema.minItems) {
|
|
983
|
+
limits.push(
|
|
984
|
+
`<span class="descr-label">Min Items:</span> <span class="descr-value">${schema.minItems}</span>`,
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
if (schema.maxItems) {
|
|
988
|
+
limits.push(
|
|
989
|
+
`<span class="descr-label">Max Items:</span> <span class="descr-value">${schema.maxItems}</span>`,
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
if (schema.uniqueItems === true) {
|
|
993
|
+
limits.push(`<span class="descr-label">Must have unique items</span>`);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const parts = mainText ? [mainText.trim()] : [];
|
|
997
|
+
if (limits.length > 0) {
|
|
998
|
+
// Same wrapper as the constraint chips, so both sit the same distance under the description.
|
|
999
|
+
// Kept on one line so markdown treats it as a single block.
|
|
1000
|
+
parts.push(`<div class="item-details">${limits.join(" ")}</div>`);
|
|
1001
|
+
}
|
|
1002
|
+
if (level > 0 && schema.items?.description) {
|
|
1003
|
+
let itemsMarkdown = "";
|
|
1004
|
+
if (schema.items.minProperties) {
|
|
1005
|
+
itemsMarkdown = `<span class="descr-label">Min Properties:</span> <span class="descr-value">${schema.items.minProperties}</span>`;
|
|
1006
|
+
}
|
|
1007
|
+
if (schema.items.maxProperties) {
|
|
1008
|
+
itemsMarkdown = `${itemsMarkdown} <span class="descr-label">Max Properties:</span> <span class="descr-value">${schema.items.maxProperties}</span>`;
|
|
1009
|
+
}
|
|
1010
|
+
parts.push(`⮕ ${itemsMarkdown} [ ${schema.items.description} ]`);
|
|
1011
|
+
}
|
|
1012
|
+
return parts.join("\n\n");
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* For changing OpenAPI-Schema to an Object Notation,
|
|
1016
|
+
* This Object would further be an input to UI Components to generate an Object-Tree
|
|
1017
|
+
* @param {object} schema - Schema object from OpenAPI spec
|
|
1018
|
+
* @param {object} obj - recursivly pass this object to generate object notation
|
|
1019
|
+
* @param {number} level - recursion level
|
|
1020
|
+
* @param {string} suffix - used for suffixing property names to avoid duplicate props during object composion
|
|
1021
|
+
*/
|
|
1022
|
+
/**
|
|
1023
|
+
* Helper to populate schema properties into a target object notation.
|
|
1024
|
+
*/
|
|
1025
|
+
function populateProperties(target, schema, level) {
|
|
1026
|
+
if (schema.properties) {
|
|
1027
|
+
for (const key in schema.properties) {
|
|
1028
|
+
const propKey = schema.required && schema.required.includes(key) ? `${key}*` : key;
|
|
1029
|
+
target[propKey] = schemaInObjectNotation(schema.properties[key], {}, level + 1);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (schema.patternProperties) {
|
|
1033
|
+
for (const key in schema.patternProperties) {
|
|
1034
|
+
target[`[pattern: ${key}]`] = schemaInObjectNotation(
|
|
1035
|
+
schema.patternProperties[key],
|
|
1036
|
+
{},
|
|
1037
|
+
level + 1,
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (schema.additionalProperties) {
|
|
1042
|
+
target["[any-key]"] = schemaInObjectNotation(schema.additionalProperties, {}, level + 1);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function handleObjectSchema(schema, baseObj = {}, level = 0) {
|
|
1047
|
+
const obj = Object.assign({}, baseObj);
|
|
1048
|
+
obj["::title"] = schema.title || "";
|
|
1049
|
+
obj["::description"] = generateMarkdownForArrayAndObjectDescription(schema, level);
|
|
1050
|
+
obj["::type"] = "object";
|
|
1051
|
+
if ((Array.isArray(schema.type) && schema.type.includes("null")) || schema.nullable) {
|
|
1052
|
+
obj["::dataTypeLabel"] = "object ┃ null";
|
|
1053
|
+
obj["::nullable"] = true;
|
|
1054
|
+
}
|
|
1055
|
+
obj["::deprecated"] = schema.deprecated || false;
|
|
1056
|
+
obj["::readwrite"] = schema.readOnly ? "readonly" : schema.writeOnly ? "writeonly" : "";
|
|
1057
|
+
populateProperties(obj, schema, level);
|
|
1058
|
+
return obj;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function handleArraySchema(schema, baseObj = {}, level = 0) {
|
|
1062
|
+
const obj = Object.assign({}, baseObj);
|
|
1063
|
+
obj["::title"] = schema.title || "";
|
|
1064
|
+
obj["::description"] = generateMarkdownForArrayAndObjectDescription(schema, level);
|
|
1065
|
+
obj["::type"] = "array";
|
|
1066
|
+
if ((Array.isArray(schema.type) && schema.type.includes("null")) || schema.nullable) {
|
|
1067
|
+
obj["::dataTypeLabel"] = "array ┃ null";
|
|
1068
|
+
obj["::nullable"] = true;
|
|
1069
|
+
}
|
|
1070
|
+
obj["::deprecated"] = schema.deprecated || false;
|
|
1071
|
+
obj["::readwrite"] = schema.readOnly ? "readonly" : schema.writeOnly ? "writeonly" : "";
|
|
1072
|
+
if (schema.items?.items) {
|
|
1073
|
+
obj["::array-type"] = schema.items.items.type;
|
|
1074
|
+
}
|
|
1075
|
+
obj["::props"] = schema.items ? schemaInObjectNotation(schema.items, {}, level + 1) : undefined;
|
|
1076
|
+
return obj;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function handleAllOfSchema(schema, baseObj = {}, level = 0) {
|
|
1080
|
+
if (schema.allOf.length === 1 && !schema.allOf[0].properties && !schema.allOf[0].items) {
|
|
1081
|
+
// If allOf has single item and the type is not an object or array, then its a primitive
|
|
1082
|
+
return `${getTypeInfo(schema.allOf[0]).html}`;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const objWithAllProps = Object.assign({}, baseObj);
|
|
1086
|
+
schema.allOf.forEach((v, i) => {
|
|
1087
|
+
if (v.type === "object" || v.properties || v.allOf || v.anyOf || v.oneOf) {
|
|
1088
|
+
const propSuffix = (v.anyOf || v.oneOf) && i > 0 ? `${i}` : "";
|
|
1089
|
+
const partialObj = schemaInObjectNotation(v, {}, level + 1, propSuffix);
|
|
1090
|
+
Object.assign(objWithAllProps, partialObj);
|
|
1091
|
+
} else if (v.type === "array" || v.items) {
|
|
1092
|
+
const partialObj = schemaInObjectNotation(v, {}, level + 1);
|
|
1093
|
+
Object.assign(objWithAllProps, partialObj);
|
|
1094
|
+
} else if (v.type) {
|
|
1095
|
+
const prop = `prop${Object.keys(objWithAllProps).length}`;
|
|
1096
|
+
const typeObj = getTypeInfo(v);
|
|
1097
|
+
objWithAllProps[prop] = `${typeObj.html}`;
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
return objWithAllProps;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function handleAnyOrOneOfSchema(schema, baseObj = {}, level = 0, suffix = "") {
|
|
1105
|
+
const obj = Object.assign({}, baseObj);
|
|
1106
|
+
obj["::description"] = schema.description || "";
|
|
1107
|
+
|
|
1108
|
+
// 1. First iterate the regular properties if defined
|
|
1109
|
+
if (schema.type === "object" || schema.properties) {
|
|
1110
|
+
obj["::type"] = "object";
|
|
1111
|
+
populateProperties(obj, schema, level);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// 2. Then build anyOf / oneOf option entries
|
|
1115
|
+
const objWithAnyOfProps = {};
|
|
1116
|
+
const xxxOf = schema.anyOf ? "anyOf" : "oneOf";
|
|
1117
|
+
schema[xxxOf].forEach((v, index) => {
|
|
1118
|
+
const optKey = `::OPTION~${index + 1}${v.title ? `~${v.title}` : ""}`;
|
|
1119
|
+
if (v.type === "object" || v.properties || v.allOf || v.anyOf || v.oneOf) {
|
|
1120
|
+
const partialObj = schemaInObjectNotation(v, {}, level + 1);
|
|
1121
|
+
objWithAnyOfProps[optKey] = partialObj;
|
|
1122
|
+
if (typeof partialObj === "object" && partialObj !== null) {
|
|
1123
|
+
partialObj["::readwrite"] = ""; // xxx-options cannot be read or write only
|
|
1124
|
+
}
|
|
1125
|
+
objWithAnyOfProps["::type"] = "xxx-of-option";
|
|
1126
|
+
} else if (v.type === "array" || v.items) {
|
|
1127
|
+
const partialObj = schemaInObjectNotation(v, {}, level + 1);
|
|
1128
|
+
objWithAnyOfProps[optKey] = partialObj;
|
|
1129
|
+
if (typeof partialObj === "object" && partialObj !== null) {
|
|
1130
|
+
partialObj["::readwrite"] = "";
|
|
1131
|
+
}
|
|
1132
|
+
objWithAnyOfProps["::type"] = "xxx-of-array";
|
|
1133
|
+
} else {
|
|
1134
|
+
objWithAnyOfProps[optKey] = `${getTypeInfo(v).html}`;
|
|
1135
|
+
objWithAnyOfProps["::type"] = "xxx-of-option";
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
const operatorKey = schema.anyOf ? "::ANY~OF" : "::ONE~OF";
|
|
1140
|
+
const fullKey = suffix ? `${operatorKey} ${suffix}` : operatorKey;
|
|
1141
|
+
obj[fullKey] = objWithAnyOfProps;
|
|
1142
|
+
obj["::type"] = "object";
|
|
1143
|
+
|
|
1144
|
+
return obj;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function handleMultiTypeSchema(schema, baseObj = {}, level = 0) {
|
|
1148
|
+
// Recognize OpenAPI 3.1 nullable object and array
|
|
1149
|
+
if (schema.type.length === 2 && schema.type.includes("null")) {
|
|
1150
|
+
if (schema.type.includes("object")) {
|
|
1151
|
+
return handleObjectSchema(schema, baseObj, level);
|
|
1152
|
+
}
|
|
1153
|
+
if (schema.type.includes("array")) {
|
|
1154
|
+
return handleArraySchema(schema, baseObj, level);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const subSchema =
|
|
1159
|
+
typeof structuredClone === "function"
|
|
1160
|
+
? structuredClone(schema)
|
|
1161
|
+
: JSON.parse(JSON.stringify(schema));
|
|
1162
|
+
const primitiveType = [];
|
|
1163
|
+
const complexTypes = [];
|
|
1164
|
+
|
|
1165
|
+
subSchema.type.forEach((v) => {
|
|
1166
|
+
if (v.match(/integer|number|string|null|boolean/g)) {
|
|
1167
|
+
primitiveType.push(v);
|
|
1168
|
+
} else if (
|
|
1169
|
+
v === "array" &&
|
|
1170
|
+
typeof subSchema.items?.type === "string" &&
|
|
1171
|
+
subSchema.items?.type.match(/integer|number|string|null|boolean/g)
|
|
1172
|
+
) {
|
|
1173
|
+
if (
|
|
1174
|
+
subSchema.items.type === "string" &&
|
|
1175
|
+
(subSchema.items.format || subSchema.items.contentMediaType)
|
|
1176
|
+
) {
|
|
1177
|
+
primitiveType.push(`[${subSchema.items.format || subSchema.items.contentMediaType}]`);
|
|
1178
|
+
} else {
|
|
1179
|
+
primitiveType.push(`[${subSchema.items.type}]`);
|
|
1180
|
+
}
|
|
1181
|
+
} else {
|
|
1182
|
+
complexTypes.push(v);
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
let multiPrimitiveTypes;
|
|
1187
|
+
if (primitiveType.length > 0) {
|
|
1188
|
+
subSchema.type = primitiveType.join("┃");
|
|
1189
|
+
multiPrimitiveTypes = getTypeInfo(subSchema);
|
|
1190
|
+
if (complexTypes.length === 0) {
|
|
1191
|
+
return `${multiPrimitiveTypes?.html || ""}`;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (complexTypes.length > 0) {
|
|
1196
|
+
const obj = Object.assign({}, baseObj);
|
|
1197
|
+
obj["::type"] = "object";
|
|
1198
|
+
const multiTypeOptions = {
|
|
1199
|
+
"::type": "xxx-of-option",
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
let optionIndex = 1;
|
|
1203
|
+
complexTypes.forEach((v) => {
|
|
1204
|
+
if (v === "object") {
|
|
1205
|
+
const objTypeOption = {
|
|
1206
|
+
"::title": schema.title || "",
|
|
1207
|
+
"::description": schema.description || "",
|
|
1208
|
+
"::type": "object",
|
|
1209
|
+
"::deprecated": schema.deprecated || false,
|
|
1210
|
+
};
|
|
1211
|
+
populateProperties(objTypeOption, schema, level);
|
|
1212
|
+
multiTypeOptions[`::OPTION~${optionIndex++}`] = objTypeOption;
|
|
1213
|
+
} else if (v === "array") {
|
|
1214
|
+
multiTypeOptions[`::OPTION~${optionIndex++}`] = {
|
|
1215
|
+
"::title": schema.title || "",
|
|
1216
|
+
"::description": schema.description || "",
|
|
1217
|
+
"::type": "array",
|
|
1218
|
+
"::props": schemaInObjectNotation(schema.items, {}, level + 1),
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
if (primitiveType.length > 0 && multiPrimitiveTypes?.html) {
|
|
1224
|
+
multiTypeOptions[`::OPTION~${optionIndex++}`] = multiPrimitiveTypes.html;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
obj["::ONE~OF"] = multiTypeOptions;
|
|
1228
|
+
return obj;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
return Object.assign({}, baseObj);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function createObjectAST(schema, level, name, isRequired) {
|
|
1235
|
+
const isNullable =
|
|
1236
|
+
schema.nullable || (Array.isArray(schema.type) && schema.type.includes("null")) || false;
|
|
1237
|
+
const properties = [];
|
|
1238
|
+
if (schema.properties) {
|
|
1239
|
+
for (const key in schema.properties) {
|
|
1240
|
+
const isPropReq = Array.isArray(schema.required) && schema.required.includes(key);
|
|
1241
|
+
const child = schemaToAST(schema.properties[key], level + 1, key, isPropReq);
|
|
1242
|
+
if (child) properties.push(child);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const patternProperties = [];
|
|
1247
|
+
if (schema.patternProperties) {
|
|
1248
|
+
for (const key in schema.patternProperties) {
|
|
1249
|
+
const child = schemaToAST(
|
|
1250
|
+
schema.patternProperties[key],
|
|
1251
|
+
level + 1,
|
|
1252
|
+
`[pattern: ${key}]`,
|
|
1253
|
+
false,
|
|
1254
|
+
);
|
|
1255
|
+
if (child) patternProperties.push(child);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
let additionalProperties = null;
|
|
1260
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
1261
|
+
additionalProperties = schemaToAST(schema.additionalProperties, level + 1, "[any-key]", false);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
return {
|
|
1265
|
+
kind: "object",
|
|
1266
|
+
name,
|
|
1267
|
+
title: schema.title || "",
|
|
1268
|
+
description: generateMarkdownForArrayAndObjectDescription(schema, level),
|
|
1269
|
+
required: isRequired,
|
|
1270
|
+
deprecated: schema.deprecated || false,
|
|
1271
|
+
readOnly: schema.readOnly || false,
|
|
1272
|
+
writeOnly: schema.writeOnly || false,
|
|
1273
|
+
nullable: isNullable,
|
|
1274
|
+
dataTypeLabel: isNullable ? "object ┃ null" : "object",
|
|
1275
|
+
properties,
|
|
1276
|
+
patternProperties,
|
|
1277
|
+
additionalProperties,
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function createArrayAST(schema, level, name, isRequired) {
|
|
1282
|
+
const isNullable =
|
|
1283
|
+
schema.nullable || (Array.isArray(schema.type) && schema.type.includes("null")) || false;
|
|
1284
|
+
let itemsNode = null;
|
|
1285
|
+
if (schema.items) {
|
|
1286
|
+
itemsNode = schemaToAST(schema.items, level + 1, "", false);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
let arrayType = "";
|
|
1290
|
+
if (schema.items?.items) {
|
|
1291
|
+
arrayType = schema.items.items.type || "";
|
|
1292
|
+
} else if (schema.items?.type && typeof schema.items.type === "string") {
|
|
1293
|
+
arrayType = schema.items.type;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return {
|
|
1297
|
+
kind: "array",
|
|
1298
|
+
name,
|
|
1299
|
+
title: schema.title || "",
|
|
1300
|
+
description: generateMarkdownForArrayAndObjectDescription(schema, level),
|
|
1301
|
+
required: isRequired,
|
|
1302
|
+
deprecated: schema.deprecated || false,
|
|
1303
|
+
readOnly: schema.readOnly || false,
|
|
1304
|
+
writeOnly: schema.writeOnly || false,
|
|
1305
|
+
nullable: isNullable,
|
|
1306
|
+
dataTypeLabel: isNullable ? "array ┃ null" : "array",
|
|
1307
|
+
arrayType,
|
|
1308
|
+
minItems: schema.minItems,
|
|
1309
|
+
maxItems: schema.maxItems,
|
|
1310
|
+
uniqueItems: schema.uniqueItems || false,
|
|
1311
|
+
items: itemsNode,
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function createPrimitiveAST(schema, name, isRequired) {
|
|
1316
|
+
const typeInfo = getTypeInfo(schema) || {};
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "primitive",
|
|
1319
|
+
name,
|
|
1320
|
+
type: typeInfo.type || schema.type || "",
|
|
1321
|
+
format: typeInfo.format || schema.format || "",
|
|
1322
|
+
contentMediaType: typeInfo.contentMediaType || schema.contentMediaType || "",
|
|
1323
|
+
contentEncoding: typeInfo.contentEncoding || schema.contentEncoding || "",
|
|
1324
|
+
contentSchema: typeInfo.contentSchema || schema.contentSchema || null,
|
|
1325
|
+
pattern: typeInfo.pattern || schema.pattern || "",
|
|
1326
|
+
constraints: typeInfo.constrain || "",
|
|
1327
|
+
defaultValue:
|
|
1328
|
+
typeInfo.default !== undefined && typeInfo.default !== "" ? String(typeInfo.default) : "",
|
|
1329
|
+
allowedValues: typeInfo.allowedValues || "",
|
|
1330
|
+
description: (schema.description || typeInfo.description || "").trim(),
|
|
1331
|
+
title: (schema.title || "").trim(),
|
|
1332
|
+
required: isRequired,
|
|
1333
|
+
deprecated: schema.deprecated || !!typeInfo.deprecated,
|
|
1334
|
+
readOnly: schema.readOnly || typeInfo.readOrWriteOnly === "🆁",
|
|
1335
|
+
writeOnly: schema.writeOnly || typeInfo.readOrWriteOnly === "🆆",
|
|
1336
|
+
html: typeInfo.html || "",
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Deepest JSON nesting level expanded; anything below an object or array at this level is truncated
|
|
1341
|
+
const MAX_SCHEMA_DEPTH = 8;
|
|
1342
|
+
// Largest tree built for one schema. A bigger tree (e.g. Stripe's expandable fields) is rebuilt one level shallower,
|
|
1343
|
+
// down to MIN_SCHEMA_DEPTH, so the page stays responsive
|
|
1344
|
+
const SCHEMA_NODE_BUDGET = 8000;
|
|
1345
|
+
const MIN_SCHEMA_DEPTH = 4;
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Whether a schema opens a new JSON nesting level (an object or array), directly or through allOf.
|
|
1349
|
+
* Primitives and unions are not containers: primitives cost nothing to show, and union options are checked one by one.
|
|
1350
|
+
* @param {object} schema - OpenAPI/JSON Schema object
|
|
1351
|
+
* @returns {boolean}
|
|
1352
|
+
*/
|
|
1353
|
+
function isContainerSchema(schema) {
|
|
1354
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
1355
|
+
return (
|
|
1356
|
+
types.includes("object") ||
|
|
1357
|
+
types.includes("array") ||
|
|
1358
|
+
!!(schema.properties || schema.patternProperties || schema.items) ||
|
|
1359
|
+
(!!schema.additionalProperties && typeof schema.additionalProperties === "object") ||
|
|
1360
|
+
!!schema.allOf?.some((sub) => sub && isContainerSchema(sub))
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// State of the schemaToAST() call in progress (conversion is synchronous)
|
|
1365
|
+
// Schemas on the current path, to stop on cycles that only go through allOf/anyOf/oneOf
|
|
1366
|
+
const schemasInProgress = new Set();
|
|
1367
|
+
let maxDepth = MAX_SCHEMA_DEPTH;
|
|
1368
|
+
let nodeCount = 0;
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Transforms an OpenAPI Schema into a strongly-typed Abstract Syntax Tree (AST) node.
|
|
1372
|
+
*
|
|
1373
|
+
* @param {object} schema - OpenAPI/JSON Schema object
|
|
1374
|
+
* @param {number} [level=0] - JSON nesting depth: grows through properties, items and additionalProperties, not through allOf/anyOf/oneOf
|
|
1375
|
+
* @param {string} [name=''] - Property or field name
|
|
1376
|
+
* @param {boolean} [isRequired=false] - Whether this property is required
|
|
1377
|
+
* @param {string} [suffix=''] - Suffix used for union composition
|
|
1378
|
+
* @returns {object|null} Typed SchemaNode AST
|
|
1379
|
+
*/
|
|
1380
|
+
export function schemaToAST(schema, level = 0, name = "", isRequired = false, suffix = "") {
|
|
1381
|
+
if (!schema) {
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
if (schemasInProgress.size > 0) {
|
|
1385
|
+
return nodeToAST(schema, level, name, isRequired, suffix);
|
|
1386
|
+
}
|
|
1387
|
+
// Top-level call: expand as deep as allowed while the tree fits the budget
|
|
1388
|
+
for (maxDepth = MAX_SCHEMA_DEPTH; ; maxDepth -= 1) {
|
|
1389
|
+
nodeCount = 0;
|
|
1390
|
+
const ast = nodeToAST(schema, level, name, isRequired, suffix);
|
|
1391
|
+
if (nodeCount <= SCHEMA_NODE_BUDGET || maxDepth <= MIN_SCHEMA_DEPTH) {
|
|
1392
|
+
return ast;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function nodeToAST(schema, level, name, isRequired, suffix) {
|
|
1398
|
+
if (!schema) {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
nodeCount += 1;
|
|
1402
|
+
// Once over budget this attempt is discarded, so stop expanding to get to the next one quickly
|
|
1403
|
+
const overBudget = nodeCount > SCHEMA_NODE_BUDGET && maxDepth > MIN_SCHEMA_DEPTH;
|
|
1404
|
+
if (
|
|
1405
|
+
schemasInProgress.has(schema) ||
|
|
1406
|
+
((level > maxDepth || overBudget) && isContainerSchema(schema))
|
|
1407
|
+
) {
|
|
1408
|
+
const isArray =
|
|
1409
|
+
schema.type === "array" ||
|
|
1410
|
+
(Array.isArray(schema.type) && schema.type.includes("array")) ||
|
|
1411
|
+
!!schema.items;
|
|
1412
|
+
return {
|
|
1413
|
+
kind: isArray ? "array" : "object",
|
|
1414
|
+
name,
|
|
1415
|
+
type: isArray ? "array" : "object",
|
|
1416
|
+
title: schema.title || "",
|
|
1417
|
+
description: schema.description || "",
|
|
1418
|
+
required: isRequired,
|
|
1419
|
+
truncated: true,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
schemasInProgress.add(schema);
|
|
1423
|
+
try {
|
|
1424
|
+
return composeSchemaAST(schema, level, name, isRequired, suffix);
|
|
1425
|
+
} finally {
|
|
1426
|
+
schemasInProgress.delete(schema);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function composeSchemaAST(schema, level, name, isRequired, suffix) {
|
|
1431
|
+
// 1. allOf
|
|
1432
|
+
if (schema.allOf) {
|
|
1433
|
+
if (schema.allOf.length === 1 && !schema.allOf[0].properties && !schema.allOf[0].items) {
|
|
1434
|
+
const mergedSchema = {
|
|
1435
|
+
...schema.allOf[0],
|
|
1436
|
+
...schema,
|
|
1437
|
+
title: schema.title || schema.allOf[0].title || "",
|
|
1438
|
+
description: schema.description || schema.allOf[0].description || "",
|
|
1439
|
+
};
|
|
1440
|
+
delete mergedSchema.allOf;
|
|
1441
|
+
return schemaToAST(mergedSchema, level, name, isRequired);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const mergedProps = [];
|
|
1445
|
+
const mergedPatternProps = [];
|
|
1446
|
+
let mergedAdditionalProps = null;
|
|
1447
|
+
const unionChildren = [];
|
|
1448
|
+
let mergedTitle = schema.title || "";
|
|
1449
|
+
let mergedDescription = schema.description || "";
|
|
1450
|
+
let mergedDeprecated = schema.deprecated || false;
|
|
1451
|
+
let mergedReadOnly = schema.readOnly || false;
|
|
1452
|
+
let mergedWriteOnly = schema.writeOnly || false;
|
|
1453
|
+
let mergedNullable = schema.nullable || false;
|
|
1454
|
+
|
|
1455
|
+
schema.allOf.forEach((sub, i) => {
|
|
1456
|
+
if (sub.title && !mergedTitle) mergedTitle = sub.title;
|
|
1457
|
+
if (sub.description && !mergedDescription) mergedDescription = sub.description;
|
|
1458
|
+
if (sub.deprecated) mergedDeprecated = true;
|
|
1459
|
+
if (sub.readOnly) mergedReadOnly = true;
|
|
1460
|
+
if (sub.writeOnly) mergedWriteOnly = true;
|
|
1461
|
+
if (sub.nullable) mergedNullable = true;
|
|
1462
|
+
|
|
1463
|
+
if (sub.type === "object" || sub.properties || sub.allOf || sub.anyOf || sub.oneOf) {
|
|
1464
|
+
const subSuffix = (sub.anyOf || sub.oneOf) && i > 0 ? `${i}` : "";
|
|
1465
|
+
const subAst = schemaToAST(sub, level, "", false, subSuffix);
|
|
1466
|
+
if (subAst) {
|
|
1467
|
+
if (subAst.kind === "object") {
|
|
1468
|
+
if (subAst.properties) mergedProps.push(...subAst.properties);
|
|
1469
|
+
if (subAst.patternProperties) mergedPatternProps.push(...subAst.patternProperties);
|
|
1470
|
+
if (subAst.additionalProperties) mergedAdditionalProps = subAst.additionalProperties;
|
|
1471
|
+
} else if (subAst.kind === "union") {
|
|
1472
|
+
unionChildren.push(subAst);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
} else if (sub.type === "array" || sub.items) {
|
|
1476
|
+
const subAst = schemaToAST(sub, level);
|
|
1477
|
+
if (subAst) {
|
|
1478
|
+
mergedProps.push(subAst);
|
|
1479
|
+
}
|
|
1480
|
+
} else if (sub.type) {
|
|
1481
|
+
const propName = `prop${mergedProps.length}`;
|
|
1482
|
+
mergedProps.push(schemaToAST(sub, level, propName, false));
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
return {
|
|
1487
|
+
kind: "object",
|
|
1488
|
+
name,
|
|
1489
|
+
title: mergedTitle,
|
|
1490
|
+
description: generateMarkdownForArrayAndObjectDescription(
|
|
1491
|
+
{ ...schema, title: mergedTitle, description: mergedDescription },
|
|
1492
|
+
level,
|
|
1493
|
+
),
|
|
1494
|
+
required: isRequired,
|
|
1495
|
+
deprecated: mergedDeprecated,
|
|
1496
|
+
readOnly: mergedReadOnly,
|
|
1497
|
+
writeOnly: mergedWriteOnly,
|
|
1498
|
+
nullable: mergedNullable,
|
|
1499
|
+
dataTypeLabel: mergedNullable ? "object ┃ null" : "object",
|
|
1500
|
+
properties: mergedProps,
|
|
1501
|
+
patternProperties: mergedPatternProps,
|
|
1502
|
+
additionalProperties: mergedAdditionalProps,
|
|
1503
|
+
unions: unionChildren,
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// 2. anyOf / oneOf
|
|
1508
|
+
if (schema.anyOf || schema.oneOf) {
|
|
1509
|
+
const operator = schema.anyOf ? "anyOf" : "oneOf";
|
|
1510
|
+
const rawOptions = schema.anyOf || schema.oneOf;
|
|
1511
|
+
const options = rawOptions
|
|
1512
|
+
.map((opt, idx) => {
|
|
1513
|
+
const optTitle = opt.title || "";
|
|
1514
|
+
const optAst = schemaToAST(opt, level, optTitle || `Option ${idx + 1}`, false);
|
|
1515
|
+
if (optAst) {
|
|
1516
|
+
optAst.readOnly = false;
|
|
1517
|
+
optAst.writeOnly = false;
|
|
1518
|
+
optAst.optionIndex = idx + 1;
|
|
1519
|
+
optAst.optionTitle = optTitle;
|
|
1520
|
+
if (optAst.kind === "primitive" && !optTitle) {
|
|
1521
|
+
// The OPT n badge already names it, a row labelled "Option n" would only repeat it
|
|
1522
|
+
optAst.name = "";
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return optAst;
|
|
1526
|
+
})
|
|
1527
|
+
.filter(Boolean);
|
|
1528
|
+
|
|
1529
|
+
let baseProps = [];
|
|
1530
|
+
if (schema.type === "object" || schema.properties) {
|
|
1531
|
+
if (schema.properties) {
|
|
1532
|
+
for (const key in schema.properties) {
|
|
1533
|
+
const isReq = Array.isArray(schema.required) && schema.required.includes(key);
|
|
1534
|
+
const propNode = schemaToAST(schema.properties[key], level + 1, key, isReq);
|
|
1535
|
+
if (propNode) baseProps.push(propNode);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
return {
|
|
1541
|
+
kind: "union",
|
|
1542
|
+
operator,
|
|
1543
|
+
name,
|
|
1544
|
+
suffix,
|
|
1545
|
+
title: schema.title || "",
|
|
1546
|
+
description: schema.description || "",
|
|
1547
|
+
required: isRequired,
|
|
1548
|
+
properties: baseProps,
|
|
1549
|
+
options,
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// 3. Multi-type array (OpenAPI 3.1)
|
|
1554
|
+
if (Array.isArray(schema.type)) {
|
|
1555
|
+
if (schema.type.length === 2 && schema.type.includes("null")) {
|
|
1556
|
+
if (schema.type.includes("object")) {
|
|
1557
|
+
return createObjectAST(schema, level, name, isRequired);
|
|
1558
|
+
}
|
|
1559
|
+
if (schema.type.includes("array")) {
|
|
1560
|
+
return createArrayAST(schema, level, name, isRequired);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
const subSchema =
|
|
1565
|
+
typeof structuredClone === "function"
|
|
1566
|
+
? structuredClone(schema)
|
|
1567
|
+
: JSON.parse(JSON.stringify(schema));
|
|
1568
|
+
const primitiveType = [];
|
|
1569
|
+
const complexTypes = [];
|
|
1570
|
+
|
|
1571
|
+
subSchema.type.forEach((v) => {
|
|
1572
|
+
if (v.match(/integer|number|string|null|boolean/g)) {
|
|
1573
|
+
primitiveType.push(v);
|
|
1574
|
+
} else if (
|
|
1575
|
+
v === "array" &&
|
|
1576
|
+
typeof subSchema.items?.type === "string" &&
|
|
1577
|
+
subSchema.items?.type.match(/integer|number|string|null|boolean/g)
|
|
1578
|
+
) {
|
|
1579
|
+
if (
|
|
1580
|
+
subSchema.items.type === "string" &&
|
|
1581
|
+
(subSchema.items.format || subSchema.items.contentMediaType)
|
|
1582
|
+
) {
|
|
1583
|
+
primitiveType.push(`[${subSchema.items.format || subSchema.items.contentMediaType}]`);
|
|
1584
|
+
} else {
|
|
1585
|
+
primitiveType.push(`[${subSchema.items.type}]`);
|
|
1586
|
+
}
|
|
1587
|
+
} else {
|
|
1588
|
+
complexTypes.push(v);
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
if (complexTypes.length === 0 && primitiveType.length > 0) {
|
|
1593
|
+
subSchema.type = primitiveType.join("┃");
|
|
1594
|
+
return createPrimitiveAST(subSchema, name, isRequired);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (complexTypes.length > 0) {
|
|
1598
|
+
const options = [];
|
|
1599
|
+
let optionIndex = 1;
|
|
1600
|
+
complexTypes.forEach((v) => {
|
|
1601
|
+
if (v === "object") {
|
|
1602
|
+
const objNode = createObjectAST(
|
|
1603
|
+
schema,
|
|
1604
|
+
level,
|
|
1605
|
+
schema.title || `Option ${optionIndex}`,
|
|
1606
|
+
false,
|
|
1607
|
+
);
|
|
1608
|
+
objNode.optionIndex = optionIndex++;
|
|
1609
|
+
objNode.optionTitle = schema.title || "";
|
|
1610
|
+
options.push(objNode);
|
|
1611
|
+
} else if (v === "array") {
|
|
1612
|
+
const arrNode = createArrayAST(
|
|
1613
|
+
schema,
|
|
1614
|
+
level,
|
|
1615
|
+
schema.title || `Option ${optionIndex}`,
|
|
1616
|
+
false,
|
|
1617
|
+
);
|
|
1618
|
+
arrNode.optionIndex = optionIndex++;
|
|
1619
|
+
arrNode.optionTitle = schema.title || "";
|
|
1620
|
+
options.push(arrNode);
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
if (primitiveType.length > 0) {
|
|
1625
|
+
subSchema.type = primitiveType.join("┃");
|
|
1626
|
+
const primNode = createPrimitiveAST(subSchema, `Option ${optionIndex}`, false);
|
|
1627
|
+
primNode.optionIndex = optionIndex++;
|
|
1628
|
+
options.push(primNode);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
return {
|
|
1632
|
+
kind: "union",
|
|
1633
|
+
operator: "oneOf",
|
|
1634
|
+
name,
|
|
1635
|
+
title: schema.title || "",
|
|
1636
|
+
description: schema.description || "",
|
|
1637
|
+
required: isRequired,
|
|
1638
|
+
options,
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// 4. Object
|
|
1644
|
+
if (schema.type === "object" || schema.properties) {
|
|
1645
|
+
return createObjectAST(schema, level, name, isRequired);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 5. Array
|
|
1649
|
+
if (schema.type === "array" || schema.items) {
|
|
1650
|
+
return createArrayAST(schema, level, name, isRequired);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// 6. Primitive
|
|
1654
|
+
return createPrimitiveAST(schema, name, isRequired);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* For changing OpenAPI-Schema to an Object Notation,
|
|
1659
|
+
* This Object would further be an input to UI Components to generate an Object-Tree
|
|
1660
|
+
* @deprecated Use schemaToAST instead.
|
|
1661
|
+
* @param {object} schema - Schema object from OpenAPI spec
|
|
1662
|
+
* @param {object} [obj={}] - base object to populate (defaults to new object)
|
|
1663
|
+
* @param {number} [level=0] - recursion level
|
|
1664
|
+
* @param {string} [suffix=''] - used for suffixing property names to avoid duplicate props during object composition
|
|
1665
|
+
*/
|
|
1666
|
+
export function schemaInObjectNotation(schema, obj = {}, level = 0, suffix = "") {
|
|
1667
|
+
if (!schema) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (level > 8) {
|
|
1671
|
+
return {
|
|
1672
|
+
...obj,
|
|
1673
|
+
"::type": schema.type || "object",
|
|
1674
|
+
"::description": schema.description || "",
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
if (schema.allOf) {
|
|
1678
|
+
return handleAllOfSchema(schema, obj, level);
|
|
1679
|
+
}
|
|
1680
|
+
if (schema.anyOf || schema.oneOf) {
|
|
1681
|
+
return handleAnyOrOneOfSchema(schema, obj, level, suffix);
|
|
1682
|
+
}
|
|
1683
|
+
if (Array.isArray(schema.type)) {
|
|
1684
|
+
return handleMultiTypeSchema(schema, obj, level);
|
|
1685
|
+
}
|
|
1686
|
+
if (schema.type === "object" || schema.properties) {
|
|
1687
|
+
return handleObjectSchema(schema, obj, level);
|
|
1688
|
+
}
|
|
1689
|
+
if (schema.type === "array" || schema.items) {
|
|
1690
|
+
return handleArraySchema(schema, obj, level);
|
|
1691
|
+
}
|
|
1692
|
+
const typeObj = getTypeInfo(schema);
|
|
1693
|
+
if (typeObj?.html) {
|
|
1694
|
+
return `${typeObj.html}`;
|
|
1695
|
+
}
|
|
1696
|
+
return "";
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Helper to consistently format raw example values according to mimeType and outputType.
|
|
1701
|
+
*/
|
|
1702
|
+
function formatExampleContent(rawVal, mimeType = "", outputType = "json") {
|
|
1703
|
+
let content = rawVal;
|
|
1704
|
+
let format = "text";
|
|
1705
|
+
|
|
1706
|
+
if (mimeType?.toLowerCase().includes("json")) {
|
|
1707
|
+
if (outputType === "text") {
|
|
1708
|
+
content = typeof rawVal === "string" ? rawVal : JSON.stringify(rawVal, undefined, 2);
|
|
1709
|
+
format = "text";
|
|
1710
|
+
} else if (typeof rawVal === "object" && rawVal !== null) {
|
|
1711
|
+
content = rawVal;
|
|
1712
|
+
format = "json";
|
|
1713
|
+
} else if (typeof rawVal === "string") {
|
|
1714
|
+
try {
|
|
1715
|
+
content = JSON.parse(rawVal);
|
|
1716
|
+
format = "json";
|
|
1717
|
+
} catch {
|
|
1718
|
+
content = rawVal;
|
|
1719
|
+
format = "text";
|
|
1720
|
+
}
|
|
1721
|
+
} else {
|
|
1722
|
+
content = rawVal;
|
|
1723
|
+
format = "json";
|
|
1724
|
+
}
|
|
1725
|
+
} else {
|
|
1726
|
+
content =
|
|
1727
|
+
typeof rawVal === "object" && rawVal !== null
|
|
1728
|
+
? JSON.stringify(rawVal, undefined, 2)
|
|
1729
|
+
: String(rawVal ?? "");
|
|
1730
|
+
format = "text";
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
return { content, format };
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/* Create Example object */
|
|
1737
|
+
export function generateExample(
|
|
1738
|
+
schema,
|
|
1739
|
+
mimeType = "",
|
|
1740
|
+
examples = null,
|
|
1741
|
+
example = null,
|
|
1742
|
+
includeReadOnly = true,
|
|
1743
|
+
includeWriteOnly = true,
|
|
1744
|
+
outputType = "json",
|
|
1745
|
+
includeGeneratedExample = false,
|
|
1746
|
+
) {
|
|
1747
|
+
const finalExamples = [];
|
|
1748
|
+
const isJson = mimeType?.toLowerCase().includes("json");
|
|
1749
|
+
const isXml = mimeType?.toLowerCase().includes("xml");
|
|
1750
|
+
|
|
1751
|
+
// 1. Process multiple examples (examples map)
|
|
1752
|
+
if (examples && typeof examples === "object" && Object.keys(examples).length > 0) {
|
|
1753
|
+
for (const eg in examples) {
|
|
1754
|
+
const rawVal = examples[eg]?.value !== undefined ? examples[eg].value : examples[eg];
|
|
1755
|
+
const { content, format } = formatExampleContent(rawVal, mimeType, outputType);
|
|
1756
|
+
|
|
1757
|
+
finalExamples.push({
|
|
1758
|
+
exampleId: eg,
|
|
1759
|
+
exampleSummary: examples[eg]?.summary || eg,
|
|
1760
|
+
exampleDescription: examples[eg]?.description || "",
|
|
1761
|
+
exampleType: mimeType,
|
|
1762
|
+
exampleValue: content,
|
|
1763
|
+
exampleFormat: format,
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
// 2. Process single example (when examples is absent or empty)
|
|
1768
|
+
else if (
|
|
1769
|
+
example !== null &&
|
|
1770
|
+
example !== undefined &&
|
|
1771
|
+
(typeof example !== "object" || Object.keys(example).length > 0)
|
|
1772
|
+
) {
|
|
1773
|
+
const rawVal = example?.value !== undefined ? example.value : example;
|
|
1774
|
+
const { content, format } = formatExampleContent(rawVal, mimeType, outputType);
|
|
1775
|
+
|
|
1776
|
+
finalExamples.push({
|
|
1777
|
+
exampleId: "Example",
|
|
1778
|
+
exampleSummary: example?.summary || "",
|
|
1779
|
+
exampleDescription: example?.description || "",
|
|
1780
|
+
exampleType: mimeType,
|
|
1781
|
+
exampleValue: content,
|
|
1782
|
+
exampleFormat: format,
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// 3. Fallback to schema-level example or schema-generated sample
|
|
1787
|
+
if (finalExamples.length === 0 || includeGeneratedExample === true) {
|
|
1788
|
+
if (schema) {
|
|
1789
|
+
const firstExample = getFirstExample(schema);
|
|
1790
|
+
if (firstExample !== undefined && firstExample !== null) {
|
|
1791
|
+
const { content, format } = formatExampleContent(firstExample, mimeType, outputType);
|
|
1792
|
+
finalExamples.push({
|
|
1793
|
+
exampleId: "Example",
|
|
1794
|
+
exampleSummary: "",
|
|
1795
|
+
exampleDescription: "",
|
|
1796
|
+
exampleType: mimeType,
|
|
1797
|
+
exampleValue: content,
|
|
1798
|
+
exampleFormat: format,
|
|
1799
|
+
});
|
|
1800
|
+
} else if (
|
|
1801
|
+
isJson ||
|
|
1802
|
+
isXml ||
|
|
1803
|
+
mimeType?.toLowerCase().includes("text") ||
|
|
1804
|
+
mimeType?.toLowerCase().includes("yaml") ||
|
|
1805
|
+
mimeType?.toLowerCase().includes("*/*")
|
|
1806
|
+
) {
|
|
1807
|
+
let xmlRootStart = "";
|
|
1808
|
+
let xmlRootEnd = "";
|
|
1809
|
+
let exampleFormat = outputType;
|
|
1810
|
+
|
|
1811
|
+
if (isXml) {
|
|
1812
|
+
xmlRootStart = schema.xml?.name
|
|
1813
|
+
? `<${schema.xml.name}${schema.xml.namespace ? ` xmlns="${schema.xml.namespace}"` : ""}>`
|
|
1814
|
+
: "<root>";
|
|
1815
|
+
xmlRootEnd = schema.xml?.name ? `</${schema.xml.name}>` : "</root>";
|
|
1816
|
+
exampleFormat = "text";
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const samples = schemaToSampleObj(schema, {
|
|
1820
|
+
includeReadOnly,
|
|
1821
|
+
includeWriteOnly,
|
|
1822
|
+
deprecated: true,
|
|
1823
|
+
useXmlTagForProp: isXml,
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
let i = 0;
|
|
1827
|
+
for (const samplesKey in samples) {
|
|
1828
|
+
if (!samples[samplesKey]) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
const summary = samples[samplesKey]["::TITLE"] || `Example ${++i}`;
|
|
1832
|
+
const description = samples[samplesKey]["::DESCRIPTION"] || "";
|
|
1833
|
+
let exampleValue = "";
|
|
1834
|
+
|
|
1835
|
+
if (isXml) {
|
|
1836
|
+
exampleValue = `<?xml version="1.0" encoding="UTF-8"?>\n${xmlRootStart}${json2xml(samples[samplesKey], 1)}\n${xmlRootEnd}`;
|
|
1837
|
+
} else {
|
|
1838
|
+
removeTitlesAndDescriptions(samples[samplesKey]);
|
|
1839
|
+
exampleValue =
|
|
1840
|
+
outputType === "text"
|
|
1841
|
+
? JSON.stringify(samples[samplesKey], null, 2)
|
|
1842
|
+
: samples[samplesKey];
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
finalExamples.push({
|
|
1846
|
+
exampleId: samplesKey,
|
|
1847
|
+
exampleSummary: summary,
|
|
1848
|
+
exampleDescription: description,
|
|
1849
|
+
exampleType: mimeType,
|
|
1850
|
+
exampleFormat,
|
|
1851
|
+
exampleValue,
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
} else if (mimeType?.toLowerCase().includes("jose")) {
|
|
1855
|
+
finalExamples.push({
|
|
1856
|
+
exampleId: "Example",
|
|
1857
|
+
exampleSummary: "Base64 Encoded",
|
|
1858
|
+
exampleDescription: "",
|
|
1859
|
+
exampleType: mimeType,
|
|
1860
|
+
exampleValue: schema.pattern || "bXJpbg==",
|
|
1861
|
+
exampleFormat: "text",
|
|
1862
|
+
});
|
|
1863
|
+
} else {
|
|
1864
|
+
finalExamples.push({
|
|
1865
|
+
exampleId: "Example",
|
|
1866
|
+
exampleSummary: "",
|
|
1867
|
+
exampleDescription: "",
|
|
1868
|
+
exampleType: mimeType,
|
|
1869
|
+
exampleValue: "",
|
|
1870
|
+
exampleFormat: "text",
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
} else {
|
|
1874
|
+
// No Example or Schema provided
|
|
1875
|
+
finalExamples.push({
|
|
1876
|
+
exampleId: "Example",
|
|
1877
|
+
exampleSummary: "",
|
|
1878
|
+
exampleDescription: "",
|
|
1879
|
+
exampleType: mimeType,
|
|
1880
|
+
exampleValue: "",
|
|
1881
|
+
exampleFormat: "text",
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
return finalExamples;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function getSerializeStyleForContentType(contentType) {
|
|
1890
|
+
if (contentType === "application/json") {
|
|
1891
|
+
return "json";
|
|
1892
|
+
}
|
|
1893
|
+
if (contentType === "application/xml") {
|
|
1894
|
+
return "xml";
|
|
1895
|
+
}
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/**
|
|
1900
|
+
* Text that names an example in an examples dropdown: its summary, or its key when it has none.
|
|
1901
|
+
* A long summary is shown whole; the dropdown wraps it, or cuts it where the browser's select can't wrap.
|
|
1902
|
+
*/
|
|
1903
|
+
export function exampleLabel(example) {
|
|
1904
|
+
return example?.exampleSummary || example?.exampleId || "";
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
export function getSchemaFromParam(param) {
|
|
1908
|
+
if (param.schema) {
|
|
1909
|
+
return [param.schema, null, null];
|
|
1910
|
+
}
|
|
1911
|
+
if (param.content) {
|
|
1912
|
+
// we gonna use the first content-encoding
|
|
1913
|
+
for (const contentType of Object.keys(param.content)) {
|
|
1914
|
+
if (param.content[contentType].schema) {
|
|
1915
|
+
return [
|
|
1916
|
+
param.content[contentType].schema,
|
|
1917
|
+
getSerializeStyleForContentType(contentType),
|
|
1918
|
+
param.content[contentType],
|
|
1919
|
+
];
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return [null, null, null];
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
export const EMPTY_ARRAY = Object.freeze([]);
|
|
1927
|
+
|
|
1928
|
+
export function getMergedProperties(schema, visited = new Set()) {
|
|
1929
|
+
if (!schema || typeof schema !== "object" || visited.has(schema)) {
|
|
1930
|
+
return {};
|
|
1931
|
+
}
|
|
1932
|
+
visited.add(schema);
|
|
1933
|
+
const properties = { ...schema.properties };
|
|
1934
|
+
if (Array.isArray(schema.allOf)) {
|
|
1935
|
+
schema.allOf.forEach((sub) => {
|
|
1936
|
+
Object.assign(properties, getMergedProperties(sub, visited));
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
return properties;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
export function getMergedRequired(schema, visited = new Set()) {
|
|
1943
|
+
if (!schema || typeof schema !== "object" || visited.has(schema)) {
|
|
1944
|
+
return [];
|
|
1945
|
+
}
|
|
1946
|
+
visited.add(schema);
|
|
1947
|
+
const required = Array.isArray(schema.required) ? [...schema.required] : [];
|
|
1948
|
+
if (Array.isArray(schema.allOf)) {
|
|
1949
|
+
schema.allOf.forEach((sub) => {
|
|
1950
|
+
required.push(...getMergedRequired(sub, visited));
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
return [...new Set(required)];
|
|
1954
|
+
}
|