@xyd-js/openapi 0.1.0-build.168

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 (64) hide show
  1. package/CHANGELOG.md +1517 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/__fixtures__/-2.complex.openai/input.yaml +39848 -0
  5. package/__fixtures__/-2.complex.openai/output.json +321646 -0
  6. package/__fixtures__/-2.complex.openai/pluginOasOpenai.ts +553 -0
  7. package/__fixtures__/-3.random/input.yaml +234 -0
  8. package/__fixtures__/-3.random/output.json +1140 -0
  9. package/__fixtures__/1.basic/input.yaml +226 -0
  10. package/__fixtures__/1.basic/output.json +1919 -0
  11. package/__fixtures__/2.more/input.yaml +76 -0
  12. package/__fixtures__/2.more/output.json +327 -0
  13. package/__fixtures__/3.multiple-responses/input.yaml +48 -0
  14. package/__fixtures__/3.multiple-responses/output.json +311 -0
  15. package/__fixtures__/5.xdocs.codeLanguages/input.yaml +231 -0
  16. package/__fixtures__/5.xdocs.codeLanguages/output.json +1879 -0
  17. package/__fixtures__/5.xdocs.sidebar/input.yaml +256 -0
  18. package/__fixtures__/5.xdocs.sidebar/output.json +843 -0
  19. package/__fixtures__/6.codeSamples/input.yaml +75 -0
  20. package/__fixtures__/6.codeSamples/output.json +293 -0
  21. package/__tests__/oapSchemaToReferences.test.ts +82 -0
  22. package/__tests__/utils.ts +81 -0
  23. package/dist/index.cjs +2154 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +40 -0
  26. package/dist/index.d.ts +40 -0
  27. package/dist/index.js +2119 -0
  28. package/dist/index.js.map +1 -0
  29. package/examples/basic/index.ts +20 -0
  30. package/examples/basic/index2.ts +36 -0
  31. package/examples/basic/openapi.yaml +124 -0
  32. package/examples/dist/index.cjs +2 -0
  33. package/examples/dist/index.cjs.map +1 -0
  34. package/examples/dist/index.d.cts +2 -0
  35. package/examples/dist/index.d.ts +2 -0
  36. package/examples/dist/index.js +2 -0
  37. package/examples/dist/index.js.map +1 -0
  38. package/examples/semi/index.ts +16 -0
  39. package/examples/semi/openapi.yaml +365 -0
  40. package/examples/semi/references.json +500 -0
  41. package/examples/webhooks/index.ts +16 -0
  42. package/examples/webhooks/openapi.yaml +248 -0
  43. package/examples/webhooks/references.json +895 -0
  44. package/index.ts +12 -0
  45. package/package.json +31 -0
  46. package/src/const.ts +14 -0
  47. package/src/converters/oas-componentSchemas.ts +205 -0
  48. package/src/converters/oas-examples.ts +530 -0
  49. package/src/converters/oas-parameters.ts +41 -0
  50. package/src/converters/oas-paths.ts +354 -0
  51. package/src/converters/oas-requestBody.ts +57 -0
  52. package/src/converters/oas-responses.ts +76 -0
  53. package/src/converters/oas-schema.ts +141 -0
  54. package/src/index.ts +21 -0
  55. package/src/oas-core.ts +579 -0
  56. package/src/types.ts +18 -0
  57. package/src/utils.ts +157 -0
  58. package/src/xdocs/index.ts +18 -0
  59. package/src/xdocs/pluginSidebar.ts +580 -0
  60. package/src/xdocs/types.ts +26 -0
  61. package/tsconfig.json +18 -0
  62. package/tsup.config.ts +19 -0
  63. package/tsup.examples-config.ts +30 -0
  64. package/vitest.config.ts +7 -0
