@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.
- package/CHANGELOG.md +19 -0
- package/dist/InteractiveSection.d.ts +0 -2
- package/dist/InteractiveSection.jsx +3 -4
- package/dist/OpenAPICodeSample.jsx +4 -4
- package/dist/OpenAPICodeSampleInteractive.d.ts +4 -3
- package/dist/OpenAPICodeSampleInteractive.jsx +22 -15
- package/dist/OpenAPICopyButton.d.ts +7 -0
- package/dist/OpenAPICopyButton.jsx +6 -6
- package/dist/OpenAPIOperation.jsx +21 -1
- package/dist/OpenAPIPath.jsx +2 -2
- package/dist/OpenAPIRequestBody.jsx +1 -1
- package/dist/OpenAPIResponse.jsx +1 -1
- package/dist/OpenAPIResponses.jsx +2 -2
- package/dist/OpenAPISchema.d.ts +5 -14
- package/dist/OpenAPISchema.jsx +79 -28
- package/dist/OpenAPISchemaName.jsx +1 -0
- package/dist/OpenAPISchemaServer.d.ts +12 -0
- package/dist/OpenAPISchemaServer.jsx +8 -0
- package/dist/OpenAPISpec.d.ts +0 -6
- package/dist/OpenAPISpec.jsx +5 -11
- package/dist/OpenAPITabs.jsx +3 -11
- package/dist/code-samples.js +44 -9
- package/dist/decycle.d.ts +2 -0
- package/dist/decycle.js +70 -0
- package/dist/schemas/OpenAPISchemas.jsx +1 -1
- package/dist/schemas/resolveOpenAPISchemas.d.ts +2 -6
- package/dist/schemas/resolveOpenAPISchemas.js +1 -21
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -5
- package/package.json +1 -1
- package/src/InteractiveSection.tsx +2 -6
- package/src/OpenAPICodeSample.tsx +16 -5
- package/src/OpenAPICodeSampleInteractive.tsx +53 -28
- package/src/OpenAPICopyButton.tsx +17 -4
- package/src/OpenAPIOperation.tsx +39 -2
- package/src/OpenAPIPath.tsx +2 -2
- package/src/OpenAPIRequestBody.tsx +1 -1
- package/src/OpenAPIResponse.tsx +4 -4
- package/src/OpenAPIResponses.tsx +1 -5
- package/src/OpenAPISchema.tsx +152 -58
- package/src/OpenAPISchemaName.tsx +3 -0
- package/src/OpenAPISchemaServer.tsx +34 -0
- package/src/OpenAPISpec.tsx +13 -11
- package/src/OpenAPITabs.tsx +3 -13
- package/src/code-samples.test.ts +69 -1
- package/src/code-samples.ts +45 -9
- package/src/decycle.ts +68 -0
- package/src/schemas/OpenAPISchemas.tsx +1 -1
- package/src/schemas/resolveOpenAPISchemas.ts +3 -31
- package/src/types.ts +6 -6
package/src/OpenAPISchema.tsx
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
44
|
-
|
|
44
|
+
const circularRefId = parentCircularRefs.get(schema);
|
|
45
45
|
// Avoid recursing infinitely, and instead render a link to the parent schema
|
|
46
|
-
if (
|
|
47
|
-
return <OpenAPISchemaCircularRef id={
|
|
46
|
+
if (circularRefId) {
|
|
47
|
+
return <OpenAPISchemaCircularRef id={circularRefId} schema={schema} />;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const circularRefs = parentCircularRefs
|
|
50
|
+
const circularRefs = new Map(parentCircularRefs);
|
|
51
|
+
circularRefs.set(schema, id);
|
|
52
|
+
|
|
51
53
|
const properties = getSchemaProperties(schema);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
89
|
+
function OpenAPISchemaProperties(props: {
|
|
83
90
|
id?: string;
|
|
84
91
|
properties: OpenAPISchemaPropertyEntry[];
|
|
85
92
|
circularRefs?: CircularRefsIds;
|
|
86
93
|
context: OpenAPIClientContext;
|
|
87
94
|
}) {
|
|
88
|
-
const {
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
135
|
+
function OpenAPIRootSchema(props: {
|
|
108
136
|
schema: OpenAPIV3.SchemaObject;
|
|
109
137
|
context: OpenAPIClientContext;
|
|
138
|
+
circularRefs?: CircularRefsIds;
|
|
110
139
|
}) {
|
|
111
|
-
const {
|
|
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
|
-
|
|
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: {
|
|
184
|
-
|
|
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
|
-
|
|
190
|
-
{enumValues.map((
|
|
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
|
-
<
|
|
193
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
+
}
|
package/src/OpenAPISpec.tsx
CHANGED
|
@@ -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 './
|
|
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
|
|
41
|
+
<OpenAPIRequestBody
|
|
42
|
+
key="body"
|
|
43
|
+
requestBody={operation.requestBody}
|
|
44
|
+
context={context}
|
|
45
|
+
/>
|
|
48
46
|
) : null}
|
|
49
47
|
{operation.responses ? (
|
|
50
|
-
<OpenAPIResponses
|
|
48
|
+
<OpenAPIResponses
|
|
49
|
+
key="responses"
|
|
50
|
+
responses={operation.responses}
|
|
51
|
+
context={context}
|
|
52
|
+
/>
|
|
51
53
|
) : null}
|
|
52
54
|
</>
|
|
53
55
|
);
|
package/src/OpenAPITabs.tsx
CHANGED
|
@@ -137,21 +137,11 @@ export function OpenAPITabsPanels() {
|
|
|
137
137
|
const key = selectedTab.key.toString();
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
|
-
<TabPanel
|
|
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
|
-
<
|
|
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
|
-
}
|
package/src/code-samples.test.ts
CHANGED
|
@@ -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
|
|
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',
|
package/src/code-samples.ts
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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}=${
|
|
372
|
+
.map(([key, value]) => `${key}=${stringifyOpenAPI(value)}`)
|
|
362
373
|
.join('&')
|
|
363
|
-
:
|
|
364
|
-
return `"${encoded}"`;
|
|
374
|
+
: stringifyOpenAPI(body);
|
|
375
|
+
return `"${encoded.replace(/"/g, "'")}"`;
|
|
365
376
|
},
|
|
366
377
|
text: () => `"${String(body)}"`,
|
|
367
|
-
|
|
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)
|
|
375
|
-
|
|
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
|
+
}
|