@gitbook/react-openapi 1.1.6 → 1.1.7

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 (50) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/InteractiveSection.d.ts +0 -2
  3. package/dist/InteractiveSection.jsx +3 -4
  4. package/dist/OpenAPICodeSample.jsx +4 -4
  5. package/dist/OpenAPICodeSampleInteractive.d.ts +4 -3
  6. package/dist/OpenAPICodeSampleInteractive.jsx +22 -15
  7. package/dist/OpenAPICopyButton.d.ts +7 -0
  8. package/dist/OpenAPICopyButton.jsx +6 -6
  9. package/dist/OpenAPIOperation.jsx +21 -1
  10. package/dist/OpenAPIPath.jsx +2 -2
  11. package/dist/OpenAPIRequestBody.jsx +1 -1
  12. package/dist/OpenAPIResponse.jsx +1 -1
  13. package/dist/OpenAPIResponses.jsx +2 -2
  14. package/dist/OpenAPISchema.d.ts +5 -14
  15. package/dist/OpenAPISchema.jsx +79 -28
  16. package/dist/OpenAPISchemaName.jsx +1 -0
  17. package/dist/OpenAPISchemaServer.d.ts +12 -0
  18. package/dist/OpenAPISchemaServer.jsx +8 -0
  19. package/dist/OpenAPISpec.d.ts +0 -6
  20. package/dist/OpenAPISpec.jsx +5 -11
  21. package/dist/OpenAPITabs.jsx +3 -11
  22. package/dist/code-samples.js +44 -9
  23. package/dist/decycle.d.ts +2 -0
  24. package/dist/decycle.js +70 -0
  25. package/dist/schemas/OpenAPISchemas.jsx +1 -1
  26. package/dist/schemas/resolveOpenAPISchemas.d.ts +2 -6
  27. package/dist/schemas/resolveOpenAPISchemas.js +1 -21
  28. package/dist/tsconfig.build.tsbuildinfo +1 -1
  29. package/dist/types.d.ts +2 -5
  30. package/package.json +1 -1
  31. package/src/InteractiveSection.tsx +2 -6
  32. package/src/OpenAPICodeSample.tsx +16 -5
  33. package/src/OpenAPICodeSampleInteractive.tsx +53 -28
  34. package/src/OpenAPICopyButton.tsx +17 -4
  35. package/src/OpenAPIOperation.tsx +39 -2
  36. package/src/OpenAPIPath.tsx +2 -2
  37. package/src/OpenAPIRequestBody.tsx +1 -1
  38. package/src/OpenAPIResponse.tsx +4 -4
  39. package/src/OpenAPIResponses.tsx +1 -5
  40. package/src/OpenAPISchema.tsx +152 -58
  41. package/src/OpenAPISchemaName.tsx +3 -0
  42. package/src/OpenAPISchemaServer.tsx +34 -0
  43. package/src/OpenAPISpec.tsx +13 -11
  44. package/src/OpenAPITabs.tsx +3 -13
  45. package/src/code-samples.test.ts +69 -1
  46. package/src/code-samples.ts +45 -9
  47. package/src/decycle.ts +68 -0
  48. package/src/schemas/OpenAPISchemas.tsx +1 -1
  49. package/src/schemas/resolveOpenAPISchemas.ts +3 -31
  50. package/src/types.ts +6 -6
@@ -1,16 +1,22 @@
1
- import type { OpenAPIV3 } from '@gitbook/openapi-parser';
1
+ 'use client';
2
+ // This component does not use any client feature but we don't want to
3
+ // render it server-side because it has recursion.
4
+
5
+ import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
2
6
  import { useId } from 'react';
3
7
 
4
8
  import clsx from 'clsx';
5
9
  import { Markdown } from './Markdown';
10
+ import { OpenAPICopyButton } from './OpenAPICopyButton';
6
11
  import { OpenAPIDisclosure } from './OpenAPIDisclosure';
7
12
  import { OpenAPISchemaName } from './OpenAPISchemaName';