@@ -0,0 +1,530 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import Oas from "oas";
3
+ // @ts-ignore
4
+ import {Operation} from 'oas/operation'; // TODO: fix ts
5
+ import oasToSnippet from "@readme/oas-to-snippet";
6
+ import {JSONSchema, sample as openApiSampler} from '@xyd-js/openapi-sampler';
7
+ import {JSONSchema7, JSONSchema7Definition} from "json-schema";
8
+
9
+ import {ExampleGroup, Example, CodeBlockTab} from "@xyd-js/uniform";
10
+
11
+ import {BUILT_IN_PROPERTIES} from "../const";
12
+ import {xDocsLanguages} from "../xdocs";
13
+
14
+ // TODO: custom snippet languages options
15
+ const DEFAULT_CODE_LANGUAGES = ["shell", "javascript", "python", "go"]
16
+
17
+ // TODO: option with another languages
18
+ export function oapExamples(
19
+ oas: Oas,
20
+ operation: Operation,
21
+ visitedExamples?: Map<JSONSchema7 | JSONSchema7[], any>
22
+ ): ExampleGroup[] {
23
+ const exampleGroups = [
24
+ ...reqExamples(operation, oas, visitedExamples),
25
+ ...resBodyExmaples(operation, oas, visitedExamples),
26
+ ]
27
+
28
+ return exampleGroups
29
+ }
30
+
31
+ function langFallback(lang: string): string {
32
+ const langLower = lang.toLowerCase()
33
+
34
+ switch (langLower) {
35
+ case "curl": {
36
+ return "shell";
37
+ }
38
+ }
39
+
40
+ return langLower;
41
+ }
42
+
43
+ function smartDeepCopy<T>(obj: T, excludeProps: string[] = []): T {
44
+ const seen = new WeakMap();
45
+
46
+ function copy(value: any): any {
47
+ // Handle primitives and null
48
+ if (value === null || typeof value !== 'object') {
49
+ return value;
50
+ }
51
+
52
+ // Handle arrays
53
+ if (Array.isArray(value)) {
54
+ return value.map(copy);
55
+ }
56
+
57
+ // Handle dates
58
+ if (value instanceof Date) {
59
+ return new Date(value);
60
+ }
61
+
62
+ // Check for circular references
63
+ if (seen.has(value)) {
64
+ return seen.get(value);
65
+ }
66
+
67
+ // Create new object
68
+ const result: any = {};
69
+ seen.set(value, result);
70
+
71
+ // Copy all properties except excluded ones
72
+ for (const [key, val] of Object.entries(value)) {
73
+ const propPath = key;
74
+ if (!excludeProps.some(prop => propPath.startsWith(prop))) {
75
+ result[key] = copy(val);
76
+ }
77
+ }
78
+
79
+ return result;
80
+ }
81
+
82
+ return copy(obj);
83
+ }
84
+
85
+ function excludeProperties(operation: Operation, excludeProps: string[]): Operation {
86
+ return smartDeepCopy(operation, excludeProps);
87
+ }
88
+
89
+ function reqExamples(operation: Operation, oas: Oas, vistedExamples?: Map<JSONSchema7 | JSONSchema7[], any>): ExampleGroup[] {
90
+ const exampleGroups: ExampleGroup[] = []
91
+ const examples: Example[] = []
92
+ const tabs: CodeBlockTab[] = []
93
+
94
+ // Handle x-codeSamples if present
95
+ if (operation.schema['x-codeSamples']) {
96
+ const codeSamples = operation.schema['x-codeSamples'] as Array<{ lang: string; source: string; label?: string }>
97
+
98
+ // Group code samples by label
99
+ const groupedByLabel = new Map<string, Array<{ lang: string; source: string; label?: string }>>()
100
+
101
+ codeSamples.forEach(sample => {
102
+ const label = sample.label || 'default'
103
+ if (!groupedByLabel.has(label)) {
104
+ groupedByLabel.set(label, [])
105
+ }
106
+ groupedByLabel.get(label)!.push(sample)
107
+ })
108
+
109
+ // Create examples for each label group
110
+ groupedByLabel.forEach((samples, label) => {
111
+ const codeSampleTabs: CodeBlockTab[] = samples.map(sample => ({
112
+ title: sample.lang,
113
+ language: langFallback(sample.lang),
114
+ code: sample.source
115
+ }))
116
+
117
+ if (codeSampleTabs.length > 0) {
118
+ examples.push({
119
+ codeblock: {
120
+ title: label === 'default' ? "Example request" : label,
121
+ tabs: codeSampleTabs
122
+ }
123
+ })
124
+ }
125
+ })
126
+
127
+ if (examples.length > 0) {
128
+ exampleGroups.push({
129
+ description: "Example request",
130
+ examples
131
+ })
132
+
133
+ return exampleGroups
134
+ }
135
+ }
136
+
137
+ // Create a single object with all parameters grouped by their location
138
+ const paramData = operation.schema.parameters
139
+ ? (operation.schema.parameters as OpenAPIV3.ParameterObject[]).reduce((acc, param) => {
140
+ const location = param.in || 'query'
141
+ if (!acc[location]) {
142
+ acc[location] = {}
143
+ }
144
+
145
+ let value = param.example
146
+ if (!value && param.schema) {
147
+ value = openApiSampler(sanitizeSchema(param.schema as JSONSchema7))
148
+ }
149
+ if (value !== undefined) {
150
+ acc[location][param.name] = value
151
+ }
152
+ return acc
153
+ }, {} as Record<string, Record<string, any>>)
154
+ : {}
155
+
156
+ // Get request body data if it exists
157
+ let bodyData = {}
158
+ if (operation.schema.requestBody) {
159
+ const body = operation.schema.requestBody as OpenAPIV3.RequestBodyObject
160
+ const contentTypes = Object.keys(body.content)
161
+
162
+ if (contentTypes.length > 0) {
163
+ const contentType = contentTypes[contentTypes.length - 1]
164
+ const content = body.content[contentType]
165
+ let schema = content?.schema as JSONSchema7
166
+
167
+ if (schema) {
168
+ schema = fixAllOfBug(schema)
169
+ schema = sanitizeSchema(schema)
170
+
171
+ let requestData
172
+ if (content.examples) {
173
+ const requestExample = content.examples["request"]
174
+ if (requestExample && "value" in requestExample) {
175
+ requestData = requestExample.value
176
+ }
177
+ }
178
+
179
+ if (!requestData) {
180
+ requestData = sampleFromSchema(schema)
181
+ }
182
+
183
+ if (contentType === 'application/x-www-form-urlencoded') {
184
+ bodyData = {formData: requestData}
185
+ } else {
186
+ bodyData = {body: requestData}
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Check if we have parameters or request body
193
+ const hasRequestBody = operation.schema.requestBody !== undefined
194
+ const hasParameters = Object.keys(paramData).length > 0
195
+
196
+ // Generate examples if we have either parameters or request body, or if we have neither
197
+ if (hasParameters || hasRequestBody || (!hasRequestBody && !hasParameters)) {
198
+ const langs = xDocsLanguages(operation.api) || DEFAULT_CODE_LANGUAGES
199
+ langs.forEach(lang => {
200
+ // Sanitize operation to remove circular references before passing to oasToSnippet
201
+ let snippetOperation = operation
202
+ const v = operation?.api?.paths?.[operation?.path]?.[operation?.method]
203
+ if (v) {
204
+ snippetOperation = fixCircularReferences(v);
205
+ }
206
+
207
+ const {code} = oasToSnippet(oas, snippetOperation, {
208
+ ...paramData,
209
+ ...bodyData
210
+ }, null, lang)
211
+
212
+ tabs.push({
213
+ title: lang,
214
+ language: lang,
215
+ code: code || ""
216
+ })
217
+ })
218
+
219
+ if (tabs.length > 0) {
220
+ examples.push({
221
+ codeblock: {
222
+ tabs
223
+ }
224
+ })
225
+ }
226
+
227
+ if (examples.length > 0) {
228
+ exampleGroups.push({
229
+ description: "Example request",
230
+ examples
231
+ })
232
+ }
233
+ }
234
+
235
+ return exampleGroups
236
+ }
237
+
238
+ function resBodyExmaples(operation: Operation, oas: Oas, vistedExamples?: Map<JSONSchema7 | JSONSchema7[], any>): ExampleGroup[] {
239
+ const exampleGroups: ExampleGroup[] = []
240
+
241
+
242
+ if (operation.schema.responses) {
243
+ const responses = operation.schema.responses as OpenAPIV3.ResponsesObject
244
+
245
+ const examples: Example[] = []
246
+
247
+ Object.entries(responses).forEach(([status, r]) => {
248
+ const response = r as OpenAPIV3.ResponseObject
249
+ if (!response.content) {
250
+ return
251
+ }
252
+
253
+ const contentTypes = Object.keys(response.content)
254
+ if (contentTypes.length === 0) {
255
+ return
256
+ }
257
+
258
+ const tabs: CodeBlockTab[] = []
259
+
260
+ for (const contentType of contentTypes) {
261
+ const content = response.content[contentType]
262
+ const schema = content?.schema as JSONSchema7
263
+
264
+ if (!schema) {
265
+ continue
266
+ }
267
+
268
+ let responseData
269
+ // Check for examples in the response content
270
+ if (content.examples) {
271
+ const responseExample = content.examples["response"]
272
+ if (responseExample && "value" in responseExample) {
273
+ responseData = responseExample.value
274
+ } else {
275
+ const namedExamples: Example[] = []
276
+ const exampleNames = Object.keys(content.examples)
277
+
278
+ exampleNames.forEach((exampleName) => {
279
+ const data = content?.examples?.[exampleName]
280
+
281
+ if (!data || !("value" in data) || typeof data.value != "object") {
282
+ return
283
+ }
284
+
285
+ namedExamples.push({
286
+ description: "",
287
+ codeblock: {
288
+ title: exampleName,
289
+ tabs: [
290
+ {
291
+ title: "application/json", // TODO: support multiple types
292
+ language: "json",
293
+ code: JSON.stringify(data.value, null, 2) || "",
294
+ }
295
+ ]
296
+ }
297
+ })
298
+ })
299
+
300
+ if (namedExamples.length === 1) {
301
+ const firstCodeblock = namedExamples[0].codeblock
302
+
303
+ tabs.push(
304
+ ...firstCodeblock.tabs.map(tab => ({
305
+ ...tab,
306
+ title: contentType
307
+ }))
308
+ )
309
+ } else {
310
+ exampleGroups.push({
311
+ description: "",
312
+ examples: namedExamples
313
+ })
314
+ }
315
+
316
+ continue
317
+ }
318
+ } else if (content.example) {
319
+ responseData = content.example
320
+ }
321
+
322
+ // If no example found, generate sample data from schema
323
+ if (!responseData) {
324
+ responseData = sampleFromSchema(schema)
325
+ }
326
+
327
+ let extension = "text"
328
+ switch (contentType) {
329
+ case "application/json":
330
+ case "application/problem+json":
331
+ case "application/vnd.api+json": {
332
+ extension = "json"
333
+ break
334
+ }
335
+ case "application/xml":
336
+ case "text/xml":
337
+ case "application/problem+xml": {
338
+ extension = "xml"
339
+ break
340
+ }
341
+ }
342
+
343
+ tabs.push({
344
+ title: contentType,
345
+ language: extension,
346
+ code: JSON.stringify(responseData, null, 2) || "",
347
+ })
348
+ }
349
+
350
+ if (tabs.length > 0) {
351
+ examples.push({
352
+ codeblock: {
353
+ title: status,
354
+ tabs
355
+ }
356
+ })
357
+ }
358
+ })
359
+
360
+ if (examples.length > 0) {
361
+ exampleGroups.push({
362
+ description: "Example response",
363
+ examples
364
+ })
365
+ }
366
+ }
367
+
368
+ return exampleGroups
369
+ }
370
+
371
+ /**
372
+ * fixAllOfBug fixes below case:
373
+ *
374
+ * ```yaml
375
+ * allOf:
376
+ * - $ref: '#/components/schemas/SomeSchema'
377
+ * - type: object
378
+ * required:
379
+ * properties:
380
+ * ```
381
+ *
382
+ */
383
+ function fixAllOfBug(schema: JSONSchema7) {
384
+ const modifiedSchema = {...schema}
385
+
386
+ if (schema?.allOf) {
387
+ schema.allOf.forEach((prop, i) => {
388
+ const propObj = prop as object
389
+
390
+ if ("properties" in propObj && !propObj["properties"]) {
391
+ delete modifiedSchema.allOf?.[i]
392
+ }
393
+ })
394
+ }
395
+
396
+ return modifiedSchema
397
+ }
398
+
399
+
400
+ function sanitizeSchema(
401
+ schema: any,
402
+ vistedExamples: Map<JSONSchema7 | JSONSchema7[], any> = new Map(),
403
+ parent?: any
404
+ ): any {
405
+ if (vistedExamples.has(schema)) {
406
+ const cached = vistedExamples.get(schema);
407
+
408
+ if (typeof cached === 'object') {
409
+ return JSON.parse(JSON.stringify(cached)); // Return a deep copy of the cached schema
410
+ }
411
+
412
+ return cached
413
+ }
414
+
415
+ if (parent) {
416
+ vistedExamples.set(schema, parent);
417
+ }
418
+
419
+ if (!schema || typeof schema !== 'object') {
420
+ vistedExamples.set(schema, schema);
421
+ return schema;
422
+ }
423
+
424
+ if (Array.isArray(schema)) {
425
+ const v = schema.map(item => sanitizeSchema(item, vistedExamples));
426
+ vistedExamples.set(schema, v);
427
+ return v;
428
+ }
429
+
430
+ const cleaned: any = {};
431
+ for (const [key, value] of Object.entries(schema)) {
432
+ if (key === "__UNSAFE_refPath") {
433
+ continue;
434
+ }
435
+ if (!BUILT_IN_PROPERTIES[key]) {
436
+ cleaned[key] = sanitizeSchema(value, vistedExamples, cleaned);
437
+ }
438
+ }
439
+ vistedExamples.set(schema, cleaned);
440
+ return cleaned;
441
+ }
442
+
443
+ function sampleFromSchema(
444
+ schema: JSONSchema7
445
+ ) {
446
+ let jsonSchema: JSONSchema | null = null
447
+
448
+ let multiSpec: JSONSchema7Definition[] | null = null
449
+
450
+ if (schema.oneOf?.length) {
451
+ // for one of schemas, we take from the last one
452
+ multiSpec = schema.oneOf
453
+ } else if (schema.anyOf?.length) {
454
+ // for any of schemas, we take from the last one
455
+ multiSpec = schema.anyOf
456
+ }
457
+
458
+ if (multiSpec?.length) {
459
+ // for one of schemas, we take from the last one
460
+ for (let i = multiSpec.length - 1; i >= 0; i--) {
461
+ const spec = multiSpec[i];
462
+ const sanitized = sanitizeSchema(spec)
463
+ if (!sanitized) {
464
+ continue
465
+ }
466
+ jsonSchema = sanitized
467
+
468
+ if (!jsonSchema?.properties) {
469
+ continue
470
+ }
471
+ break;
472
+ }
473
+ } else {
474
+ jsonSchema = sanitizeSchema(schema)
475
+ }
476
+
477
+ if (jsonSchema) {
478
+ return openApiSampler(jsonSchema)
479
+ }
480
+
481
+ return null
482
+ }
483
+
484
+ function fixCircularReferences(schema: any, visited: WeakMap<any, any> = new WeakMap()): any {
485
+ if (!schema || typeof schema !== 'object') {
486
+ return schema;
487
+ }
488
+
489
+ // Check if we've already processed this object to prevent infinite recursion
490
+ if (visited.has(schema)) {
491
+ return visited.get(schema);
492
+ }
493
+
494
+ // Check if this schema has circular references
495
+ if ((schema as any).__UNSAFE_circular) {
496
+ // Return a simplified version without circular references
497
+ const simplified = {
498
+ type: 'object',
499
+ description: 'Circular reference detected - schema simplified'
500
+ };
501
+ visited.set(schema, simplified);
502
+ return simplified;
503
+ }
504
+
505
+ // Handle arrays
506
+ if (Array.isArray(schema)) {
507
+ const result = schema.map(item => fixCircularReferences(item, visited));
508
+ visited.set(schema, result);
509
+ return result;
510
+ }
511
+
512
+ // Recursively fix circular references in nested objects
513
+ const fixedSchema: any = {};
514
+ visited.set(schema, fixedSchema); // Set early to prevent infinite recursion
515
+
516
+ for (const [key, value] of Object.entries(schema)) {
517
+ if (key === '__UNSAFE_circular' || key === '__UNSAFE_refPath') {
518
+ continue; // Skip unsafe properties
519
+ }
520
+
521
+ if (typeof value === 'object' && value !== null) {
522
+ fixedSchema[key] = fixCircularReferences(value, visited);
523
+ } else {
524
+ fixedSchema[key] = value;
525
+ }
526
+ }
527
+
528
+ return fixedSchema;
529
+ }
530
+
@@ -0,0 +1,41 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+
3
+ import {DefinitionProperty} from "@xyd-js/uniform";
4
+
5
+ import { schemaObjectToUniformDefinitionPropertyMeta } from "../oas-core";
6
+
7
+ // oapParametersToDefinitionProperties converts OpenAPI parameters to uniform DefinitionProperties
8
+ export function oapParametersToDefinitionProperties(
9
+ parameters: OpenAPIV3.ParameterObject[]
10
+ ): { [key: string]: DefinitionProperty[] } {
11
+ const parameterIn: { [key: string]: DefinitionProperty[] } = {}
12
+
13
+ parameters.forEach((param) => {
14
+ if (!parameterIn[param.in]) {
15
+ parameterIn[param.in] = []
16
+ }
17
+
18
+ const schema = param.schema as OpenAPIV3.SchemaObject
19
+
20
+ const meta = [
21
+ ...(schemaObjectToUniformDefinitionPropertyMeta(schema, param.name) || []),
22
+ ...(schemaObjectToUniformDefinitionPropertyMeta(param, param.name) || []),
23
+ ]
24
+
25
+ let oapV2Type = ""
26
+ if ("type" in param) {
27
+ oapV2Type = param.type as string
28
+ }
29
+
30
+ const property: DefinitionProperty = {
31
+ name: param.name,
32
+ type: schema?.type || oapV2Type || "",
33
+ description: param.description || "",
34
+ meta
35
+ }
36
+
37
+ parameterIn[param.in].push(property)
38
+ })
39
+
40
+ return parameterIn
41
+ }