@xyd-js/openapi 0.1.0-xyd.11 → 0.1.0-xyd.13

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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/__fixtures__/-2.complex.openai/input.yaml +39848 -0
  3. package/__fixtures__/-2.complex.openai/output.json +321646 -0
  4. package/__fixtures__/-2.complex.openai/pluginOasOpenai.ts +553 -0
  5. package/__fixtures__/1.basic/input.yaml +226 -0
  6. package/__fixtures__/1.basic/output.json +1919 -0
  7. package/__fixtures__/2.more/input.yaml +76 -0
  8. package/__fixtures__/2.more/output.json +292 -0
  9. package/__fixtures__/3.multiple-responses/input.yaml +48 -0
  10. package/__fixtures__/3.multiple-responses/output.json +266 -0
  11. package/__fixtures__/4.abc/input.yaml +639 -0
  12. package/__fixtures__/4.abc/output.json +3828 -0
  13. package/__fixtures__/5.xdocs.codeLanguages/input.yaml +231 -0
  14. package/__fixtures__/5.xdocs.codeLanguages/output.json +1879 -0
  15. package/__fixtures__/5.xdocs.sidebar/input.yaml +256 -0
  16. package/__fixtures__/5.xdocs.sidebar/output.json +843 -0
  17. package/__fixtures__/6.codeSamples/input.yaml +75 -0
  18. package/__fixtures__/6.codeSamples/output.json +293 -0
  19. package/__tests__/oapSchemaToReferences.test.ts +88 -0
  20. package/__tests__/utils.ts +81 -0
  21. package/dist/index.cjs +1859 -162
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +36 -4
  24. package/dist/index.d.ts +36 -4
  25. package/dist/index.js +1855 -155
  26. package/dist/index.js.map +1 -1
  27. package/index.ts +10 -2
  28. package/package.json +11 -6
  29. package/src/const.ts +5 -1
  30. package/src/converters/oas-componentSchemas.ts +205 -0
  31. package/src/converters/oas-examples.ts +417 -0
  32. package/src/{parameters.ts → converters/oas-parameters.ts} +17 -3
  33. package/src/converters/oas-paths.ts +354 -0
  34. package/src/{requestBody.ts → converters/oas-requestBody.ts} +30 -10
  35. package/src/converters/oas-responses.ts +76 -0
  36. package/src/converters/oas-schema.ts +141 -0
  37. package/src/index.ts +13 -5
  38. package/src/oas-core.ts +579 -0
  39. package/src/types.ts +18 -0
  40. package/src/utils.ts +103 -90
  41. package/src/xdocs/index.ts +18 -0
  42. package/src/xdocs/pluginSidebar.ts +580 -0
  43. package/src/xdocs/types.ts +26 -0
  44. package/vitest.config.ts +7 -0
  45. package/src/examples.ts +0 -116
  46. package/src/paths.ts +0 -103
  47. package/src/properties.ts +0 -37
  48. package/src/responses.ts +0 -38
  49. package/src/schema.ts +0 -62