13
+ import { retrocycle } from './decycle';
8
14
  import type { OpenAPIClientContext } from './types';
9
15
  import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
10
16
 
11
17
  type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
12
18
 
13
- interface OpenAPISchemaPropertyEntry {
19
+ export interface OpenAPISchemaPropertyEntry {
14
20
  propertyName?: string | undefined;
15
21
  required?: boolean | undefined;
16
22
  schema: OpenAPIV3.SchemaObject;
@@ -22,15 +28,10 @@ interface OpenAPISchemaPropertyEntry {
22
28
  function OpenAPISchemaProperty(props: {
23
29
  property: OpenAPISchemaPropertyEntry;
24
30
  context: OpenAPIClientContext;
25
- circularRefs?: CircularRefsIds;
31
+ circularRefs: CircularRefsIds;
26
32
  className?: string;
27
33
  }) {
28
- const {
29
- property,
30
- circularRefs: parentCircularRefs = new Map<OpenAPIV3.SchemaObject, string>(),
31
- context,
32
- className,
33
- } = props;
34
+ const { circularRefs: parentCircularRefs, context, className, property } = props;
34
35
 
35
36
  const { schema } = property;
36
37
 
@@ -40,37 +41,43 @@ function OpenAPISchemaProperty(props: {
40
41
  <div id={id} className={clsx('openapi-schema', className)}>
41
42
  <OpenAPISchemaPresentation property={property} />
42
43
  {(() => {
43
- const parentCircularRef = parentCircularRefs.get(schema);
44
-
44
+ const circularRefId = parentCircularRefs.get(schema);
45
45
  // Avoid recursing infinitely, and instead render a link to the parent schema
46
- if (parentCircularRef) {
47
- return <OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />;
46
+ if (circularRefId) {
47
+ return <OpenAPISchemaCircularRef id={circularRefId} schema={schema} />;
48
48
  }
49
49
 
50
- const circularRefs = parentCircularRefs.set(schema, id);
50
+ const circularRefs = new Map(parentCircularRefs);
51
+ circularRefs.set(schema, id);
52
+
51
53
  const properties = getSchemaProperties(schema);
52
- const alternatives = getSchemaAlternatives(schema, new Set(circularRefs.keys()));
53
- return (
54
- <>
55
- {alternatives?.map((schema, index) => (
56
- <OpenAPISchemaAlternative
57
- key={index}
58
- schema={schema}
54
+ if (properties?.length) {
55
+ return (
56
+ <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
57
+ <OpenAPISchemaProperties
58
+ properties={properties}
59
59
  circularRefs={circularRefs}
60
60
  context={context}
61
61
  />
62
- ))}
63
- {properties?.length ? (
64
- <OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
65
- <OpenAPISchemaProperties
66
- properties={properties}
67
- circularRefs={circularRefs}
68
- context={context}
69
- />
70
- </OpenAPIDisclosure>
71
- ) : null}
72
- </>
73
- );
62
+ </OpenAPIDisclosure>
63
+ );
64
+ }
65
+
66
+ const ancestors = new Set(circularRefs.keys());
67
+ const alternatives = getSchemaAlternatives(schema, ancestors);
68
+
69
+ if (alternatives) {
70
+ return alternatives.map((schema, index) => (
71
+ <OpenAPISchemaAlternative
72
+ key={index}
73
+ schema={schema}
74
+ circularRefs={circularRefs}
75
+ context={context}
76
+ />
77
+ ));
78
+ }
79
+
80
+ return null;
74
81
  })()}
75
82
  </div>
76
83
  );
@@ -79,41 +86,77 @@ function OpenAPISchemaProperty(props: {
79
86
  /**
80
87
  * Render a set of properties of an OpenAPI schema.
81
88
  */
82
- export function OpenAPISchemaProperties(props: {
89
+ function OpenAPISchemaProperties(props: {
83
90
  id?: string;
84
91
  properties: OpenAPISchemaPropertyEntry[];
85
92
  circularRefs?: CircularRefsIds;
86
93
  context: OpenAPIClientContext;
87
94
  }) {
88
- const { id, properties, circularRefs, context } = props;
95
+ const {
96
+ id,
97
+ properties,
98
+ circularRefs = new Map<OpenAPIV3.SchemaObject, string>(),
99
+ context,
100
+ } = props;
89
101
 
90
102
  return (
91
103
  <div id={id} className="openapi-schema-properties">
92
- {properties.map((property, index) => (
93
- <OpenAPISchemaProperty
94
- key={index}
95
- circularRefs={circularRefs}
96
- property={property}
97
- context={context}
98
- />
99
- ))}
104
+ {properties.map((property, index) => {
105
+ return (
106
+ <OpenAPISchemaProperty
107
+ key={index}
108
+ circularRefs={circularRefs}
109
+ property={property}
110
+ context={context}
111
+ />
112
+ );
113
+ })}
100
114
  </div>
101
115
  );
102
116
  }
103
117
 
118
+ export function OpenAPISchemaPropertiesFromServer(props: {
119
+ id?: string;
120
+ properties: string;
121
+ context: OpenAPIClientContext;
122
+ }) {
123
+ return (
124
+ <OpenAPISchemaProperties
125
+ id={props.id}
126
+ properties={JSON.parse(props.properties, retrocycle())}
127
+ context={props.context}
128
+ />
129
+ );
130
+ }
131
+
104
132
  /**
105
133
  * Render a root schema (such as the request body or response body).
106
134
  */
107
- export function OpenAPIRootSchema(props: {
135
+ function OpenAPIRootSchema(props: {
108
136
  schema: OpenAPIV3.SchemaObject;
109
137
  context: OpenAPIClientContext;
138
+ circularRefs?: CircularRefsIds;
110
139
  }) {
111
- const { schema, context } = props;
140
+ const {
141
+ schema,
142
+ context,
143
+ circularRefs: parentCircularRefs = new Map<OpenAPIV3.SchemaObject, string>(),
144
+ } = props;
112
145
 
146
+ const id = useId();
113
147
  const properties = getSchemaProperties(schema);
114
148
 
115
149
  if (properties?.length) {
116
- return <OpenAPISchemaProperties properties={properties} context={context} />;
150
+ const circularRefs = new Map(parentCircularRefs);
151
+ circularRefs.set(schema, id);
152
+
153
+ return (
154
+ <OpenAPISchemaProperties
155
+ properties={properties}
156
+ circularRefs={circularRefs}
157
+ context={context}
158
+ />
159
+ );
117
160
  }
118
161
 
119
162
  return (
@@ -121,6 +164,19 @@ export function OpenAPIRootSchema(props: {
121
164
  className="openapi-schema-root"
122
165
  property={{ schema }}
123
166
  context={context}
167
+ circularRefs={parentCircularRefs}
168
+ />
169
+ );
170
+ }
171
+
172
+ export function OpenAPIRootSchemaFromServer(props: {
173
+ schema: string;
174
+ context: OpenAPIClientContext;
175
+ }) {
176
+ return (
177
+ <OpenAPIRootSchema
178
+ schema={JSON.parse(props.schema, retrocycle())}
179
+ context={props.context}
124
180
  />
125
181
  );
126
182
  }
@@ -136,6 +192,7 @@ function OpenAPISchemaAlternative(props: {
136
192
  context: OpenAPIClientContext;
137
193
  }) {
138
194
  const { schema, circularRefs, context } = props;
195
+
139
196
  const description = resolveDescription(schema);
140
197
  const properties = getSchemaProperties(schema);
141
198
 
@@ -180,20 +237,59 @@ function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaO
180
237
  /**
181
238
  * Render the enum value for a schema.
182
239
  */
183
- function OpenAPISchemaEnum(props: { enumValues: any[] }) {
184
- const { enumValues } = props;
240
+ function OpenAPISchemaEnum(props: {
241
+ schema: OpenAPIV3.SchemaObject & OpenAPICustomOperationProperties;
242
+ }) {
243
+ const { schema } = props;
244
+
245
+ const enumValues = (() => {
246
+ // Render x-gitbook-enum first, as it has a different format
247
+ if (schema['x-gitbook-enum']) {
248
+ return Object.entries(schema['x-gitbook-enum']).map(([name, { description }]) => {
249
+ return {
250
+ value: name,
251
+ description,
252
+ };
253
+ });
254
+ }
255
+
256
+ if (schema['x-enumDescriptions']) {
257
+ return Object.entries(schema['x-enumDescriptions']).map(([value, description]) => {
258
+ return {
259
+ value,
260
+ description,
261
+ };
262
+ });
263
+ }
264
+
265
+ return schema.enum?.map((value) => {
266
+ return {
267
+ value,
268
+ description: undefined,
269
+ };
270
+ });
271
+ })();
272
+
273
+ if (!enumValues?.length) {
274
+ return null;
275
+ }
185
276
 
186
277
  return (
187
278
  <div className="openapi-schema-enum">
188
- <span>
189
- Options:{' '}
190
- {enumValues.map((value, index) => (
279
+ <span>Available options:</span>
280
+ <div className="openapi-schema-enum-list">
281
+ {enumValues.map((item, index) => (
191
282
  <span key={index} className="openapi-schema-enum-value">
192
- <code>{`${value}`}</code>
193
- {index < enumValues.length - 1 ? ', ' : ''}
283
+ <OpenAPICopyButton
284
+ value={item.value}
285
+ label={item.description}
286
+ withTooltip={!!item.description}
287
+ >
288
+ <code>{`${item.value}`}</code>
289
+ </OpenAPICopyButton>
194
290
  </span>
195
291
  ))}
196
- </span>
292
+ </div>
197
293
  </div>
198
294
  );
199
295
  }
@@ -238,9 +334,7 @@ function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry
238
334
  Pattern: <code>{schema.pattern}</code>
239
335
  </div>
240
336
  ) : null}
241
- {schema.enum && schema.enum.length > 0 ? (
242
- <OpenAPISchemaEnum enumValues={schema.enum} />
243
- ) : null}
337
+ <OpenAPISchemaEnum schema={schema} />
244
338
  </div>
245
339
  );
246
340
  }
@@ -365,7 +459,7 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
365
459
  // Otherwise try to infer a nice title
366
460
  let type = 'any';
367
461
 
368
- if (schema.enum) {
462
+ if (schema.enum || schema['x-enumDescriptions'] || schema['x-gitbook-enum']) {
369
463
  type = `${schema.type} · enum`;
370
464
  // check array AND schema.items as this is sometimes null despite what the type indicates
371
465
  } else if (schema.type === 'array' && !!schema.items) {
@@ -32,6 +32,9 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
32
32
  ) : null}
33
33
  </span>
34
34
  {schema?.readOnly ? <span className="openapi-schema-readonly">read-only</span> : null}
35
+ {schema?.writeOnly ? (
36
+ <span className="openapi-schema-writeonly">write-only</span>
37
+ ) : null}
35
38
  {required ? (
36
39
  <span className="openapi-schema-required">required</span>
37
40
  ) : (
@@ -0,0 +1,34 @@
1
+ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
+ import {
3
+ OpenAPIRootSchemaFromServer,
4
+ OpenAPISchemaPropertiesFromServer,
5
+ type OpenAPISchemaPropertyEntry,
6
+ } from './OpenAPISchema';
7
+ import { decycle } from './decycle';
8
+ import type { OpenAPIClientContext } from './types';
9
+
10
+ export function OpenAPISchemaProperties(props: {
11
+ id?: string;
12
+ properties: OpenAPISchemaPropertyEntry[];
13
+ context: OpenAPIClientContext;
14
+ }) {
15
+ return (
16
+ <OpenAPISchemaPropertiesFromServer
17
+ id={props.id}
18
+ properties={JSON.stringify(props.properties, decycle())}
19
+ context={props.context}
20
+ />
21
+ );
22
+ }
23
+
24
+ export function OpenAPIRootSchema(props: {
25
+ schema: OpenAPIV3.SchemaObject;
26
+ context: OpenAPIClientContext;
27
+ }) {
28
+ return (
29
+ <OpenAPIRootSchemaFromServer
30
+ schema={JSON.stringify(props.schema, decycle())}
31
+ context={props.context}
32
+ />
33
+ );
34
+ }
@@ -2,18 +2,12 @@ import type { OpenAPI } from '@gitbook/openapi-parser';
2
2
 
3
3
  import { OpenAPIRequestBody } from './OpenAPIRequestBody';
4
4
  import { OpenAPIResponses } from './OpenAPIResponses';
5
- import { OpenAPISchemaProperties } from './OpenAPISchema';
5
+ import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
6
6
  import { OpenAPISecurities } from './OpenAPISecurities';
7
7
  import { StaticSection } from './StaticSection';
8
8
  import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
9
9
  import { parameterToProperty } from './utils';
10
10
 
11
- /**
12
- * Client component to render the spec for the request and response.
13
- *
14
- * We use a client component as rendering recursive JSON schema in the server is expensive
15
- * (the entire schema is rendered at once, while the client component only renders the visible part)
16
- */
17
11
  export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAPIClientContext }) {
18
12
  const { data, context } = props;
19
13
 
@@ -25,13 +19,13 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
25
19
  return (
26
20
  <>
27
21
  {securities.length > 0 ? (
28
- <OpenAPISecurities securities={securities} context={context} />
22
+ <OpenAPISecurities key="securities" securities={securities} context={context} />
29
23
  ) : null}
30
24
 
31
25
  {parameterGroups.map((group) => {
32
26
  return (
33
27
  <StaticSection
34
- key={group.key}
28
+ key={`parameter-${group.key}`}
35
29
  className="openapi-parameters"
36
30
  header={group.label}
37
31
  >
@@ -44,10 +38,18 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
44
38
  })}
45
39
 
46
40
  {operation.requestBody ? (
47
- <OpenAPIRequestBody requestBody={operation.requestBody} context={context} />
41
+ <OpenAPIRequestBody
42
+ key="body"
43
+ requestBody={operation.requestBody}
44
+ context={context}
45
+ />
48
46
  ) : null}
49
47
  {operation.responses ? (
50
- <OpenAPIResponses responses={operation.responses} context={context} />
48
+ <OpenAPIResponses
49
+ key="responses"
50
+ responses={operation.responses}
51
+ context={context}
52
+ />
51
53
  ) : null}
52
54
  </>
53
55
  );
@@ -137,21 +137,11 @@ export function OpenAPITabsPanels() {
137
137
  const key = selectedTab.key.toString();
138
138
 
139
139
  return (
140
- <TabPanel key={key} id={key} className="openapi-tabs-panel">
141
- {selectedTab.body}
140
+ <TabPanel id={key} className="openapi-tabs-panel">
141
+ <div className="openapi-tabs-body">{selectedTab.body}</div>
142
142
  {selectedTab.footer ? (
143
- <OpenAPITabsPanelFooter>{selectedTab.footer}</OpenAPITabsPanelFooter>
143
+ <div className="openapi-tabs-footer">{selectedTab.footer}</div>
144
144
  ) : null}
145
145
  </TabPanel>
146
146
  );
147
147
  }
148
-
149
- /**
150
- * The OpenAPI Tabs panel footer component.
151
- * This component should be used as a child of the OpenAPITabs component.
152
- */
153
- function OpenAPITabsPanelFooter(props: { children: React.ReactNode }) {
154
- const { children } = props;
155
-
156
- return <div className="openapi-tabs-footer">{children}</div>;
157
- }
@@ -110,6 +110,23 @@ describe('curL code sample generator', () => {
110
110
  );
111
111
  });
112
112
 
113
+ it('should convert json to xml body properly', () => {
114
+ const input: CodeSampleInput = {
115
+ method: 'GET',
116
+ url: 'https://example.com/path',
117
+ headers: {
118
+ 'Content-Type': 'application/xml',
119
+ },
120
+ body: '{ "key": "value" }',
121
+ };
122
+
123
+ const output = generator?.generate(input);
124
+
125
+ expect(output).toBe(
126
+ "curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/xml' \\\n --data-binary $'<?xml version=1.0?>\n <key>value</key>\n '"
127
+ );
128
+ });
129
+
113
130
  it('should format application/graphql body properly', () => {
114
131
  const input: CodeSampleInput = {
115
132
  method: 'GET',
@@ -258,6 +275,23 @@ describe('javascript code sample generator', () => {
258
275
  );
259
276
  });
260
277
 
278
+ it('should convert json to xml body properly', () => {
279
+ const input: CodeSampleInput = {
280
+ method: 'GET',
281
+ url: 'https://example.com/path',
282
+ headers: {
283
+ 'Content-Type': 'application/xml',
284
+ },
285
+ body: '{ "key": "value" }',
286
+ };
287
+
288
+ const output = generator?.generate(input);
289
+
290
+ expect(output).toBe(
291
+ 'const xml = `\n <?xml version=1.0?>\n <key>value</key>\n`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/xml"\n },\n body: xml\n});\n\nconst data = await response.json();'
292
+ );
293
+ });
294
+
261
295
  it('should format application/graphql body properly', () => {
262
296
  const input: CodeSampleInput = {
263
297
  method: 'GET',
@@ -406,6 +440,23 @@ describe('python code sample generator', () => {
406
440
  );
407
441
  });
408
442
 
443
+ it('should convert json to xml body properly', () => {
444
+ const input: CodeSampleInput = {
445
+ method: 'GET',
446
+ url: 'https://example.com/path',
447
+ headers: {
448
+ 'Content-Type': 'application/xml',
449
+ },
450
+ body: '{ "key": "value" }',
451
+ };
452
+
453
+ const output = generator?.generate(input);
454
+
455
+ expect(output).toBe(
456
+ 'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/xml"},\n data="<?xml version=1.0?>\\n<key>value</key>\\n"\n)\n\ndata = response.json()'
457
+ );
458
+ });
459
+
409
460
  it('should format application/graphql body properly', () => {
410
461
  const input: CodeSampleInput = {
411
462
  method: 'GET',
@@ -514,7 +565,7 @@ describe('http code sample generator', () => {
514
565
  const output = generator?.generate(input);
515
566
 
516
567
  expect(output).toBe(
517
- 'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 15\nAccept: */*\n\n"key=value"'
568
+ 'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 15\nAccept: */*\n\n"key=\'value\'"'
518
569
  );
519
570
  });
520
571
 
@@ -554,6 +605,23 @@ describe('http code sample generator', () => {
554
605
  );
555
606
  });
556
607
 
608
+ it('should convert json to xml body properly', () => {
609
+ const input: CodeSampleInput = {
610
+ method: 'GET',
611
+ url: 'https://example.com/path',
612
+ headers: {
613
+ 'Content-Type': 'application/xml',
614
+ },
615
+ body: '{ "key": "value" }',
616
+ };
617
+
618
+ const output = generator?.generate(input);
619
+
620
+ expect(output).toBe(
621
+ 'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/xml\nContent-Length: 24\nAccept: */*\n\n"<?xml version=1.0?>\n<key>value</key>\n"'
622
+ );
623
+ });
624
+
557
625
  it('should format application/graphql body properly', () => {
558
626
  const input: CodeSampleInput = {
559
627
  method: 'GET',
@@ -8,6 +8,7 @@ import {
8
8
  isText,
9
9
  isXML,
10
10
  } from './contentTypeChecks';
11
+ import { json2xml } from './json2xml';
11
12
  import { stringifyOpenAPI } from './stringifyOpenAPI';
12
13
 
13
14
  export interface CodeSampleInput {
@@ -238,7 +239,10 @@ const BodyGenerators = {
238
239
  : String(body);
239
240
  } else if (isText(contentType)) {
240
241
  body = `--data '${String(body).replace(/"/g, '')}'`;
241
- } else if (isXML(contentType) || isCSV(contentType)) {
242
+ } else if (isXML(contentType)) {
243
+ // Convert to XML and ensure proper formatting
244
+ body = `--data-binary $'${convertBodyToXML(body)}'`;
245
+ } else if (isCSV(contentType)) {
242
246
  // We use --data-binary to avoid cURL converting newlines to \r\n
243
247
  body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '').replace(/\\n/g, '\n')}'`;
244
248
  } else if (isGraphQL(contentType)) {
@@ -312,7 +316,9 @@ const BodyGenerators = {
312
316
  body = 'formData';
313
317
  } else if (isXML(contentType)) {
314
318
  code += 'const xml = `\n';
315
- code += indent(String(body), 4);
319
+
320
+ // Convert JSON to XML if needed
321
+ code += indent(convertBodyToXML(body), 4);
316
322
  code += '`;\n\n';
317
323
  body = 'xml';
318
324
  } else if (isText(contentType)) {
@@ -346,6 +352,11 @@ const BodyGenerators = {
346
352
  body = 'files';
347
353
  }
348
354
 
355
+ if (isXML(contentType)) {
356
+ // Convert JSON to XML if needed
357
+ body = convertBodyToXML(body);
358
+ }
359
+
349
360
  return { body, code, headers };
350
361
  },
351
362
  getHTTPBody: (body: any, headers?: Record<string, string>) => {
@@ -358,23 +369,48 @@ const BodyGenerators = {
358
369
  formUrlEncoded: () => {
359
370
  const encoded = isPlainObject(body)
360
371
  ? Object.entries(body)
361
- .map(([key, value]) => `${key}=${String(value)}`)
372
+ .map(([key, value]) => `${key}=${stringifyOpenAPI(value)}`)
362
373
  .join('&')
363
- : String(body);
364
- return `"${encoded}"`;
374
+ : stringifyOpenAPI(body);
375
+ return `"${encoded.replace(/"/g, "'")}"`;
365
376
  },
366
377
  text: () => `"${String(body)}"`,
367
- xmlOrCsv: () => `"${stringifyOpenAPI(body).replace(/"/g, '')}"`,
378
+ xml: () => {
379
+ // Convert JSON to XML if needed
380
+ return `"${convertBodyToXML(body)}"`;
381
+ },
382
+ csv: () => `"${stringifyOpenAPI(body).replace(/"/g, '')}"`,
368
383
  default: () => `${stringifyOpenAPI(body, null, 2)}`,
369
384
  };
370
385
 
371
386
  if (isPDF(contentType)) return typeHandlers.pdf();
372
387
  if (isFormUrlEncoded(contentType)) return typeHandlers.formUrlEncoded();
373
388
  if (isText(contentType)) return typeHandlers.text();
374
- if (isXML(contentType) || isCSV(contentType)) {
375
- return typeHandlers.xmlOrCsv();
376
- }
389
+ if (isXML(contentType)) return typeHandlers.xml();
390
+ if (isCSV(contentType)) return typeHandlers.csv();
377
391
 
378
392
  return typeHandlers.default();
379
393
  },
380
394
  };
395
+
396
+ /**
397
+ * Converts a body to XML format
398
+ */
399
+ function convertBodyToXML(body: any): string {
400
+ // If body is already a string and looks like XML, return it as is
401
+ if (typeof body === 'string' && body.trim().startsWith('<')) {
402
+ return body;
403
+ }
404
+
405
+ // If body is not an object, try to parse it as JSON
406
+ if (typeof body !== 'object' || body === null) {
407
+ try {
408
+ body = JSON.parse(body);
409
+ } catch {
410
+ // If parsing fails, return the original body
411
+ return body;
412
+ }
413
+ }
414
+
415
+ return json2xml(body).replace(/"/g, '').replace(/\\n/g, '\n').replace(/\\t/g, '\t');
416
+ }