@@ -0,0 +1,205 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+
3
+ import {
4
+ Reference,
5
+ Definition,
6
+ DefinitionProperty,
7
+ ReferenceType,
8
+ OpenAPIReferenceContext, SymbolDef,
9
+ ExampleGroup,
10
+ CodeBlockTab,
11
+ } from "@xyd-js/uniform";
12
+
13
+ import {OasJSONSchema, uniformOasOptions} from "../types";
14
+ import {schemaObjectToUniformDefinitionProperties} from "../oas-core";
15
+
16
+ export function schemaComponentsToUniformReferences(
17
+ openapi: OpenAPIV3.Document,
18
+ options?: uniformOasOptions,
19
+ ): Reference[] {
20
+ const references: Reference[] = [];
21
+
22
+ if (!openapi.components?.schemas) {
23
+ return references;
24
+ }
25
+
26
+ for (const [componentSchemaName, componentSchema] of Object.entries(openapi.components.schemas)) {
27
+ if (options?.regions && options.regions.length > 0) {
28
+ if (!options.regions.some(region => region === "/components/schemas/" + componentSchemaName)) {
29
+ continue
30
+ }
31
+ }
32
+
33
+ if ('$ref' in componentSchema) {
34
+ console.warn(`Skipping reference object: ${componentSchemaName}`);
35
+ continue; // Skip reference objects
36
+ }
37
+
38
+ let properties: DefinitionProperty[] = [];
39
+ let rootProperty: DefinitionProperty | undefined = undefined;
40
+ const respProperties = schemaObjectToUniformDefinitionProperties(componentSchema, false) || [];
41
+ if (Array.isArray(respProperties)) {
42
+ properties = respProperties
43
+ } else {
44
+ rootProperty = respProperties
45
+ }
46
+
47
+ const symbolDef = definitionPropertyTypeDef(componentSchema)
48
+
49
+ const definition: Definition = {
50
+ title: componentSchemaName,
51
+ properties,
52
+ rootProperty,
53
+ meta: [],
54
+ symbolDef,
55
+ };
56
+
57
+ // Create reference
58
+ const reference: Reference = {
59
+ title: componentSchemaName,
60
+ description: componentSchema.description || "",
61
+ canonical: `objects/${componentSchemaName}`,
62
+ definitions: [definition],
63
+ examples: {
64
+ groups: createSchemaExampleGroup(componentSchema as OpenAPIV3.SchemaObject)
65
+ },
66
+ type: ReferenceType.REST_COMPONENT_SCHEMA,
67
+ context: {
68
+ componentSchema: componentSchemaName,
69
+ group: ["Objects"]
70
+ } as OpenAPIReferenceContext
71
+ };
72
+
73
+ // TODO: !!!! better api !!!!
74
+ reference.__UNSAFE_selector = function __UNSAFE_selector(selector: string) {
75
+ switch (selector) {
76
+ case "[schema]": {
77
+ return openapi
78
+ }
79
+
80
+ case "[component]": {
81
+ return componentSchema
82
+ }
83
+
84
+ default:
85
+ return null
86
+ }
87
+ }
88
+
89
+ references.push(reference);
90
+ }
91
+
92
+ return references;
93
+ }
94
+
95
+ function createSchemaExampleGroup(schema: OpenAPIV3.SchemaObject, map: any): ExampleGroup[] {
96
+ const example = generateSchemaExample(schema);
97
+ if (!example) {
98
+ return [];
99
+ }
100
+
101
+ const tabs: CodeBlockTab[] = [{
102
+ title: 'json',
103
+ language: 'json',
104
+ code: JSON.stringify(example, null, 2)
105
+ }];
106
+
107
+ return [{
108
+ description: 'Example',
109
+ examples: [{
110
+ codeblock: {
111
+ tabs
112
+ }
113
+ }]
114
+ }];
115
+ }
116
+
117
+ function definitionPropertyTypeDef(
118
+ schema: OpenAPIV3.SchemaObject | undefined,
119
+ ) {
120
+ if (!schema) {
121
+ return
122
+ }
123
+
124
+ let typeDef: SymbolDef | undefined
125
+ let oasSchema = schema as OasJSONSchema
126
+ if (oasSchema.type === "array") {
127
+ oasSchema = oasSchema.items as OasJSONSchema
128
+ }
129
+ if (oasSchema?.__internal_getRefPath) {
130
+ const symbolId = oasSchema.__internal_getRefPath()
131
+
132
+ typeDef = {
133
+ id: symbolId,
134
+ }
135
+ }
136
+
137
+ return typeDef
138
+ }
139
+
140
+ function generateSchemaExample(
141
+ schema: OpenAPIV3.SchemaObject,
142
+ visitedExample?: Map<OpenAPIV3.SchemaObject, any>,
143
+ parent?: any
144
+ ): any {
145
+ if (!schema) {
146
+ return null;
147
+ }
148
+ if (!visitedExample) {
149
+ visitedExample = new Map<OpenAPIV3.SchemaObject, any>();
150
+ }
151
+
152
+ const cached = visitedExample.get(schema)
153
+ if (cached) {
154
+ return JSON.parse(JSON.stringify(cached)); // Return a deep copy of the cached example
155
+ }
156
+ if (parent) {
157
+ visitedExample.set(schema, parent);
158
+ }
159
+
160
+ // Handle examples array
161
+ if ('examples' in schema && Array.isArray(schema.examples)) {
162
+ const v = schema.examples[0];
163
+ visitedExample.set(schema, v);
164
+ return v; // Return the first example from the examples array
165
+ }
166
+
167
+ // Handle single example
168
+ if ('example' in schema && schema.example !== undefined) {
169
+ const v = schema.example;
170
+ visitedExample.set(schema, v);
171
+ return v; // Return the single example if it exists
172
+ }
173
+
174
+ // Handle object type with properties
175
+ if (schema.type === 'object' && schema.properties) {
176
+ const result: Record<string, any> = {};
177
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
178
+ result[propName] = generateSchemaExample(propSchema as OpenAPIV3.SchemaObject, visitedExample, result);
179
+ }
180
+ visitedExample.set(schema, result);
181
+ return result;
182
+ }
183
+
184
+ // Handle array type
185
+ if (schema.type === 'array' && schema.items) {
186
+ const itemExample = generateSchemaExample(schema.items as OpenAPIV3.SchemaObject, visitedExample);
187
+ const v = itemExample ? [itemExample] : [];
188
+ visitedExample.set(schema, v);
189
+
190
+ return v; // Return an array with a single item example
191
+ }
192
+
193
+ // Handle primitive types with default values
194
+ switch (schema.type) {
195
+ case 'string':
196
+ return '';
197
+ case 'number':
198
+ case 'integer':
199
+ return 0;
200
+ case 'boolean':
201
+ return false;
202
+ default:
203
+ return null;
204
+ }
205
+ }
@@ -0,0 +1,417 @@
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 OpenAPISampler from "openapi-sampler";
7
+ import type {JSONSchema7} 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 }>
97
+ const codeSampleTabs: CodeBlockTab[] = codeSamples.map(sample => ({
98
+ title: sample.lang,
99
+ language: langFallback(sample.lang),
100
+ code: sample.source
101
+ }))
102
+
103
+ if (codeSampleTabs.length > 0) {
104
+ examples.push({
105
+ codeblock: {
106
+ tabs: codeSampleTabs
107
+ }
108
+ })
109
+
110
+ exampleGroups.push({
111
+ description: "Example request",
112
+ examples
113
+ })
114
+
115
+ return exampleGroups
116
+ }
117
+ }
118
+
119
+ // Create a single object with all parameters grouped by their location
120
+ const paramData = operation.schema.parameters
121
+ ? (operation.schema.parameters as OpenAPIV3.ParameterObject[]).reduce((acc, param) => {
122
+ const location = param.in || 'query'
123
+ if (!acc[location]) {
124
+ acc[location] = {}
125
+ }
126
+
127
+ let value = param.example
128
+ if (!value && param.schema) {
129
+ value = OpenAPISampler.sample(sanitizeSchema(param.schema as JSONSchema7))
130
+ }
131
+ if (value !== undefined) {
132
+ acc[location][param.name] = value
133
+ }
134
+ return acc
135
+ }, {} as Record<string, Record<string, any>>)
136
+ : {}
137
+
138
+ // Get request body data if it exists
139
+ let bodyData = {}
140
+ if (operation.schema.requestBody) {
141
+ const body = operation.schema.requestBody as OpenAPIV3.RequestBodyObject
142
+ const contentTypes = Object.keys(body.content)
143
+
144
+ if (contentTypes.length > 0) {
145
+ const contentType = contentTypes[contentTypes.length - 1]
146
+ const content = body.content[contentType]
147
+ let schema = content?.schema as JSONSchema7
148
+
149
+ if (schema) {
150
+ schema = fixAllOfBug(schema)
151
+ schema = sanitizeSchema(schema)
152
+
153
+ let requestData
154
+ if (content.examples) {
155
+ const requestExample = content.examples["request"]
156
+ if (requestExample && "value" in requestExample) {
157
+ requestData = requestExample.value
158
+ }
159
+ }
160
+
161
+ if (!requestData) {
162
+ requestData = OpenAPISampler.sample(schema)
163
+ }
164
+
165
+ if (contentType === 'application/x-www-form-urlencoded') {
166
+ bodyData = {formData: requestData}
167
+ } else {
168
+ bodyData = {body: requestData}
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ // Check if we have parameters or request body
175
+ const hasRequestBody = operation.schema.requestBody !== undefined
176
+ const hasParameters = Object.keys(paramData).length > 0
177
+
178
+ // Generate examples if we have either parameters or request body, or if we have neither
179
+ if (hasParameters || hasRequestBody || (!hasRequestBody && !hasParameters)) {
180
+ const langs = xDocsLanguages(operation.api) || DEFAULT_CODE_LANGUAGES
181
+ langs.forEach(lang => {
182
+ // TODO: needed for circural references - find better solution?
183
+ const operationCopy = excludeProperties(operation, ['api.components', 'api.paths']);
184
+ const {code} = oasToSnippet(oas, operationCopy, {
185
+ ...paramData,
186
+ ...bodyData
187
+ }, null, lang)
188
+
189
+ tabs.push({
190
+ title: lang,
191
+ language: lang,
192
+ code: code || ""
193
+ })
194
+ })
195
+
196
+ if (tabs.length > 0) {
197
+ examples.push({
198
+ codeblock: {
199
+ tabs
200
+ }
201
+ })
202
+ }
203
+
204
+ if (examples.length > 0) {
205
+ exampleGroups.push({
206
+ description: "Example request",
207
+ examples
208
+ })
209
+ }
210
+ }
211
+
212
+ return exampleGroups
213
+ }
214
+
215
+ function resBodyExmaples(operation: Operation, oas: Oas, vistedExamples?: Map<JSONSchema7 | JSONSchema7[], any>): ExampleGroup[] {
216
+ const exampleGroups: ExampleGroup[] = []
217
+
218
+ if (operation.schema.responses) {
219
+ const responses = operation.schema.responses as OpenAPIV3.ResponsesObject
220
+
221
+ const examples: Example[] = []
222
+
223
+ Object.entries(responses).forEach(([status, r]) => {
224
+ const response = r as OpenAPIV3.ResponseObject
225
+ if (!response.content) {
226
+ return
227
+ }
228
+
229
+ const contentTypes = Object.keys(response.content)
230
+ if (contentTypes.length === 0) {
231
+ return
232
+ }
233
+
234
+ const tabs: CodeBlockTab[] = []
235
+
236
+ for (const contentType of contentTypes) {
237
+ const content = response.content[contentType]
238
+ const schema = content?.schema as JSONSchema7
239
+
240
+ if (!schema) {
241
+ continue
242
+ }
243
+
244
+ let responseData
245
+ // Check for examples in the response content
246
+ if (content.examples) {
247
+ const responseExample = content.examples["response"]
248
+ if (responseExample && "value" in responseExample) {
249
+ responseData = responseExample.value
250
+ } else {
251
+ const namedExamples: Example[] = []
252
+ const exampleNames = Object.keys(content.examples)
253
+
254
+ exampleNames.forEach((exampleName) => {
255
+ const data = content?.examples?.[exampleName]
256
+
257
+ if (!data || !("value" in data) || typeof data.value != "object") {
258
+ return
259
+ }
260
+
261
+ namedExamples.push({
262
+ description: "",
263
+ codeblock: {
264
+ title: exampleName,
265
+ tabs: [
266
+ {
267
+ title: "application/json", // TODO: support multiple types
268
+ language: "json",
269
+ code: JSON.stringify(data.value, null, 2) || "",
270
+ }
271
+ ]
272
+ }
273
+ })
274
+ })
275
+
276
+ if (namedExamples.length === 1) {
277
+ const firstCodeblock = namedExamples[0].codeblock
278
+
279
+ tabs.push(
280
+ ...firstCodeblock.tabs.map(tab => ({
281
+ ...tab,
282
+ title: contentType
283
+ }))
284
+ )
285
+ } else {
286
+ exampleGroups.push({
287
+ description: "",
288
+ examples: namedExamples
289
+ })
290
+ }
291
+
292
+ continue
293
+ }
294
+ } else if (content.example) {
295
+ responseData = content.example
296
+ }
297
+
298
+ // If no example found, generate sample data from schema
299
+ if (!responseData) {
300
+ responseData = OpenAPISampler.sample(sanitizeSchema(schema))
301
+ }
302
+
303
+ let extension = "text"
304
+ switch (contentType) {
305
+ case "application/json":
306
+ case "application/problem+json":
307
+ case "application/vnd.api+json": {
308
+ extension = "json"
309
+ break
310
+ }
311
+ case "application/xml":
312
+ case "text/xml":
313
+ case "application/problem+xml": {
314
+ extension = "xml"
315
+ break
316
+ }
317
+ }
318
+
319
+ tabs.push({
320
+ title: contentType,
321
+ language: extension,
322
+ code: JSON.stringify(responseData, null, 2) || "",
323
+ })
324
+ }
325
+
326
+ if (tabs.length > 0) {
327
+ examples.push({
328
+ codeblock: {
329
+ title: status,
330
+ tabs
331
+ }
332
+ })
333
+ }
334
+ })
335
+
336
+ if (examples.length > 0) {
337
+ exampleGroups.push({
338
+ description: "Example response",
339
+ examples
340
+ })
341
+ }
342
+ }
343
+
344
+ return exampleGroups
345
+ }
346
+
347
+ /**
348
+ * fixAllOfBug fixes below case:
349
+ *
350
+ * ```yaml
351
+ * allOf:
352
+ * - $ref: '#/components/schemas/SomeSchema'
353
+ * - type: object
354
+ * required:
355
+ * properties:
356
+ * ```
357
+ *
358
+ */
359
+ function fixAllOfBug(schema: JSONSchema7) {
360
+ const modifiedSchema = {...schema}
361
+
362
+ if (schema?.allOf) {
363
+ schema.allOf.forEach((prop, i) => {
364
+ const propObj = prop as object
365
+
366
+ if ("properties" in propObj && !propObj["properties"]) {
367
+ delete modifiedSchema.allOf?.[i]
368
+ }
369
+ })
370
+ }
371
+
372
+ return modifiedSchema
373
+ }
374
+
375
+
376
+ function sanitizeSchema(
377
+ schema: any,
378
+ vistedExamples: Map<JSONSchema7 | JSONSchema7[], any> = new Map(),
379
+ parent?: any
380
+ ): any {
381
+ if (vistedExamples.has(schema)) {
382
+ const cached = vistedExamples.get(schema);
383
+
384
+ if (typeof cached === 'object') {
385
+ return JSON.parse(JSON.stringify(cached)); // Return a deep copy of the cached schema
386
+ }
387
+
388
+ return cached
389
+ }
390
+
391
+ if (parent) {
392
+ vistedExamples.set(schema, parent);
393
+ }
394
+
395
+ if (!schema || typeof schema !== 'object') {
396
+ vistedExamples.set(schema, schema);
397
+ return schema;
398
+ }
399
+
400
+ if (Array.isArray(schema)) {
401
+ const v = schema.map(item => sanitizeSchema(item, vistedExamples));
402
+ vistedExamples.set(schema, v);
403
+ return v;
404
+ }
405
+
406
+ const cleaned: any = {};
407
+ for (const [key, value] of Object.entries(schema)) {
408
+ if (key === "__UNSAFE_refPath") {
409
+ continue;
410
+ }
411
+ if (!BUILT_IN_PROPERTIES[key]) {
412
+ cleaned[key] = sanitizeSchema(value, vistedExamples, cleaned);
413
+ }
414
+ }
415
+ vistedExamples.set(schema, cleaned);
416
+ return cleaned;
417
+ }
@@ -1,6 +1,9 @@
1
1
  import {OpenAPIV3} from "openapi-types";
2
+
2
3
  import {DefinitionProperty} from "@xyd-js/uniform";
3
4
 
5
+ import { schemaObjectToUniformDefinitionPropertyMeta } from "../oas-core";
6
+
4
7
  // oapParametersToDefinitionProperties converts OpenAPI parameters to uniform DefinitionProperties
5
8
  export function oapParametersToDefinitionProperties(
6
9
  parameters: OpenAPIV3.ParameterObject[]
@@ -14,12 +17,23 @@ export function oapParametersToDefinitionProperties(
14
17
 
15
18
  const schema = param.schema as OpenAPIV3.SchemaObject
16
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
+
17
30
  const property: DefinitionProperty = {
18
31
  name: param.name,
19
- type: schema.type || "",
20
- description: param.description || ""
32
+ type: schema?.type || oapV2Type || "",
33
+ description: param.description || "",
34
+ meta
21
35
  }
22
-
36
+
23
37
  parameterIn[param.in].push(property)
24
38
  })
25
39